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:
parent
a88aafa1e5
commit
0e568f1f0a
975 changed files with 130593 additions and 16783 deletions
206
tests/Paca.Tests/Browser/PacaUniqueBrowserFeaturesGateTests.cs
Normal file
206
tests/Paca.Tests/Browser/PacaUniqueBrowserFeaturesGateTests.cs
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Paca.Core;
|
||||
using Xunit;
|
||||
|
||||
namespace Paca.Tests.Browser;
|
||||
|
||||
/// <summary>
|
||||
/// 크롬/파이어폭스 대비 Paca만의 초강력 차별화 브라우저 10대 기능 및 진짜 파비콘 게이트 (Gate 136 ~ Gate 145)
|
||||
/// </summary>
|
||||
public class PacaUniqueBrowserFeaturesGateTests
|
||||
{
|
||||
[Fact]
|
||||
public void Gate136_RealFaviconFetcher_FallbackLadder_Audit()
|
||||
{
|
||||
var fetcher = new RealFaviconFetcher();
|
||||
|
||||
// 1. HTML 내에 고해상도 apple-touch-icon 이 있는 경우 최우선 추출
|
||||
var htmlWithApple = @"<html><head>
|
||||
<link rel='icon' href='/favicon.ico'>
|
||||
<link rel='apple-touch-icon' sizes='180x180' href='/icons/apple-180.png'>
|
||||
</head></html>";
|
||||
var iconUrl = fetcher.ExtractBestFaviconUrl("https://example.com/page", htmlWithApple);
|
||||
Assert.Equal("https://example.com/icons/apple-180.png", iconUrl);
|
||||
|
||||
// 2. 일반 link rel='icon' 만 있는 경우
|
||||
var htmlWithRelIcon = @"<html><head><link rel='icon' href='/static/icon.png'></head></html>";
|
||||
var relIconUrl = fetcher.ExtractBestFaviconUrl("https://example.com/page", htmlWithRelIcon);
|
||||
Assert.Equal("https://example.com/static/icon.png", relIconUrl);
|
||||
|
||||
// 3. HTML 에 아이콘 태그가 전혀 없는 경우 -> 도메인 루트 /favicon.ico 로 폴백
|
||||
var emptyHtml = "<html><head><title>No Icon</title></head></html>";
|
||||
var fallbackUrl = fetcher.ExtractBestFaviconUrl("https://myblog.org/sub/article", emptyHtml);
|
||||
Assert.Equal("https://myblog.org/favicon.ico", fallbackUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate137_SmartMediaSnifferV2_StreamDetection_Audit()
|
||||
{
|
||||
var sniffer = new SmartMediaSnifferV2();
|
||||
|
||||
// 일반 웹 리소스는 무시
|
||||
Assert.Null(sniffer.SniffRequest("https://example.com/style.css", "text/css"));
|
||||
Assert.Null(sniffer.SniffRequest("https://example.com/script.js", "application/javascript"));
|
||||
|
||||
// HLS m3u8 스트림 감지
|
||||
var hls = sniffer.SniffRequest("https://stream.cdn.com/live/playlist.m3u8", "application/vnd.apple.mpegurl");
|
||||
Assert.NotNull(hls);
|
||||
Assert.Equal(SniffedMediaType.HlsStream, hls.Type);
|
||||
|
||||
// DASH mpd 스트림 감지
|
||||
var dash = sniffer.SniffRequest("https://stream.cdn.com/dash/manifest.mpd", "application/dash+xml");
|
||||
Assert.NotNull(dash);
|
||||
Assert.Equal(SniffedMediaType.DashStream, dash.Type);
|
||||
|
||||
// 직접 MP4 비디오 감지
|
||||
var mp4 = sniffer.SniffRequest("https://video.org/file.mp4?token=abc", "video/mp4");
|
||||
Assert.NotNull(mp4);
|
||||
Assert.Equal(SniffedMediaType.DirectVideo, mp4.Type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate138_AntiTrackingFingerprintGuard_Audit()
|
||||
{
|
||||
var guard = new AntiTrackingFingerprintGuard();
|
||||
Assert.True(guard.IsTrackingDomain("google-analytics.com"));
|
||||
Assert.True(guard.IsTrackingDomain("stats.doubleclick.net"));
|
||||
Assert.True(guard.IsTrackingDomain("connect.facebook.net"));
|
||||
Assert.False(guard.IsTrackingDomain("youtube.com"));
|
||||
|
||||
// Canvas 핑거프린팅 방어 노이즈 스크립트 생성 확인
|
||||
var noiseScript = guard.GenerateCanvasNoiseScript();
|
||||
Assert.Contains("HTMLCanvasElement.prototype.toDataURL", noiseScript);
|
||||
Assert.Contains("getImageData", noiseScript);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate139_ReaderModeExtractor_CleanArticle_Audit()
|
||||
{
|
||||
var extractor = new ReaderModeExtractor();
|
||||
var messyHtml = @"
|
||||
<html>
|
||||
<body>
|
||||
<header><nav>Menu 1 Menu 2</nav></header>
|
||||
<div class='sidebar'>Banner Ads</div>
|
||||
<article>
|
||||
<h1>The Future of Video Browsers</h1>
|
||||
<p>Paca browser provides built-in media streaming intelligence without third-party extensions.</p>
|
||||
<p>It respects user privacy and eliminates bloated telemetry.</p>
|
||||
</article>
|
||||
<footer>Copyright 2026</footer>
|
||||
</body>
|
||||
</html>";
|
||||
|
||||
var article = extractor.ExtractArticle(messyHtml);
|
||||
Assert.NotNull(article);
|
||||
Assert.Equal("The Future of Video Browsers", article.Title);
|
||||
Assert.Contains("Paca browser provides built-in", article.CleanText);
|
||||
Assert.DoesNotContain("Banner Ads", article.CleanText);
|
||||
Assert.DoesNotContain("Menu 1", article.CleanText);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate140_VideoPiPOverlayController_ForcePiP_Audit()
|
||||
{
|
||||
var pip = new VideoPiPOverlayController();
|
||||
var bypassScript = pip.GenerateForcePiPScript();
|
||||
|
||||
// 사이트의 disablePictureInPicture 속성 무력화 스크립트
|
||||
Assert.Contains("disablePictureInPicture", bypassScript);
|
||||
Assert.Contains("requestPictureInPicture", bypassScript);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate141_SpeedBoostTurboCache_Prefetch_Audit()
|
||||
{
|
||||
var cache = new SpeedBoostTurboCache();
|
||||
Assert.Equal(0, cache.PreloadedCount);
|
||||
|
||||
cache.RegisterPrefetchTarget("https://example.com/next-video-page");
|
||||
Assert.Equal(1, cache.PreloadedCount);
|
||||
Assert.True(cache.IsPrefetched("https://example.com/next-video-page"));
|
||||
Assert.False(cache.IsPrefetched("https://example.com/random-page"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate142_OneClickAudioExtractor_AudioOnlyStreamFilter_Audit()
|
||||
{
|
||||
var extractor = new OneClickAudioExtractor();
|
||||
var streams = new List<MediaStreamTrackInfo>
|
||||
{
|
||||
new("v1", "video", "1080p 60fps", 6000),
|
||||
new("v2", "video", "720p", 3000),
|
||||
new("a1", "audio", "AAC 128k", 128),
|
||||
new("a2", "audio", "Opus 192k High-Res", 192)
|
||||
};
|
||||
|
||||
var bestAudio = extractor.SelectHighestQualityAudioTrack(streams);
|
||||
Assert.NotNull(bestAudio);
|
||||
Assert.Equal("a2", bestAudio.TrackId);
|
||||
Assert.Equal("Opus 192k High-Res", bestAudio.FormatLabel);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate143_CrossPlatformTabHandshake_Payload_Audit()
|
||||
{
|
||||
var handshake = new CrossPlatformTabHandshake();
|
||||
var packet = handshake.CreatePushPacket(
|
||||
sourceDeviceId: "GALAXY-Z-FOLD-6",
|
||||
sourcePlatform: "Android",
|
||||
targetDeviceId: "DESKTOP-PACA-WIN",
|
||||
pageUrl: "https://www.youtube.com/watch?v=sample123",
|
||||
pageTitle: "Epic 4K Nature",
|
||||
scrollPositionRatio: 0.42
|
||||
);
|
||||
|
||||
Assert.Equal("PACA_TAB_PUSH_V1", packet.ProtocolHeader);
|
||||
Assert.Equal("Android", packet.SourcePlatform);
|
||||
Assert.Equal(0.42, packet.ScrollPositionRatio);
|
||||
|
||||
var restored = handshake.ParsePushPacket(handshake.Serialize(packet));
|
||||
Assert.NotNull(restored);
|
||||
Assert.Equal("Epic 4K Nature", restored.PageTitle);
|
||||
Assert.Equal("https://www.youtube.com/watch?v=sample123", restored.PageUrl);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate144_AutoFillFormSecureVault_MasterKey_Audit()
|
||||
{
|
||||
var vault = new AutoFillFormSecureVault();
|
||||
var masterKey = "MyMasterPassword2026!";
|
||||
|
||||
vault.SaveCredential("https://github.com/login", "pacadev", "SecretP@ss!", masterKey);
|
||||
var retrieved = vault.RetrieveCredential("https://github.com/login", masterKey);
|
||||
|
||||
Assert.NotNull(retrieved);
|
||||
Assert.Equal("pacadev", retrieved.Username);
|
||||
Assert.Equal("SecretP@ss!", retrieved.Password);
|
||||
|
||||
// 잘못된 비밀번호 시도 시 복호화 실패(null)
|
||||
var failed = vault.RetrieveCredential("https://github.com/login", "WrongPassword");
|
||||
Assert.Null(failed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Gate145_EndToEndInteractiveJourneyAudit_Audit()
|
||||
{
|
||||
// Favicon 추출 -> 스니핑 -> 읽기모드 -> PiP 스크립트 -> 오디오 분리 -> 탭 푸시 전체 파이프라인
|
||||
var fetcher = new RealFaviconFetcher();
|
||||
var sniffer = new SmartMediaSnifferV2();
|
||||
var extractor = new OneClickAudioExtractor();
|
||||
var handshake = new CrossPlatformTabHandshake();
|
||||
|
||||
var sampleHtml = "<html><head><link rel='apple-touch-icon' href='/paca.png'></head><body><article><h1>Title</h1><p>Body</p></article></body></html>";
|
||||
var faviconUrl = fetcher.ExtractBestFaviconUrl("https://paca.tv/watch/1", sampleHtml);
|
||||
Assert.Equal("https://paca.tv/paca.png", faviconUrl);
|
||||
|
||||
var stream = sniffer.SniffRequest("https://paca.tv/live.m3u8", "application/x-mpegURL");
|
||||
Assert.NotNull(stream);
|
||||
|
||||
var pushPacket = handshake.CreatePushPacket("PC", "Windows", "Mobile", "https://paca.tv/watch/1", "Title", 0.5);
|
||||
Assert.Equal("PACA_TAB_PUSH_V1", pushPacket.ProtocolHeader);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue