using System.Net; using System.Net.Http; using VideoDownloader.Core.Config; using VideoDownloader.Core.Hls; using VideoDownloader.Core.Models; using VideoDownloader.Core.Platform; namespace VideoDownloader.Tests.Platform; /// HlsDownloader 가 주입된 IMediaMuxer 를 실제 리먹스 경로에서 사용하는지 검증(RED: ctor 미할당 회귀 방지). public class HlsMuxerWiringTests { private sealed class RecordingMuxer : IMediaMuxer { public int RemuxCalls; public Task RemuxAsync(string ffmpeg, string input, string output, CancellationToken ct) { RemuxCalls++; File.WriteAllText(output, "remuxed"); // 성공 시뮬레이션 return Task.CompletedTask; } public Task DownloadDashAsync(string ffmpeg, Uri mpdUrl, string output, double durationSec, HttpClient http, IProgress? progress, CancellationToken ct) => Task.CompletedTask; public string? ResolveFfmpeg(AppConfig config) => "ffmpeg"; } [Fact] public async Task HlsDownloader_주입된_뮥서로_리먹스한다() { if (!OperatingSystem.IsWindows()) return; using var server = new HttpListener(); var port = Random.Shared.Next(20000, 60000); server.Prefixes.Add($"http://127.0.0.1:{port}/"); server.Start(); var seg = new byte[16]; using var listenerTask = Task.Run(async () => { for (var i = 0; i < 3; i++) { var ctx = await server.GetContextAsync(); var path = ctx.Request.Url!.AbsolutePath; var body = path.EndsWith(".m3u8") ? "#EXTM3U\n#EXT-X-TARGETDURATION:2\n#EXTINF:2.0,\nseg0.ts\n#EXTINF:2.0,\nseg1.ts\n#EXT-X-ENDLIST\n" : null; var buf = body != null ? System.Text.Encoding.UTF8.GetBytes(body) : seg; ctx.Response.ContentType = body != null ? "application/vnd.apple.mpegurl" : "video/mp2t"; await ctx.Response.OutputStream.WriteAsync(buf); ctx.Response.Close(); } }); var muxer = new RecordingMuxer(); var config = new AppConfig(); var dl = new HlsDownloader(new HttpClient(), config, muxer); var outBase = Path.Combine(Path.GetTempPath(), $"hls-{Guid.NewGuid():N}"); try { var result = await dl.DownloadAsync(new Uri($"http://127.0.0.1:{port}/index.m3u8"), outBase); Assert.True(muxer.RemuxCalls > 0, "주입한 IMediaMuxer.RemuxAsync 가 호출되어야 한다"); Assert.EndsWith(".mp4", result); } finally { server.Stop(); try { await listenerTask; } catch { } foreach (var f in Directory.GetFiles(Path.GetTempPath(), Path.GetFileName(outBase) + "*")) File.Delete(f); if (Directory.Exists(outBase + ".parts")) Directory.Delete(outBase + ".parts", true); } } }