test(core/browser/platform): 26 extreme RED/GREEN TDD cycles and full regression hardening (3,235 PASS)
This commit is contained in:
parent
bcd242691f
commit
8677853822
100 changed files with 4814 additions and 333 deletions
|
|
@ -0,0 +1,125 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Core.Platform;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Platform;
|
||||
|
||||
public class MediaEnhanceAndDeduplicationExtremeRedTests
|
||||
{
|
||||
private const string SampleInput = "C:\\Media\\video.mp4";
|
||||
private const string SampleOutput = "C:\\Media\\converted.mp4";
|
||||
|
||||
// ==========================================
|
||||
// 1. HdrToneMapper
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public void HdrToneMapper_Build10BitPassthroughArgs_SameInputAndOutput_ThrowsArgumentException()
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
HdrToneMapper.Build10BitPassthroughArgs(SampleInput, SampleInput));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, SampleOutput)]
|
||||
[InlineData(SampleInput, null)]
|
||||
[InlineData("", SampleOutput)]
|
||||
[InlineData(SampleInput, " ")]
|
||||
public void HdrToneMapper_Build10BitPassthroughArgs_NullOrWhitespacePaths_ThrowsArgumentException(string? inPath, string? outPath)
|
||||
{
|
||||
Assert.Throws<ArgumentException>(() =>
|
||||
HdrToneMapper.Build10BitPassthroughArgs(inPath!, outPath!));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HdrToneMapper_Build10BitPassthroughArgs_PathsWithQuotes_Stripped()
|
||||
{
|
||||
string malIn = "C:\\Media\\in\"put.mp4";
|
||||
string malOut = "C:\\Media\\out\"put.mp4";
|
||||
|
||||
var args = HdrToneMapper.Build10BitPassthroughArgs(malIn, malOut);
|
||||
Assert.NotNull(args);
|
||||
Assert.DoesNotContain(args, a => a.Contains('"'));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HdrToneMapper_ConvertToSdrAsync_SameInputAndOutput_ThrowsArgumentException()
|
||||
{
|
||||
var mapper = new HdrToneMapper(DesktopProcessRunner.Default);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
mapper.ConvertToSdrAsync("ffmpeg.exe", SampleInput, SampleInput, ToneMapAlgorithm.Mobius, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(-100)]
|
||||
public void HdrToneMapper_BuildToneMapFilter_NonPositiveNits_ClampsToSafeMinimum(int nits)
|
||||
{
|
||||
var filter = HdrToneMapper.BuildToneMapFilter(ToneMapAlgorithm.Mobius, nits);
|
||||
Assert.NotNull(filter);
|
||||
Assert.DoesNotContain("npl=0", filter);
|
||||
Assert.DoesNotContain("npl=-", filter);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. VideoDenoiseUpscaler
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public async Task VideoDenoiseUpscaler_EnhanceVideoAsync_SameInputAndOutput_ThrowsArgumentException()
|
||||
{
|
||||
var upscaler = new VideoDenoiseUpscaler(DesktopProcessRunner.Default);
|
||||
var spec = new DenoiseUpscaleSpec(DenoiseStrength.Light, 1920, 1080);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
upscaler.EnhanceVideoAsync("ffmpeg.exe", SampleInput, SampleInput, spec, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task VideoDenoiseUpscaler_EnhanceVideoAsync_NullSpec_ThrowsArgumentNullException()
|
||||
{
|
||||
var upscaler = new VideoDenoiseUpscaler(DesktopProcessRunner.Default);
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||
upscaler.EnhanceVideoAsync("ffmpeg.exe", SampleInput, SampleOutput, null!, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, SampleOutput)]
|
||||
[InlineData(SampleInput, null)]
|
||||
[InlineData("", SampleOutput)]
|
||||
[InlineData(SampleInput, " ")]
|
||||
public async Task VideoDenoiseUpscaler_EnhanceVideoAsync_NullOrWhitespacePaths_ThrowsArgumentException(string? inPath, string? outPath)
|
||||
{
|
||||
var upscaler = new VideoDenoiseUpscaler(DesktopProcessRunner.Default);
|
||||
var spec = new DenoiseUpscaleSpec(DenoiseStrength.Light, 1920, 1080);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
upscaler.EnhanceVideoAsync("ffmpeg.exe", inPath!, outPath!, spec, CancellationToken.None));
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. VideoDeduplicator
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public void VideoDeduplicator_Constructor_NegativeThreshold_ClampsOrThrows()
|
||||
{
|
||||
var dedup = new VideoDeduplicator(-5);
|
||||
// Verify it doesn't fail to match identical hashes due to negative distance threshold
|
||||
dedup.RegisterVideo("test.mp4", 0x12345678UL);
|
||||
var match = dedup.FindDuplicate(0x12345678UL);
|
||||
Assert.NotNull(match);
|
||||
Assert.Equal(0, match.HammingDistance);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VideoDeduplicator_RegisterVideo_PathWithQuotes_SanitizesKey()
|
||||
{
|
||||
var dedup = new VideoDeduplicator(5);
|
||||
dedup.RegisterVideo("C:\\Media\\vi\"deo.mp4", 0x12345678UL);
|
||||
var match = dedup.FindDuplicate(0x12345678UL);
|
||||
Assert.NotNull(match);
|
||||
Assert.DoesNotContain("\"", match.FilePath);
|
||||
}
|
||||
}
|
||||
110
tests/Paca.Tests/Platform/PlatformMediaToolsExtremeRedTests.cs
Normal file
110
tests/Paca.Tests/Platform/PlatformMediaToolsExtremeRedTests.cs
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Core.Platform;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Platform;
|
||||
|
||||
public class PlatformMediaToolsExtremeRedTests
|
||||
{
|
||||
// ==========================================
|
||||
// 1. ChapterSplitter
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public void ChapterSplitter_GenerateFfmetadata_NullChapters_ReturnsValidHeaderWithoutCrashing()
|
||||
{
|
||||
var meta = ChapterSplitter.GenerateFfmetadata(null!);
|
||||
Assert.NotNull(meta);
|
||||
Assert.Contains(";FFMETADATA1", meta);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChapterSplitter_GenerateFfmetadata_ListContainingNullChapter_SkipsNullChapter()
|
||||
{
|
||||
var chapters = new List<ChapterInfo>
|
||||
{
|
||||
null!,
|
||||
new(1, "Intro", 0.0, 30.0),
|
||||
null!
|
||||
};
|
||||
|
||||
var meta = ChapterSplitter.GenerateFfmetadata(chapters);
|
||||
Assert.NotNull(meta);
|
||||
Assert.Contains("title=Intro", meta);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ChapterSplitter_GetChapterFileName_NullTitle_ReturnsSafeDefault()
|
||||
{
|
||||
var name = ChapterSplitter.GetChapterFileName(1, null!, "mp4");
|
||||
Assert.NotNull(name);
|
||||
Assert.StartsWith("01_", name);
|
||||
Assert.EndsWith(".mp4", name);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. LosslessSmartTrimmer
|
||||
// ==========================================
|
||||
|
||||
[Theory]
|
||||
[InlineData(double.NaN)]
|
||||
[InlineData(double.NegativeInfinity)]
|
||||
[InlineData(double.PositiveInfinity)]
|
||||
public void LosslessSmartTrimmer_FindNearestKeyframe_InvalidTargetSec_ReturnsDefault(double target)
|
||||
{
|
||||
var keyframes = new[] { 0.0, 5.0, 10.0, 15.0 };
|
||||
var nearest = LosslessSmartTrimmer.FindNearestKeyframe(keyframes, target);
|
||||
Assert.False(double.IsNaN(nearest));
|
||||
Assert.False(double.IsInfinity(nearest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LosslessSmartTrimmer_FindNearestKeyframe_KeyframesWithNaNOrNegatives_FiltersCorrectly()
|
||||
{
|
||||
var keyframes = new[] { double.NaN, -10.0, 5.0, 10.0 };
|
||||
var nearest = LosslessSmartTrimmer.FindNearestKeyframe(keyframes, 6.0);
|
||||
Assert.Equal(5.0, nearest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task LosslessSmartTrimmer_TrimLosslessAsync_NaNTimestamps_ThrowsArgumentException()
|
||||
{
|
||||
var trimmer = new LosslessSmartTrimmer(DesktopProcessRunner.Default);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
trimmer.TrimLosslessAsync("ffmpeg.exe", "in.mp4", "out.mp4", double.NaN, 10.0, CancellationToken.None));
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. VideoWatermarkBurner
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public void VideoWatermarkBurner_BuildDrawTextFilter_NullText_DoesNotThrow()
|
||||
{
|
||||
var filter = VideoWatermarkBurner.BuildDrawTextFilter(null!);
|
||||
Assert.NotNull(filter);
|
||||
Assert.Contains("drawtext=", filter);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void VideoWatermarkBurner_BuildDrawTextFilter_TextWithNewlines_EscapedOrStripped()
|
||||
{
|
||||
var filter = VideoWatermarkBurner.BuildDrawTextFilter("Line 1\r\nLine 2\nLine 3");
|
||||
Assert.NotNull(filter);
|
||||
Assert.DoesNotContain("\r", filter);
|
||||
Assert.DoesNotContain("\n", filter);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-0.1)]
|
||||
[InlineData(1.1)]
|
||||
[InlineData(double.NaN)]
|
||||
public void VideoWatermarkBurner_BuildOverlayFilter_InvalidOpacity_ThrowsArgumentOutOfRangeException(double opacity)
|
||||
{
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
VideoWatermarkBurner.BuildOverlayFilter(WatermarkPosition.Center, opacity: opacity));
|
||||
}
|
||||
}
|
||||
119
tests/Paca.Tests/Platform/VrAndAudioAdaptiveExtremeRedTests.cs
Normal file
119
tests/Paca.Tests/Platform/VrAndAudioAdaptiveExtremeRedTests.cs
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Core.Platform;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Platform;
|
||||
|
||||
public class VrAndAudioAdaptiveExtremeRedTests
|
||||
{
|
||||
private const string SampleInput = "C:\\Media\\input.mp4";
|
||||
private const string SampleOutput = "C:\\Media\\output.mp4";
|
||||
|
||||
// ==========================================
|
||||
// 1. VrSpatialAudioEngine
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public async Task VrSpatialAudioEngine_InjectVrMetadataAsync_SameInputAndOutput_ThrowsArgumentException()
|
||||
{
|
||||
var engine = new VrSpatialAudioEngine(DesktopProcessRunner.Default);
|
||||
var meta = new VrSpatialMetadata("equirectangular", "mono", 1);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
engine.InjectVrMetadataAsync("ffmpeg.exe", SampleInput, SampleInput, meta, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, SampleOutput)]
|
||||
[InlineData(SampleInput, null)]
|
||||
[InlineData("", SampleOutput)]
|
||||
[InlineData(SampleInput, " ")]
|
||||
public async Task VrSpatialAudioEngine_InjectVrMetadataAsync_NullOrWhitespacePaths_ThrowsArgumentException(string? inPath, string? outPath)
|
||||
{
|
||||
var engine = new VrSpatialAudioEngine(DesktopProcessRunner.Default);
|
||||
var meta = new VrSpatialMetadata("equirectangular", "mono", 1);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
engine.InjectVrMetadataAsync("ffmpeg.exe", inPath!, outPath!, meta, CancellationToken.None));
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 2. VoiceActivitySubtitleAdapter
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public void VoiceActivitySubtitleAdapter_GenerateVttSkeleton_ListWithNullSegment_SkipsSafely()
|
||||
{
|
||||
var list = new List<SpeechSegment>
|
||||
{
|
||||
null!,
|
||||
new(0.0, 5.0),
|
||||
null!
|
||||
};
|
||||
|
||||
var vtt = VoiceActivitySubtitleAdapter.GenerateVttSkeleton(list);
|
||||
Assert.NotNull(vtt);
|
||||
Assert.Contains("WEBVTT", vtt);
|
||||
Assert.Contains("00:00:00.000 --> 00:00:05.000", vtt);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(double.NaN)]
|
||||
[InlineData(-5.0)]
|
||||
public void VoiceActivitySubtitleAdapter_FormatTimecode_NegativeOrNaN_ReturnsZeroTimecode(double sec)
|
||||
{
|
||||
var code = VoiceActivitySubtitleAdapter.FormatTimecode(sec);
|
||||
Assert.Equal("00:00:00.000", code);
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 3. HighlightShortsClipper
|
||||
// ==========================================
|
||||
|
||||
[Fact]
|
||||
public async Task HighlightShortsClipper_ExportShortsClipAsync_SameInputAndOutput_ThrowsArgumentException()
|
||||
{
|
||||
var clipper = new HighlightShortsClipper(DesktopProcessRunner.Default);
|
||||
var clip = new HighlightClip(0.0, 15.0, "Test", 7.5);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
clipper.ExportShortsClipAsync("ffmpeg.exe", SampleInput, SampleInput, clip, false, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task HighlightShortsClipper_ExportShortsClipAsync_NullClip_ThrowsArgumentNullException()
|
||||
{
|
||||
var clipper = new HighlightShortsClipper(DesktopProcessRunner.Default);
|
||||
await Assert.ThrowsAsync<ArgumentNullException>(() =>
|
||||
clipper.ExportShortsClipAsync("ffmpeg.exe", SampleInput, SampleOutput, null!, false, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(null, SampleOutput)]
|
||||
[InlineData(SampleInput, null)]
|
||||
[InlineData("", SampleOutput)]
|
||||
[InlineData(SampleInput, " ")]
|
||||
public async Task HighlightShortsClipper_ExportShortsClipAsync_NullOrWhitespacePaths_ThrowsArgumentException(string? inPath, string? outPath)
|
||||
{
|
||||
var clipper = new HighlightShortsClipper(DesktopProcessRunner.Default);
|
||||
var clip = new HighlightClip(0.0, 15.0, "Test", 7.5);
|
||||
await Assert.ThrowsAsync<ArgumentException>(() =>
|
||||
clipper.ExportShortsClipAsync("ffmpeg.exe", inPath!, outPath!, clip, false, CancellationToken.None));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HighlightShortsClipper_DetectHighlightWindows_ListWithNullSamples_SkipsSafely()
|
||||
{
|
||||
var samples = new List<VolumeSample>
|
||||
{
|
||||
null!,
|
||||
new(10.0, -5.0),
|
||||
null!,
|
||||
new(30.0, -2.0)
|
||||
};
|
||||
|
||||
var clips = HighlightShortsClipper.DetectHighlightWindows(samples, windowDurationSec: 10.0, topCount: 2);
|
||||
Assert.NotNull(clips);
|
||||
Assert.Equal(2, clips.Count);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue