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,203 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Paca.App;
using Paca.App.Browser.Features;
using Paca.Browser;
using Paca.Browser.Tabs;
using Xunit;
namespace Paca.Tests.Browser;
/// <summary>
/// 사생활 보호(InPrivate/Private Browsing) 무결성 검증 게이트:
/// 세션 스냅샷, 닫힌 탭 복원, 방문 기록 영속화, 원시 네트워크 로그, NTP 대시보드 무누수 검증.
/// </summary>
public class PrivateBrowsingSessionGateTests
{
private static TabManager NewManager() => new(() => new TabItem());
[Fact]
public void CaptureSession_사생활보호_InPrivate_탭은_세션스냅샷에_포함되지_않는다()
{
var m = NewManager();
var normalTab = m.NewTab("https://public-site.com");
var privateTab = m.NewTab("https://secret-bank.com", isPrivate: true);
m.Activate(privateTab);
var snap = m.CaptureSession();
Assert.Contains("https://public-site.com", snap.Urls);
Assert.DoesNotContain("https://secret-bank.com", snap.Urls);
Assert.Single(snap.Urls);
}
[Fact]
public void CaptureSession_모든_탭이_InPrivate일때_비공개URL이_누출되지_않는다()
{
var m = NewManager();
m.NewTab("https://confidential-1.com", isPrivate: true);
m.NewTab("https://confidential-2.com", isPrivate: true);
var snap = m.CaptureSession();
Assert.DoesNotContain("https://confidential-1.com", snap.Urls);
Assert.DoesNotContain("https://confidential-2.com", snap.Urls);
}
[Fact]
public void ReopenClosedTab_InPrivate_탭_복원_시_IsPrivate_속성이_그대로_보존된다()
{
var m = NewManager();
var privateTab = m.NewTab("https://secret-passwords.com", isPrivate: true);
// InPrivate 탭 닫기
m.CloseTab(privateTab);
// Ctrl+Shift+T 복원 시 InPrivate 상태가 유지되어야 함 (일반 탭으로 격하 누수 방지)
var reopened = m.ReopenClosedTab();
Assert.NotNull(reopened);
Assert.True(reopened!.IsPrivate, "복원된 탭도 IsPrivate 상태가 엄격히 유지되어야 합니다.");
Assert.Equal("https://secret-passwords.com", reopened.Url);
}
[Fact]
public void NewTabPageBuilder_InPrivate_NTP는_안내문구와_검색창을_제공하고_일반기록은_배제한다()
{
var html = NewTabPageBuilder.BuildInPrivateNtp(isDark: true, accentColor: "#8B5CF6");
Assert.NotNull(html);
Assert.Contains("InPrivate", html, StringComparison.OrdinalIgnoreCase);
Assert.Contains("방문 기록", html);
Assert.Contains("쿠키", html);
Assert.Contains("저장되지 않습니다", html);
// 일반 NTP의 컴포넌트(shortcuts, recentHistory, recentDownloads) JSON 데이터가 없어야 함
Assert.DoesNotContain("\"recentHistory\":[", html);
Assert.DoesNotContain("\"recentDownloads\":[", html);
}
[Fact]
public void NewTabPageBuilder_IsNewTabUrl_paca_inprivate_인식_검증()
{
Assert.True(NewTabPageBuilder.IsNewTabUrl("paca://inprivate"));
Assert.True(NewTabPageBuilder.IsNewTabUrl("about:inprivate"));
}
[Fact]
public async Task MainWindow_InPrivate_창_생성_시_기본_IsPrivate_및_비공개_식별자_유지()
{
var dataDir = Path.Combine(Path.GetTempPath(), $"paca_inprivate_test_{Guid.NewGuid():N}");
Environment.SetEnvironmentVariable("VD_DATA_DIR", dataDir);
try
{
using var sta = StaDispatcher.Start();
WpfTestApp.Ensure(sta.Dispatcher);
await sta.InvokeAsync(async () =>
{
var privateWindow = new MainWindow(isPrivateWindow: true);
privateWindow.Show();
Assert.True(privateWindow.IsPrivateWindow, "isPrivateWindow: true로 생성된 창의 IsPrivateWindow는 true여야 합니다.");
Assert.Contains("InPrivate", privateWindow.Title, StringComparison.OrdinalIgnoreCase);
// 새 탭 생성 시 기본으로 InPrivate인지 확인
var newTab = privateWindow.Tabs.NewTab("https://test.org");
Assert.True(newTab.IsPrivate, "InPrivate 창에서 생성된 탭은 기본적으로 IsPrivate이어야 합니다.");
privateWindow.Close();
await Task.CompletedTask;
});
}
finally
{
try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { }
}
}
[Fact]
public async Task InPrivate_탭_종료_시_아카이브_파일_archive_json에_저장되지_않는다()
{
var dataDir = Path.Combine(Path.GetTempPath(), $"paca_archive_leak_test_{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();
// 1. InPrivate 탭 생성 및 닫기
var secretTab = window.Tabs.NewTab("https://classified-credentials.com", isPrivate: true);
secretTab.Title = "기밀 페이지";
window.Tabs.CloseTab(secretTab);
// 2. archive.json 확인: InPrivate 탭이 저장되지 않았어야 함
var archivePath = Path.Combine(dataDir, "archive.json");
if (File.Exists(archivePath))
{
var archiveContent = File.ReadAllText(archivePath);
Assert.DoesNotContain("classified-credentials.com", archiveContent);
}
// 3. 일반 탭 생성 및 닫기 -> 일반 탭은 정상 저장되어야 함
var normalTab = window.Tabs.NewTab("https://public-news.com");
normalTab.Title = "공개 뉴스";
window.Tabs.CloseTab(normalTab);
if (File.Exists(archivePath))
{
var archiveContent = File.ReadAllText(archivePath);
Assert.Contains("public-news.com", archiveContent);
}
window.Close();
await Task.CompletedTask;
});
}
finally
{
try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { }
}
}
[Fact]
public async Task InPrivate_탭_활성화_시_주소창_InPrivate_배지가_동기화된다()
{
var dataDir = Path.Combine(Path.GetTempPath(), $"paca_badge_test_{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 normalTab = window.Tabs.NewTab("https://normal.com", activate: true);
Assert.Equal(Visibility.Collapsed, window.InPrivateAddressBadge.Visibility);
var privateTab = window.Tabs.NewTab("https://secret.com", activate: true, isPrivate: true);
Assert.Equal(Visibility.Visible, window.InPrivateAddressBadge.Visibility);
window.Tabs.Activate(normalTab);
Assert.Equal(Visibility.Collapsed, window.InPrivateAddressBadge.Visibility);
window.Close();
await Task.CompletedTask;
});
}
finally
{
try { if (Directory.Exists(dataDir)) Directory.Delete(dataDir, true); } catch { }
}
}
}