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,224 @@
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;
/// <summary>
/// 초고강도 사생활 보호(InPrivate / Incognito) 무결성 게이트:
/// 1. 영상 재생목록 디스크 무누수 (playlist.json 차단)
/// 2. 사이트 줌 배율 디스크 무누수 (sitezoom.json 차단)
/// 3. 보조 창(Glance, Player) InPrivate 격리 옵션 승계
/// 4. 원격 브라우저 탭 동기화(/api/tabs) 비공개 탭 100% 필터링
/// 5. 확장 프로그램 콘텐츠 스크립트 InPrivate 탭 주입 차단
/// 6. 모바일 시크릿 탭 활성 시 PC 전송 액션 방어 가드
/// </summary>
[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<RemoteTabInfo>
{
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));
}
}