using System; using System.IO; using System.Linq; using System.Threading.Tasks; using Paca.Mobile; using Paca.Mobile.Core; using Xunit; namespace Paca.Tests.Mobile; /// /// PACA Mobile 사생활 보호(InPrivate / Incognito) 세션 격리 및 수명주기 무결성 검증 게이트 /// public class MobilePrivateBrowsingGateTests : IDisposable { private readonly string _testTempDir; public MobilePrivateBrowsingGateTests() { _testTempDir = Path.Combine(Path.GetTempPath(), "paca_mobile_priv_tests_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_testTempDir); } public void Dispose() { try { if (Directory.Exists(_testTempDir)) Directory.Delete(_testTempDir, recursive: true); } catch { } } [Fact] public void Gate1_MobilePrivateBrowsingPolicy_사생활보호_정책_단정_검증() { var privTab = new BrowserTab { Id = "priv-1", Url = "https://confidential-portal.org", IsPrivate = true }; var normTab = new BrowserTab { Id = "norm-1", Url = "https://public-news.org", IsPrivate = false }; // 1. 방문 기록 저장 차단 여부 Assert.False(MobilePrivateBrowsingPolicy.ShouldPersistHistory(privTab)); Assert.True(MobilePrivateBrowsingPolicy.ShouldPersistHistory(normTab)); Assert.False(MobilePrivateBrowsingPolicy.ShouldPersistHistory(null)); // 2. 디스크 쿠키 Flush 차단 여부 (RAM에만 유지) Assert.False(MobilePrivateBrowsingPolicy.ShouldFlushCookies(privTab)); Assert.True(MobilePrivateBrowsingPolicy.ShouldFlushCookies(normTab)); Assert.False(MobilePrivateBrowsingPolicy.ShouldFlushCookies(null)); // 3. 디스크 캐시 및 양식 자동저장 차단 여부 Assert.False(MobilePrivateBrowsingPolicy.ShouldEnableDiskCache(privTab)); Assert.True(MobilePrivateBrowsingPolicy.ShouldEnableDiskCache(normTab)); Assert.False(MobilePrivateBrowsingPolicy.ShouldSaveFormData(privTab)); Assert.True(MobilePrivateBrowsingPolicy.ShouldSaveFormData(normTab)); // 4. 최근 앱 섬네일 스크린샷 방어(FLAG_SECURE) 활성화 여부 Assert.True(MobilePrivateBrowsingPolicy.ShouldEnableSecureWindow(privTab)); Assert.False(MobilePrivateBrowsingPolicy.ShouldEnableSecureWindow(normTab)); Assert.False(MobilePrivateBrowsingPolicy.ShouldEnableSecureWindow(null)); } [Fact] public void Gate2_MobileTabManager_시크릿탭_카운트_및_CloseAllPrivateTabs_검증() { var manager = new MobileTabManager(); // manager starts with 1 normal tab Assert.Equal(1, manager.NormalTabCount); Assert.Equal(0, manager.PrivateTabCount); Assert.False(manager.HasPrivateTabs); var priv1 = manager.AddTab("https://secret1.com", isPrivate: true); var priv2 = manager.AddTab("https://secret2.com", isPrivate: true); var norm2 = manager.AddTab("https://public2.com", isPrivate: false); Assert.Equal(4, manager.Count); Assert.Equal(2, manager.NormalTabCount); Assert.Equal(2, manager.PrivateTabCount); Assert.True(manager.HasPrivateTabs); bool allPrivateClosedFired = false; manager.AllPrivateTabsClosed += () => allPrivateClosedFired = true; // 모든 시크릿 탭 일괄 닫기 manager.CloseAllPrivateTabs(); Assert.True(allPrivateClosedFired, "AllPrivateTabsClosed 이벤트가 발생해야 합니다."); Assert.Equal(2, manager.Count); Assert.Equal(2, manager.NormalTabCount); Assert.Equal(0, manager.PrivateTabCount); Assert.False(manager.HasPrivateTabs); Assert.All(manager.Tabs, t => Assert.False(t.IsPrivate)); } [Fact] public void Gate3_마지막_시크릿탭_개별_종료_시_AllPrivateTabsClosed_이벤트_발생_검증() { var manager = new MobileTabManager(); var priv1 = manager.AddTab("https://secret-only.com", isPrivate: true); BrowserTab? closedTab = null; manager.TabClosed += t => closedTab = t; bool allPrivateClosedFired = false; manager.AllPrivateTabsClosed += () => allPrivateClosedFired = true; manager.CloseTab(priv1.Id); Assert.NotNull(closedTab); Assert.Equal(priv1.Id, closedTab!.Id); Assert.True(allPrivateClosedFired, "마지막 시크릿 탭이 닫히면 AllPrivateTabsClosed가 발행되어야 합니다."); Assert.False(manager.HasPrivateTabs); } [Fact] public void Gate4_MobilePrivateSessionManager_세션_종료_시_플랫폼_정화_훅_연동_검증() { bool cacheCleared = false; bool cookiesCleared = false; bool formDataCleared = false; bool webStorageCleared = false; var sessionManager = new MobilePrivateSessionManager( clearCache: () => cacheCleared = true, clearCookies: () => cookiesCleared = true, clearFormData: () => formDataCleared = true, clearWebStorage: () => webStorageCleared = true ); sessionManager.PurgePrivateSessionData(); Assert.True(cacheCleared, "캐시 정화 훅이 실행되어야 합니다."); Assert.True(cookiesCleared, "쿠키 정화 훅이 실행되어야 합니다."); Assert.True(formDataCleared, "폼 데이터 정화 훅이 실행되어야 합니다."); Assert.True(webStorageCleared, "웹 스토리지 정화 훅이 실행되어야 합니다."); } [Fact] public async Task Gate5_MobileTabManager_세션_영속화_시_시크릿탭_누출_원천_차단() { var manager = new MobileTabManager(); manager.AddTab("https://public-portal.com", isPrivate: false); manager.AddTab("https://confidential-bank-account.org/dashboard", isPrivate: true); var sessionFile = Path.Combine(_testTempDir, "session_test.json"); await manager.SaveSessionAsync(sessionFile); Assert.True(File.Exists(sessionFile)); var rawJson = await File.ReadAllTextAsync(sessionFile); // 시크릿 URL이 디스크 파일에 일체 기록되지 않아야 함 Assert.DoesNotContain("confidential-bank-account", rawJson); Assert.Contains("public-portal.com", rawJson); } }