using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading.Tasks; using System.Windows; using Paca.App; using Paca.App.Browser; using Paca.Browser.Tabs; using Paca.Mobile; using Paca.Mobile.Core; using Paca.Server; using Paca.Tests.Infrastructure; using Xunit; namespace Paca.Tests.Browser; /// /// 초고강도 사생활 보호(InPrivate / Incognito) 무결성 게이트: /// 1. 영상 재생목록 디스크 무누수 (playlist.json 차단) /// 2. 사이트 줌 배율 디스크 무누수 (sitezoom.json 차단) /// 3. 보조 창(Glance, Player) InPrivate 격리 옵션 승계 /// 4. 원격 브라우저 탭 동기화(/api/tabs) 비공개 탭 100% 필터링 /// 5. 확장 프로그램 콘텐츠 스크립트 InPrivate 탭 주입 차단 /// 6. 모바일 시크릿 탭 활성 시 PC 전송 액션 방어 가드 /// [Collection("E2E")] public class PrivateBrowsingExhaustiveGateTests { [Fact] public async Task Gate1_InPrivate_영상_재생_시_playlist_json_디스크_누출_원천_차단() { var dataDir = Path.Combine(Path.GetTempPath(), $"paca_priv_playlist_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); await sta.InvokeAsync(async () => { var window = new MainWindow(); window.Show(); // InPrivate 탭 생성 var privateTab = window.Tabs.NewTab("https://classified-stream.tv/watch?v=999", activate: true, isPrivate: true); // 미디어 감지 시뮬레이션: OnResponseReceived 혹은 AddToPlaylist 호출 // privateTab이 활성/InPrivate인 상태에서 AddToPlaylist 호출 var privMediaUrl = "https://classified-stream.tv/media.mp4"; window.SimulatePrivateMediaDetection(privMediaUrl, "비밀 동영상", isPrivate: true); var playlistPath = Path.Combine(dataDir, "playlist.json"); if (File.Exists(playlistPath)) { var content = File.ReadAllText(playlistPath); Assert.DoesNotContain("classified-stream.tv", content); Assert.DoesNotContain("비밀 동영상", content); } // 반면 일반 탭의 미디어는 정상적으로 playlist.json에 저장되어야 함 var normalTab = window.Tabs.NewTab("https://public-site.org/home", activate: true, isPrivate: false); window.SimulatePrivateMediaDetection("https://public-video.org/public-stream.mp4", "Public Video", isPrivate: false); Assert.True(File.Exists(playlistPath), "일반 미디어는 playlist.json에 저장되어야 합니다."); var normalContent = File.ReadAllText(playlistPath); Assert.Contains("public-video.org", normalContent); Assert.Contains("Public Video", normalContent); window.Close(); await Task.CompletedTask; }); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } } [Fact] public async Task Gate2_InPrivate_줌_배율_변경_시_sitezoom_json_디스크_누출_원천_차단() { var dataDir = Path.Combine(Path.GetTempPath(), $"paca_priv_zoom_{Guid.NewGuid():N}"); Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir); try { using var sta = StaDispatcher.Start(); WpfTestApp.Ensure(sta.Dispatcher); await sta.InvokeAsync(async () => { var window = new MainWindow(); window.Show(); var secretUrl = "https://confidential-portal.internal"; var privateTab = window.Tabs.NewTab(secretUrl, activate: true, isPrivate: true); // InPrivate 탭에서 줌 레벨 변경 실행 window.ApplyZoomForTest(secretUrl, 1.5, isPrivate: true); var sitezoomPath = Path.Combine(dataDir, "sitezoom.json"); if (File.Exists(sitezoomPath)) { var content = File.ReadAllText(sitezoomPath); Assert.DoesNotContain("confidential-portal.internal", content); } // 일반 탭에서 줌 레벨 변경 실행 var publicUrl = "https://public-portal.org"; var normalTab = window.Tabs.NewTab(publicUrl, activate: true, isPrivate: false); window.ApplyZoomForTest(publicUrl, 1.25, isPrivate: false); Assert.True(File.Exists(sitezoomPath), "일반 탭 줌 설정은 sitezoom.json에 기록되어야 합니다."); var publicContent = File.ReadAllText(sitezoomPath); Assert.Contains("public-portal.org", publicContent); window.Close(); await Task.CompletedTask; }); } finally { try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { } } } [Fact] public void Gate3_GlanceWindow_및_PlayerWindow_InPrivate_생성자_플래그_지원_검증() { // GlanceWindow 및 PlayerWindow가 isPrivate 생성자 매개변수를 지원하는지 리플렉션/컴파일 레벨 검증 var glanceCtor = typeof(GlanceWindow).GetConstructors() .FirstOrDefault(c => c.GetParameters().Any(p => p.Name == "isPrivate")); Assert.NotNull(glanceCtor); var playerCtor = typeof(PlayerWindow).GetConstructors() .FirstOrDefault(c => c.GetParameters().Any(p => p.Name == "isPrivate")); Assert.NotNull(playerCtor); } [Fact] public async Task Gate4_ServerHost_api_tabs_동기화_시_InPrivate_탭_100퍼센트_배제() { var tabs = new List { new("tab-1", "공개 위키피디아", "https://wikipedia.org", IsActive: false, IsPrivate: false), new("tab-2", "비밀 은행 계좌", "https://secret-bank.com", IsActive: true, IsPrivate: true), new("tab-3", "회사 인트라넷 문서", "https://internal-docs.corp", IsActive: false, IsPrivate: true), new("tab-4", "공개 유튜브 음악", "https://youtube.com", IsActive: false, IsPrivate: false) }; var server = await ServerHost.StartAsync(new ServerOptions { Port = 0, Token = "test-token", TabProvider = () => tabs, EnableMdns = false, EnableLanBeacon = false }); try { using var client = new System.Net.Http.HttpClient(); client.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", "test-token"); var res = await client.GetAsync($"http://127.0.0.1:{server.Port}/api/tabs"); Assert.True(res.IsSuccessStatusCode); var json = await res.Content.ReadAsStringAsync(); // 공개 탭은 노출되어야 함 Assert.Contains("wikipedia.org", json); Assert.Contains("youtube.com", json); // InPrivate 탭은 100% 철저히 배제되어야 함 Assert.DoesNotContain("secret-bank.com", json); Assert.DoesNotContain("internal-docs.corp", json); Assert.DoesNotContain("비밀 은행 계좌", json); } finally { await server.DisposeAsync(); } } [Fact] public void Gate5_Extension_콘텐츠스크립트_InPrivate_탭_인젝션_차단_계약_검증() { var tabManager = new TabManager(() => new TabItem()); var privTab = tabManager.NewTab("https://secret.com", isPrivate: true); var normTab = tabManager.NewTab("https://public.com", isPrivate: false); Assert.True(privTab.IsPrivate); Assert.False(normTab.IsPrivate); // MainWindow.CanInjectExtensionToTab 메서드 또는 격리 검증 Assert.False(MainWindow.CanInjectExtensionToTab(privTab, isPrivateWindow: false)); Assert.False(MainWindow.CanInjectExtensionToTab(normTab, isPrivateWindow: true)); Assert.True(MainWindow.CanInjectExtensionToTab(normTab, isPrivateWindow: false)); } [Fact] public void Gate6_Mobile_시크릿탭_PC_동기화_가드_검증() { var privTab = new BrowserTab { Id = "m-priv-1", Title = "시크릿 웹페이지", Url = "https://secret-vault.org", IsPrivate = true }; var normTab = new BrowserTab { Id = "m-norm-1", Title = "공개 웹페이지", Url = "https://public-news.org", IsPrivate = false }; // 시크릿 탭은 PC 전송 대상에서 차단되어야 함 Assert.False(MobileSyncSecurityGuard.CanSendTabToPc(privTab)); Assert.True(MobileSyncSecurityGuard.CanSendTabToPc(normTab)); Assert.False(MobileSyncSecurityGuard.CanSendTabToPc(null)); } }