releases/tests/Paca.Tests/BrowserStorageAndSessionHardeningPhase7Tests.cs

114 lines
4.1 KiB
C#

using System;
using System.IO;
using Paca.Browser;
using Xunit;
namespace Paca.Tests;
public sealed class BrowserStorageAndSessionHardeningPhase7Tests : IDisposable
{
private readonly string _tempDir;
public BrowserStorageAndSessionHardeningPhase7Tests()
{
_tempDir = Path.Combine(Path.GetTempPath(), "paca_phase7_test_" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_tempDir);
}
public void Dispose()
{
try
{
if (Directory.Exists(_tempDir))
{
Directory.Delete(_tempDir, true);
}
}
catch { }
}
[Fact]
public void Gate701_WorkspaceRoutingRules_Preserves_W_Prefixed_Domains_And_Handles_Null()
{
// Arrange
var rulesFile = Path.Combine(_tempDir, "routing_rules.json");
var rules = new WorkspaceRoutingRules(rulesFile);
// Act - domains starting with 'w'
rules.Add("wired.com", "TechNews");
rules.Add("whatsapp.com", "Messaging");
rules.Add("www.wsj.com", "Finance");
rules.Add("weather.com", "Utility");
// Assert - must NOT corrupt domains like 'wired.com' -> 'ired.com'
Assert.Equal("TechNews", rules.Match("https://wired.com/story/ai"));
Assert.Equal("TechNews", rules.Match("https://www.wired.com/story/ai"));
Assert.Equal("Messaging", rules.Match("https://web.whatsapp.com"));
Assert.Equal("Messaging", rules.Match("https://whatsapp.com"));
Assert.Equal("Finance", rules.Match("https://wsj.com"));
Assert.Equal("Utility", rules.Match("https://weather.com/today"));
// Null & Malformed safety
Assert.Null(rules.Match(null));
Assert.Null(rules.Match(""));
Assert.Null(rules.Match("javascript:alert(1)"));
Assert.Null(rules.Match("not-a-url"));
}
[Fact]
public void Gate702_NotesStore_Ensures_Directory_On_Delete_And_Explicit_Save()
{
// Arrange: deep nested path whose parent directory doesn't exist yet
var deepNotesPath = Path.Combine(_tempDir, "nested", "sub", "notes.json");
var store = new NotesStore(deepNotesPath);
// Act & Assert 1: Add creates file & directory
var id1 = store.Add("First Note", "Content 1");
Assert.True(File.Exists(deepNotesPath));
Assert.Single(store.List());
// Now simulate directory deletion or separate store instance targeting a new path
var deepNotesPath2 = Path.Combine(_tempDir, "another", "sub", "notes2.json");
var store2 = new NotesStore(deepNotesPath2);
// Act 2: Explicit Save without prior Add should ensure directory and not throw
store2.Save();
Assert.True(File.Exists(deepNotesPath2));
// Act 3: Delete should ensure directory and persist
store.Delete(id1);
Assert.Empty(store.List());
var reloaded = new NotesStore(deepNotesPath);
Assert.Empty(reloaded.List());
}
[Fact]
public void Gate703_TrackerCounter_And_PermissionGate_Handle_Malformed_Urls_And_Null_Safely()
{
// Arrange
var counter = new TrackerCounter();
var gate = new PermissionGate();
// Act & Assert 1: TrackerCounter should not throw UriFormatException on malformed or relative URLs
var initialTotal = counter.Total;
counter.Record("https://google-analytics.com/collect");
Assert.Equal(initialTotal + 1, counter.Total);
Assert.True(counter.ByDomain.ContainsKey("google-analytics.com"));
// Malformed URLs must be safely handled without throwing uncaught exceptions
counter.Record("malformed-not-an-url");
counter.Record("");
counter.Record("javascript:void(0)");
// Act & Assert 2: PermissionGate handles null or whitespace gracefully
var verdict1 = gate.Decide("Camera", "");
Assert.Equal(PermissionVerdict.Ask, verdict1);
var verdict2 = gate.Decide("", "https://example.com");
Assert.Equal(PermissionVerdict.Ask, verdict2);
gate.ResetOrigin(null!);
var allOrigins = gate.GetAllOrigins();
Assert.NotNull(allOrigins);
}
}