feat(ads/seo/release): v1.2.0 with live Google Ads, isolated ad profile, ads.txt, and SEO/AEO/GEO optimization
This commit is contained in:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
180
tests/Paca.Tests/Browser/DeepUserScenarioTests.cs
Normal file
180
tests/Paca.Tests/Browser/DeepUserScenarioTests.cs
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
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;
|
||||
|
||||
/// <summary>
|
||||
/// 21차: 극한 실사용자 프로세스 시뮬레이션 4대 심층 E2E 파이프라인.
|
||||
/// </summary>
|
||||
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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue