using System.Net.Http; using System.Net.Http.Headers; using System.Net.Http.Json; using VideoDownloader.Server; namespace VideoDownloader.Tests.Server; /// /// E2E (M0): 실제 Kestrel 서버 기동 → 인증/페어링/큐 제어/라이브러리/스트리밍/웹 컴패니언 검증. /// public class RemoteServerE2ETests : IDisposable { private readonly string _libDir = Path.Combine(Path.GetTempPath(), $"vd_srv_{Guid.NewGuid():N}"); private sealed class FakeQueue : IQueueAdapter { public List Items { get; } = new(); public List Actions { get; } = new(); public Task> GetItemsAsync() => Task.FromResult>(Items); public Task AddAsync(string url, string? title) { var item = new QueueItemDto(Guid.NewGuid().ToString("N"), title ?? url, url, "대기", 0); Items.Add(item); Changed?.Invoke(); return Task.FromResult(item.Id); } public Task PauseAsync(string id) { Actions.Add($"pause:{id}"); return Task.CompletedTask; } public Task ResumeAsync(string id) { Actions.Add($"resume:{id}"); return Task.CompletedTask; } public Task CancelAsync(string id) { Actions.Add($"cancel:{id}"); return Task.CompletedTask; } public event Action? Changed; } [Fact] public async Task 서버_전_API_E2E_인증_큐_라이브러리_스트리밍_웹컴패니언() { Directory.CreateDirectory(_libDir); var mediaPath = Path.Combine(_libDir, "sample.mp4"); File.WriteAllBytes(mediaPath, new byte[256 * 1024]); // 256KB 가짜 미디어 var queue = new FakeQueue(); await using var server = await ServerHost.StartAsync(new ServerOptions { Port = 0, // 사용 가능한 포트 자동 할당 Token = "test-token-123", LibraryFolder = _libDir, Queue = queue, }); using var http = new HttpClient { BaseAddress = new Uri($"http://127.0.0.1:{server.Port}") }; // 1) 인증 없으면 401 var anon = await http.GetAsync("/api/queue"); Assert.Equal(System.Net.HttpStatusCode.Unauthorized, anon.StatusCode); // 2) 틀린 토큰도 401 using var bad = new HttpRequestMessage(HttpMethod.Get, "/api/queue"); bad.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "wrong"); Assert.Equal(System.Net.HttpStatusCode.Unauthorized, (await http.SendAsync(bad)).StatusCode); // 3) 페어링(올바른 토큰) var pair = await http.PostAsJsonAsync("/api/pair", new { token = "test-token-123", device = "E2E-Phone" }); Assert.True(pair.IsSuccessStatusCode); // 4) 큐: URL 추가 → 목록 조회 http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "test-token-123"); var add = await http.PostAsJsonAsync("/api/queue", new { url = "https://x.test/v.mp4", title = "E2E 영상" }); Assert.True(add.IsSuccessStatusCode); var items = await http.GetFromJsonAsync>("/api/queue"); Assert.NotNull(items); Assert.Single(items!); Assert.Equal("E2E 영상", items![0].Title); var id = items[0].Id; // 5) 큐 제어 위임 Assert.True((await http.PostAsync($"/api/queue/{id}/pause", null)).IsSuccessStatusCode); Assert.True((await http.PostAsync($"/api/queue/{id}/resume", null)).IsSuccessStatusCode); Assert.True((await http.PostAsync($"/api/queue/{id}/cancel", null)).IsSuccessStatusCode); Assert.Equal(new[] { $"pause:{id}", $"resume:{id}", $"cancel:{id}" }, queue.Actions); // 6) 라이브러리 var lib = await http.GetFromJsonAsync>("/api/library"); Assert.NotNull(lib); Assert.Single(lib!); Assert.Equal("sample.mp4", lib![0].FileName); // 7) 스트리밍: Range 요청 → 206 부분 응답 using var range = new HttpRequestMessage(HttpMethod.Get, $"/stream/{lib[0].Id}"); range.Headers.Range = new System.Net.Http.Headers.RangeHeaderValue(0, 1023); range.Headers.Authorization = new AuthenticationHeaderValue("Bearer", "test-token-123"); var partial = await http.SendAsync(range); Assert.Equal(System.Net.HttpStatusCode.PartialContent, partial.StatusCode); var bytes = await partial.Content.ReadAsByteArrayAsync(); Assert.Equal(1024, bytes.Length); // 8) 파일 다운로드: 전체 길이 var file = await http.GetByteArrayAsync($"/file/{lib[0].Id}"); Assert.Equal(256 * 1024, file.Length); // 9) 웹 컴패니언 페이지 제공 var page = await http.GetStringAsync("/"); Assert.Contains("VideoDownloader", page); Assert.Contains("api/queue", page); // 페이지가 API 를 호출하는 스크립트 포함 } public void Dispose() { try { Directory.Delete(_libDir, true); } catch { } } }