using System.IO; using System.Net; using Paca.App.Services; using Paca.App.ViewModels; using Paca.Browser.Tabs; using Paca.Core.Detection; using Paca.Core.Util; using Paca.Core.Utils; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.Browser; /// /// 21차: 극한 실사용자 프로세스 시뮬레이션 4대 심층 E2E 파이프라인. /// public class DeepUserScenarioTests { [Fact] public void 극한시나리오1_멀티탭_복합미디어_탐색_동시슬롯_배치제어_풀라이프사이클() { using var pipeline = AppScenarioPipeline.Create(); // 1. 사용자가 4개의 탭을 열어 서로 다른 사이트 탐색 pipeline.OpenTab("https://stream1.test/live.m3u8") .OpenTab("https://dash.test/manifest.mpd") .OpenTab("https://direct.test/movie.mp4") .OpenTab("https://youtube.com/watch?v=abcdef12345"); Xunit.Assert.Equal(4, pipeline.TabManager.Tabs.Count); // 2. 4개의 미디어가 감지되어 대기열에 순차 진입 pipeline.SimulateMediaDetection("https://stream1.test/live.m3u8", MediaKind.Hls) .SimulateMediaDetection("https://dash.test/manifest.mpd", MediaKind.Dash) .SimulateMediaDetection("https://direct.test/movie.mp4", MediaKind.Direct) .SimulateMediaDetection("https://youtube.com/watch?v=abcdef12345", MediaKind.Social); pipeline.AssertQueueCount(4); pipeline.AssertDetectedCount(4); // 3. 사용자가 다중 선택(Ctrl+A) 후 일괄 일시정지 실행 pipeline.SelectItems(_ => true) .BatchPause(); foreach (var item in pipeline.DownloadManager.Items) { Xunit.Assert.True(item.PausedForResume || item.Status == "일시정지" || item.Status == "대기"); } // 4. 사용자가 특정 2개만 선택하여 일괄 재개 pipeline.SelectItems(i => i.Kind == MediaKind.Hls || i.Kind == MediaKind.Direct) .BatchResume(); var resumedItems = pipeline.DownloadManager.Items.Where(i => i.IsSelected).ToList(); Xunit.Assert.Equal(2, resumedItems.Count); // 5. 사용자가 나머지 항목 일괄 삭제 실행 pipeline.SelectItems(i => !i.IsSelected) .BatchDelete(); pipeline.AssertQueueCount(2); } [Fact] public void 극한시나리오2_네트워크단절_및_안티봇_지수백오프_복구_파이프라인() { using var pipeline = AppScenarioPipeline.Create(); // 다운로드 항목 생성 및 실패 시뮬레이션 var item = new DownloadItemViewModel("Resilient Stream") { MediaUrl = new Uri("https://stream.test/index.m3u8"), Kind = MediaKind.Hls, Status = "다운로드 중" }; pipeline.DownloadManager.Items.Add(item); // 1. 네트워크 타임아웃 오류 발생 item.Status = "실패"; item.Error = "HTTP 503 Service Unavailable"; item.RetryCount = 1; Xunit.Assert.Equal("실패", item.Status); Xunit.Assert.Equal(1, item.RetryCount); // 2. 백오프 후 자동 재시도 복구 시뮬레이션 item.Status = "대기"; item.Error = null; item.RetryCount++; Xunit.Assert.Equal("대기", item.Status); Xunit.Assert.Null(item.Error); Xunit.Assert.Equal(2, item.RetryCount); // 3. YouTube 안티봇 n-challenge 토큰 갱신 플래그 검증 pipeline.Config.AutoUpdateYtDlp = true; pipeline.Config.AutoInstallDeno = true; Xunit.Assert.True(pipeline.Config.AutoUpdateYtDlp); Xunit.Assert.True(pipeline.Config.AutoInstallDeno); } [Fact] public void 극한시나리오3_멀티작업공간_다중탭_크래시복원_및_재생목록_지속성_파이프라인() { using var pipeline = AppScenarioPipeline.Create(); // 1. 사용자가 여러 탭을 설정 (핀 고정, 음소거, 잠금) var tab1 = pipeline.TabManager.NewTab("https://work.test/docs"); tab1.IsPinned = true; tab1.Title = "업무 문서"; var tab2 = pipeline.TabManager.NewTab("https://music.test/audio"); tab2.IsAudioPlaying = true; tab2.IsMuted = true; tab2.Title = "배경 음악"; var tab3 = pipeline.TabManager.NewTab("https://study.test/video"); tab3.IsLocked = true; tab3.Title = "학습 강의"; // 2. 재생목록에 트랙 추가 pipeline.AddPlaylistTrack("https://music.test/stream.m3u8", "배경 음악 트랙", "HLS") .AddPlaylistTrack("https://study.test/lecture.mp4", "학습 강의 영상", "MP4"); pipeline.AssertPlaylistCount(2); // 3. 세션 스냅샷 캡처 (비정상 종료 또는 앱 재부팅 시뮬레이션) var snapshot = pipeline.SaveSession(); Xunit.Assert.NotNull(snapshot); Xunit.Assert.True(snapshot.Urls.Count >= 3); // 4. 새로운 세션에서 콜드 복원 var newTabManager = new TabManager(() => new TabItem(), _ => { }); newTabManager.RestoreSession(snapshot); Xunit.Assert.Equal(snapshot.Urls.Count, newTabManager.Tabs.Count); var restoredPinned = newTabManager.Tabs.FirstOrDefault(t => t.Url == "https://work.test/docs"); Xunit.Assert.NotNull(restoredPinned); Xunit.Assert.True(restoredPinned.IsPinned); Xunit.Assert.Equal("https://work.test/docs", restoredPinned.Url); } [Fact] public void 극한시나리오4_스케줄러_게이트_템플릿명명_모바일서버_스트리밍브리지_파이프라인() { using var pipeline = AppScenarioPipeline.Create(); // 1. 야간 스케줄러 및 파일명 템플릿 구성 var scheduler = new DownloadScheduler("01:00-06:00"); bool gateOpen = false; pipeline.SetScheduleGate(() => gateOpen); // 낮 14:00 시점: 게이트 닫힘 var daytime = new DateTime(2026, 8, 29, 14, 0, 0); Xunit.Assert.False(scheduler.IsOpen(daytime)); // 2. 야간 02:30 시점: 게이트 열림 var nighttime = new DateTime(2026, 8, 29, 2, 30, 0); Xunit.Assert.True(scheduler.IsOpen(nighttime)); gateOpen = scheduler.IsOpen(nighttime); Xunit.Assert.True(gateOpen); // 3. 파일명 템플릿 변환 검증 var template = "VideoDownloader_{Title}_{Resolution}_{Date}"; var formattedName = FileNameUtil.ApplyTemplate(template, "Nightly Movie", "1080p", "20260829"); Xunit.Assert.Equal("VideoDownloader_Nightly Movie_1080p_20260829", formattedName); // 4. 디스크 파일 생성 및 대역폭 리미터 스트림 검증 var filePath = Path.Combine(pipeline.Config.OutputFolder, formattedName + ".mp4"); File.WriteAllBytes(filePath, new byte[1024 * 50]); // 50 KB Xunit.Assert.True(File.Exists(filePath)); using var fileStream = File.OpenRead(filePath); using var throttledStream = new ThrottledStream(fileStream, 100 * 1024); // 100 KB/s Xunit.Assert.True(throttledStream.CanRead); Xunit.Assert.Equal(50 * 1024, throttledStream.Length); } }