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:
Yun Chan 2026-09-14 00:39:44 +09:00
parent a88aafa1e5
commit 0e568f1f0a
975 changed files with 130593 additions and 16783 deletions

View file

@ -0,0 +1,165 @@
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;
/// <summary>
/// PACA Mobile 사생활 보호(InPrivate / Incognito) 세션 격리 및 수명주기 무결성 검증 게이트
/// </summary>
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);
}
}