using VideoDownloader.Core.Platform; using VideoDownloader.Server; using VideoDownloader.Tests.Platform; namespace VideoDownloader.Tests.Server; /// M2 라이브러리 썸네일/해상도 — ffmpeg 서브프로세스를 Fake 러너로 검증(사이드카 캐시). public class ThumbnailerTests { private string _dir = ""; private string NewMedia() { _dir = Path.Combine(Path.GetTempPath(), $"vd_thumb_{Guid.NewGuid():N}"); Directory.CreateDirectory(_dir); var p = Path.Combine(_dir, "clip.mp4"); File.WriteAllBytes(p, new byte[100]); return p; } private static Task FakeFfmpegCreatesJpg(ProcessSpec spec, Action? _, CancellationToken __) { // 출력 경로(마지막 인자)에 가짜 JPEG 생성 var outPath = spec.Args[^1]; File.WriteAllBytes(outPath, new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 }); // JPEG SOI/APP0 return Task.FromResult(new ProcessResult(0, "", "")); } private static Task FakeFfmpegReportsResolution(ProcessSpec spec, Action? onLine, CancellationToken _) { var line = " Stream #0:0[0x1](und): Video: h264 (High), yuv420p, 1920x1080 [SAR 1:1 DAR 16:9], 5000 kb/s"; onLine?.Invoke("err", line); return Task.FromResult(new ProcessResult(0, "", line)); } [Fact] public async Task 썸네일_생성후_사이드카_캐시로_재실행_없음() { var media = NewMedia(); var runner = new FakeProcessRunner { Behavior = FakeFfmpegCreatesJpg }; var t = new Thumbnailer(runner); var bytes1 = await t.GetThumbnailAsync("ffmpeg", media); Assert.NotNull(bytes1); Assert.True(bytes1!.Length == 4); runner.Behavior = (_, _, _) => throw new Xunit.Sdk.XunitException("캐시 히트 시 ffmpeg 재실행 없음"); var bytes2 = await t.GetThumbnailAsync("ffmpeg", media); // 캐시 Assert.Equal(bytes1, bytes2); Directory.Delete(_dir, true); } [Fact] public async Task ffmpeg_실패_시_null() { var media = NewMedia(); var runner = new FakeProcessRunner { Behavior = (_, _, _) => Task.FromResult(new ProcessResult(1, "", "boom")) }; Assert.Null(await new Thumbnailer(runner).GetThumbnailAsync("ffmpeg", media)); Directory.Delete(_dir, true); } [Fact] public async Task 해상도_추출후_사이드카_캐시() { var media = NewMedia(); var runner = new FakeProcessRunner { Behavior = FakeFfmpegReportsResolution }; var t = new Thumbnailer(runner); Assert.Equal("1920x1080", await t.GetResolutionAsync("ffmpeg", media)); runner.Behavior = (_, _, _) => throw new Xunit.Sdk.XunitException("캐시 히트 시 재실행 없음"); Assert.Equal("1920x1080", await t.GetResolutionAsync("ffmpeg", media)); Directory.Delete(_dir, true); } [Fact] public async Task 해상기_추출_불가_시_null() { var media = NewMedia(); var runner = new FakeProcessRunner { Behavior = (_, _, _) => Task.FromResult(new ProcessResult(0, "", "no streams")) }; Assert.Null(await new Thumbnailer(runner).GetResolutionAsync("ffmpeg", media)); Directory.Delete(_dir, true); } }