using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using Paca.Browser.Networking; using Xunit; namespace Paca.Tests; public sealed class ModernBrowsingMilestone10Phase110Tests : IDisposable { private readonly string _tempDir; public ModernBrowsingMilestone10Phase110Tests() { _tempDir = Path.Combine(Path.GetTempPath(), "paca_m10_test_" + Guid.NewGuid().ToString("N")); Directory.CreateDirectory(_tempDir); } public void Dispose() { try { if (Directory.Exists(_tempDir)) { Directory.Delete(_tempDir, true); } } catch { // Ignored in cleanup } } [Fact] public void Gate167_DohProviderSelector_SelectsProvidersAndBuildsDnsQueryUrls() { // Arrange var configPath = Path.Combine(_tempDir, "doh_config.json"); var selector = new DohProviderSelector(configPath); // Act & Assert 1: Default state is SystemDefault / Off Assert.Equal(DohProvider.SystemDefault, selector.CurrentProvider); Assert.False(selector.IsEnabled); Assert.Empty(selector.EffectiveTemplateUrl); // Act & Assert 2: Select Cloudflare (1.1.1.1) selector.SelectProvider(DohProvider.Cloudflare); Assert.True(selector.IsEnabled); Assert.Equal("https://cloudflare-dns.com/dns-query", selector.EffectiveTemplateUrl); var queryCloudflareA = selector.BuildQueryUrl("paca-browser.org", DohRecordType.A); Assert.Equal("https://cloudflare-dns.com/dns-query?name=paca-browser.org&type=A", queryCloudflareA); // Act & Assert 3: Select Google (8.8.8.8) selector.SelectProvider(DohProvider.Google); Assert.Equal("https://dns.google/dns-query", selector.EffectiveTemplateUrl); var queryGoogleAaaa = selector.BuildQueryUrl("github.com", DohRecordType.AAAA); Assert.Equal("https://dns.google/dns-query?name=github.com&type=AAAA", queryGoogleAaaa); // Act & Assert 4: Select Quad9 (9.9.9.9) selector.SelectProvider(DohProvider.Quad9); Assert.Equal("https://dns.quad9.net/dns-query", selector.EffectiveTemplateUrl); // Act & Assert 5: Select Custom Assert.Throws(() => selector.SelectProvider(DohProvider.Custom, "http://insecure-dns.local/query")); Assert.Throws(() => selector.SelectProvider(DohProvider.Custom, "")); selector.SelectProvider(DohProvider.Custom, "https://secure-resolver.internal.net/dns-query"); Assert.Equal("https://secure-resolver.internal.net/dns-query", selector.EffectiveTemplateUrl); Assert.Equal("https://secure-resolver.internal.net/dns-query?name=my-server.local&type=TXT", selector.BuildQueryUrl("my-server.local", DohRecordType.TXT)); // Act & Assert 6: Persistence Save & Reload selector.Save(); var reloaded = new DohProviderSelector(configPath); Assert.Equal(DohProvider.Custom, reloaded.CurrentProvider); Assert.Equal("https://secure-resolver.internal.net/dns-query", reloaded.EffectiveTemplateUrl); } [Fact] public void Gate168_AntiFingerprintingEngine_InjectsPerDomainNoiseAndFarblingScript() { // Arrange var engine = new AntiFingerprintingEngine(FarblingMode.Balanced, sessionSalt: "test-salt-2026"); // Act 1: Domain Seed Determinism & Diversity var seed1 = engine.GetDomainSeed("example.com"); var seed2 = engine.GetDomainSeed("example.com"); var seedTracker = engine.GetDomainSeed("malicious-tracker.org"); Assert.Equal(seed1, seed2); Assert.NotEqual(seed1, seedTracker); // Act 2: Canvas Farbling Noise var originalPixels = new byte[32]; // 8 pixels RGBA for (int i = 0; i < originalPixels.Length; i++) { originalPixels[i] = (byte)(100 + (i * 3) % 100); } var farbledSiteA1 = engine.ApplyCanvasNoise("example.com", originalPixels, 4, 2); var farbledSiteA2 = engine.ApplyCanvasNoise("example.com", originalPixels, 4, 2); var farbledSiteB = engine.ApplyCanvasNoise("another-site.org", originalPixels, 4, 2); // Verify deterministic for same domain Assert.Equal(farbledSiteA1, farbledSiteA2); // Verify different across domains Assert.False(farbledSiteA1.SequenceEqual(farbledSiteB)); // Verify noise magnitude is subtly bounded (+-2 in Balanced mode) for (int i = 0; i < originalPixels.Length; i++) { var diff = Math.Abs(farbledSiteA1[i] - originalPixels[i]); Assert.True(diff <= 2, $"Pixel noise diff {diff} exceeded balanced threshold 2 at index {i}"); } // Act 3: Audio Buffer Farbling var originalAudio = new float[] { 0.125f, -0.25f, 0.5f, 0.75f, -0.1f }; var farbledAudioA = engine.ApplyAudioNoise("example.com", originalAudio); var farbledAudioB = engine.ApplyAudioNoise("another-site.org", originalAudio); Assert.Equal(originalAudio.Length, farbledAudioA.Length); Assert.False(farbledAudioA.SequenceEqual(farbledAudioB)); for (int i = 0; i < originalAudio.Length; i++) { var delta = Math.Abs(farbledAudioA[i] - originalAudio[i]); Assert.True(delta <= 0.001f, $"Audio noise delta {delta} exceeded threshold at index {i}"); } // Act 4: Farbling Injection Script var script = engine.GenerateFarblingScript("example.com"); Assert.False(string.IsNullOrWhiteSpace(script)); Assert.Contains("HTMLCanvasElement", script); Assert.Contains("getImageData", script); Assert.Contains("AudioBuffer", script); Assert.Contains(seed1.ToString(), script); } [Fact] public void Gate169_IsolatedIncognitoTabPolicy_IsolatesInWindowPartitionsAndPurgesOnClose() { // Arrange var policy = new IsolatedIncognitoTabPolicy(); // Act 1: Initial tab state Assert.False(policy.IsIncognitoTab("tab-normal-1")); Assert.Equal(0, policy.ActivePartitionsCount); Assert.Equal(0, policy.ActiveIncognitoTabsCount); // Act 2: Create isolated incognito tabs in the same window var part1 = policy.CreatePartition("Private Session Alpha"); policy.AssignTabToPartition("tab-incognito-1", part1.PartitionId); policy.AssignTabToPartition("tab-incognito-2", part1.PartitionId); var part2 = policy.CreatePartition("Private Session Beta"); policy.AssignTabToPartition("tab-incognito-3", part2.PartitionId); Assert.True(policy.IsIncognitoTab("tab-incognito-1")); Assert.True(policy.IsIncognitoTab("tab-incognito-2")); Assert.True(policy.IsIncognitoTab("tab-incognito-3")); Assert.Equal(2, policy.ActivePartitionsCount); Assert.Equal(3, policy.ActiveIncognitoTabsCount); // Act 3: Partition Storage Isolation policy.SetPartitionStorage(part1.PartitionId, "session_user", "alice@paca.internal"); policy.SetPartitionStorage(part2.PartitionId, "session_user", "bob@paca.internal"); Assert.Equal("alice@paca.internal", policy.GetPartitionStorage(part1.PartitionId, "session_user")); Assert.Equal("bob@paca.internal", policy.GetPartitionStorage(part2.PartitionId, "session_user")); Assert.Null(policy.GetPartitionStorage("unknown-partition", "session_user")); // Act 4: Close tab-incognito-1 (part1 still has tab-incognito-2, should NOT purge) policy.CloseTab("tab-incognito-1"); Assert.False(policy.IsIncognitoTab("tab-incognito-1")); Assert.Equal(2, policy.ActivePartitionsCount); Assert.Equal(2, policy.ActiveIncognitoTabsCount); Assert.Equal("alice@paca.internal", policy.GetPartitionStorage(part1.PartitionId, "session_user")); // Act 5: Close tab-incognito-2 (last tab in part1, should auto-purge ephemeral partition) policy.CloseTab("tab-incognito-2"); Assert.Equal(1, policy.ActivePartitionsCount); Assert.Equal(1, policy.ActiveIncognitoTabsCount); Assert.Null(policy.GetPartitionStorage(part1.PartitionId, "session_user")); // Act 6: Explicit Purge of part2 policy.PurgePartition(part2.PartitionId); Assert.Equal(0, policy.ActivePartitionsCount); Assert.Equal(0, policy.ActiveIncognitoTabsCount); Assert.Null(policy.GetPartitionStorage(part2.PartitionId, "session_user")); } [Fact] public void Gate170_JavaScriptKillSwitchPolicy_EnforcesPerDomainRulesAndInteractionRestore() { // Arrange var configPath = Path.Combine(_tempDir, "js_policy.json"); var policy = new JavaScriptKillSwitchPolicy(configPath); // Act 1: Default JS rule is Allow Assert.Equal(JsExecutionRule.Allow, policy.DefaultRule); Assert.True(policy.IsJsAllowed("https://good-news.org/page")); // Act 2: Disable JS on spam site policy.SetRule("spam-site.com", JsExecutionRule.Block); Assert.False(policy.IsJsAllowed("https://spam-site.com/article/123")); Assert.False(policy.IsJsAllowed("https://sub.spam-site.com/login")); Assert.True(policy.IsJsAllowed("https://good-news.org/page")); // Act 3: Interaction and selection restore rule Assert.False(policy.IsInteractionRestoreEnabled("paywall-archive.com")); policy.SetInteractionRestore("paywall-archive.com", true); Assert.True(policy.IsInteractionRestoreEnabled("paywall-archive.com")); var restoreScript = policy.GetRestoreInteractionScript(); Assert.Contains("contextmenu", restoreScript); Assert.Contains("user-select", restoreScript); Assert.Contains("selectstart", restoreScript); Assert.Contains("!important", restoreScript); // Act 4: Persistence Save & Reload policy.Save(); var reloaded = new JavaScriptKillSwitchPolicy(configPath); Assert.False(reloaded.IsJsAllowed("https://spam-site.com")); Assert.True(reloaded.IsInteractionRestoreEnabled("paywall-archive.com")); } [Fact] public void Gate171_MockLocationSimulator_SpoofsPresetsAndCoordinatesWithJsEmulation() { // Arrange var configPath = Path.Combine(_tempDir, "mock_location.json"); var simulator = new MockLocationSimulator(configPath); // Act 1: Preset Seoul simulator.SetPreset("tab-101", LocationPreset.Seoul); Assert.True(simulator.IsSimulationActive("tab-101")); var seoulCoords = simulator.GetCoordinates("tab-101"); Assert.NotNull(seoulCoords); Assert.Equal(37.5665, seoulCoords.Latitude, precision: 4); Assert.Equal(126.9780, seoulCoords.Longitude, precision: 4); Assert.Contains("Seoul", seoulCoords.LocationName); // Act 2: Preset San Francisco simulator.SetPreset("tab-102", LocationPreset.SanFrancisco); var sfCoords = simulator.GetCoordinates("tab-102"); Assert.NotNull(sfCoords); Assert.Equal(37.7749, sfCoords.Latitude, precision: 4); Assert.Equal(-122.4194, sfCoords.Longitude, precision: 4); // Act 3: Custom Coordinates var custom = new GeoCoordinates(35.6762, 139.6503, 5.0, LocationName: "Tokyo Tower"); simulator.SetCustomCoordinates("tab-103", custom); var tokyoCoords = simulator.GetCoordinates("tab-103"); Assert.NotNull(tokyoCoords); Assert.Equal(35.6762, tokyoCoords.Latitude, precision: 4); Assert.Equal(139.6503, tokyoCoords.Longitude, precision: 4); // Act 4: Geolocation Emulation JavaScript Generator var script = simulator.GenerateGeolocationOverrideScript("tab-103"); Assert.Contains("navigator.geolocation.getCurrentPosition", script); Assert.Contains("navigator.geolocation.watchPosition", script); Assert.Contains("35.6762", script); Assert.Contains("139.6503", script); // Act 5: Clear simulation simulator.ClearMockLocation("tab-101"); Assert.False(simulator.IsSimulationActive("tab-101")); Assert.Null(simulator.GetCoordinates("tab-101")); // Act 6: Persistence Save & Reload simulator.Save(); var reloaded = new MockLocationSimulator(configPath); Assert.True(reloaded.IsSimulationActive("tab-103")); Assert.Equal("Tokyo Tower", reloaded.GetCoordinates("tab-103")?.LocationName); } [Fact] public void Gate172_AmsiScannerBridge_ScansBuffersAndFilesWithMalwareDetection() { // Arrange var bridge = new AmsiScannerBridge(); // Act 1: Clean buffer scan var cleanCode = Encoding.UTF8.GetBytes("function sayHello() { console.log('Welcome to PACA Browser'); }"); var cleanReport = bridge.ScanBuffer(cleanCode, "app.js"); Assert.Equal(AmsiScanVerdict.Clean, cleanReport.Verdict); Assert.False(cleanReport.IsBlocked); Assert.True(bridge.IsSafeToExecute(cleanReport)); // Act 2: EICAR Standard Anti-Virus Test File Detection var eicarString = "X5O!P%@AP[4\\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*"; var eicarBytes = Encoding.UTF8.GetBytes(eicarString); var malwareReport = bridge.ScanBuffer(eicarBytes, "eicar.com"); Assert.Equal(AmsiScanVerdict.MalwareDetected, malwareReport.Verdict); Assert.True(malwareReport.IsBlocked); Assert.False(bridge.IsSafeToExecute(malwareReport)); Assert.Contains("EICAR", malwareReport.Details); // Act 3: Suspicious Dropper Script Pattern var suspiciousScript = "cmd.exe /c powershell -ExecutionPolicy Bypass -NoProfile -EncodedCommand SQBFAFgA..."; var susReport = bridge.ScanString(suspiciousScript, "invoice_attachment.bat"); Assert.Equal(AmsiScanVerdict.Suspicious, susReport.Verdict); Assert.True(susReport.IsBlocked); Assert.False(bridge.IsSafeToExecute(susReport)); // Act 4: File Scanning on Disk var cleanFilePath = Path.Combine(_tempDir, "safe_readme.txt"); File.WriteAllText(cleanFilePath, "PACA Browser Secure Download Completed."); var fileReportClean = bridge.ScanFile(cleanFilePath); Assert.Equal(AmsiScanVerdict.Clean, fileReportClean.Verdict); Assert.True(bridge.IsSafeToExecute(fileReportClean)); var infectedFilePath = Path.Combine(_tempDir, "trojan_test.bin"); File.WriteAllText(infectedFilePath, eicarString); var fileReportInfected = bridge.ScanFile(infectedFilePath); Assert.Equal(AmsiScanVerdict.MalwareDetected, fileReportInfected.Verdict); Assert.True(fileReportInfected.IsBlocked); } [Fact] public void Gate173_RealtimeStreamInspector_TracksWebSocketSseWebRtcAndBandwidth() { // Arrange var inspector = new RealtimeStreamInspector(); // Act 1: Register active streams inspector.RegisterConnection("ws-1", StreamProtocol.WebSocket, "wss://stream.crypto.com/ticker", "tab-1"); inspector.RegisterConnection("sse-1", StreamProtocol.ServerSentEvents, "https://news.paca.org/live-feed", "tab-1"); inspector.RegisterConnection("rtc-1", StreamProtocol.WebRtc, "webrtc://meet.corp.net/room-alpha", "tab-2"); // Act 2: Record traffic & messages inspector.RecordTraffic("ws-1", bytesReceivedDelta: 1024, bytesSentDelta: 256, messagesReceivedDelta: 10, messagesSentDelta: 2); inspector.RecordTraffic("sse-1", bytesReceivedDelta: 2048, bytesSentDelta: 0, messagesReceivedDelta: 20, messagesSentDelta: 0); inspector.RecordTraffic("rtc-1", bytesReceivedDelta: 50000, bytesSentDelta: 40000, messagesReceivedDelta: 50, messagesSentDelta: 45); // Act 3: Global Aggregate Metrics var globalMetrics = inspector.GetAggregateMetrics(); Assert.Equal(3, globalMetrics.TotalActiveConnections); Assert.Equal(1, globalMetrics.WebSocketCount); Assert.Equal(1, globalMetrics.SseCount); Assert.Equal(1, globalMetrics.WebRtcCount); Assert.Equal(53072, globalMetrics.TotalBytesReceived); Assert.Equal(40256, globalMetrics.TotalBytesSent); Assert.Equal(127, globalMetrics.TotalMessages); // Act 4: Tab-scoped Metrics var tab1Metrics = inspector.GetAggregateMetrics("tab-1"); Assert.Equal(2, tab1Metrics.TotalActiveConnections); Assert.Equal(3072, tab1Metrics.TotalBytesReceived); Assert.Equal(256, tab1Metrics.TotalBytesSent); var tab2Metrics = inspector.GetAggregateMetrics("tab-2"); Assert.Equal(1, tab2Metrics.TotalActiveConnections); Assert.Equal(50000, tab2Metrics.TotalBytesReceived); // Act 5: Connection State & Closure inspector.CloseConnection("ws-1", StreamState.Closed); var updatedWs1 = inspector.GetConnection("ws-1"); Assert.NotNull(updatedWs1); Assert.Equal(StreamState.Closed, updatedWs1.State); Assert.NotNull(updatedWs1.ClosedAtUtc); var activeAfterClose = inspector.GetActiveConnections(); Assert.Equal(2, activeAfterClose.Count); Assert.DoesNotContain(activeAfterClose, c => c.Id == "ws-1"); } [Fact] public void Gate174_DiskCacheCapManager_EnforcesSizeCapAndPlansLruEviction() { // Arrange: Start with small 1000 byte cap var manager = new DiskCacheCapManager(maxSizeBytes: 1000); // Act & Assert 1: Presets manager.SetPreset(DiskCacheCapPreset.Cap512MB); Assert.Equal(512L * 1024 * 1024, manager.MaxSizeBytes); Assert.Equal(DiskCacheCapPreset.Cap512MB, manager.Preset); manager.SetPreset(DiskCacheCapPreset.Cap1GB); Assert.Equal(1024L * 1024 * 1024, manager.MaxSizeBytes); Assert.Equal(DiskCacheCapPreset.Cap1GB, manager.Preset); manager.SetPreset(DiskCacheCapPreset.Cap2GB); Assert.Equal(2048L * 1024 * 1024, manager.MaxSizeBytes); Assert.Equal(DiskCacheCapPreset.Cap2GB, manager.Preset); // Reset to Custom 1000 bytes for deterministic LRU eviction testing manager.SetPreset(DiskCacheCapPreset.Custom, customSizeBytes: 1000); Assert.Equal(1000, manager.MaxSizeBytes); // Act 2: Register cache entries with different last-accessed times var now = DateTime.UtcNow; var file1 = Path.Combine(_tempDir, "cache_001.bin"); var file2 = Path.Combine(_tempDir, "cache_002.bin"); var file3 = Path.Combine(_tempDir, "cache_003.bin"); File.WriteAllBytes(file1, new byte[400]); File.WriteAllBytes(file2, new byte[400]); File.WriteAllBytes(file3, new byte[300]); // file1 accessed 15m ago (oldest), file2 accessed 10m ago, file3 accessed 1m ago (newest) manager.RegisterEntry("key1", file1, 400, lastAccessedUtc: now.AddMinutes(-15)); manager.RegisterEntry("key2", file2, 400, lastAccessedUtc: now.AddMinutes(-10)); manager.RegisterEntry("key3", file3, 300, lastAccessedUtc: now.AddMinutes(-1)); // Act & Assert 3: Total usage exceeds cap Assert.Equal(1100, manager.GetTotalUsageBytes()); Assert.True(manager.IsCapExceeded()); // Act 4: Plan eviction targeting 20% headroom (target usage <= 800 bytes) // Excess to free = 1100 - 800 = 300 bytes. // In LRU order, key1 (400 bytes) should be evicted first. Evicting key1 drops usage to 700 bytes <= 800. var plan = manager.PlanEviction(targetHeadroomRatio: 0.20); Assert.True(plan.IsEvictionNeeded); Assert.Single(plan.EntriesToEvict); Assert.Equal("key1", plan.EntriesToEvict[0].Key); Assert.Equal(400, plan.TotalBytesToEvict); Assert.Equal(700, plan.ProjectedUsageBytes); // Act 5: Execute eviction var deletedFilePaths = new List(); var freedBytes = manager.ExecuteEviction(plan, deleteAction: path => { deletedFilePaths.Add(path); if (File.Exists(path)) File.Delete(path); }); Assert.Equal(400, freedBytes); Assert.Contains(file1, deletedFilePaths); Assert.False(File.Exists(file1)); Assert.True(File.Exists(file2)); Assert.True(File.Exists(file3)); // Usage is now within cap Assert.Equal(700, manager.GetTotalUsageBytes()); Assert.False(manager.IsCapExceeded()); // Act 6: Touch entry updates LRU order // Touch key2 to make it newer than key3 manager.TouchEntry("key2"); // Add another entry of 400 bytes -> total = 700 + 400 = 1100 bytes var file4 = Path.Combine(_tempDir, "cache_004.bin"); File.WriteAllBytes(file4, new byte[400]); manager.RegisterEntry("key4", file4, 400, lastAccessedUtc: DateTime.UtcNow); // Since key2 was touched, key3 is now the oldest (accessed ~1m ago vs key2 touched now) var plan2 = manager.PlanEviction(targetHeadroomRatio: 0.20); Assert.True(plan2.IsEvictionNeeded); Assert.Equal("key3", plan2.EntriesToEvict[0].Key); } }