using System.Text.Json;
namespace XFESystem.Browser.Tests;
public sealed class RemotePageProofTests
{
private const string Hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
[Fact]
public void PublishedProofBindsActualLayoutSourceAndSnapshotWithoutInvalidatingPaint()
{
var page = new BrowserService(new Fixture()); page.Navigate("https://example.test/");
page.Layout(new(800, 500)); long passes = page.LayoutPassCount, revision = page.Revision;
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(12)); var value = proof.RootElement;
Assert.Equal(Hash, value.GetProperty("BodySha256").GetString());
Assert.Equal(12, value.GetProperty("SnapshotSequence").GetInt64());
Assert.Equal("Remote fixture", value.GetProperty("Title").GetString());
Assert.Contains(value.GetProperty("TextRuns").EnumerateArray(), item => item.GetProperty("Text").GetString()!.Contains("Actual"));
Assert.Contains(value.GetProperty("SearchInputs").EnumerateArray(), item => item.GetProperty("Name").GetString() == "q" && item.GetProperty("Bounds").GetProperty("Width").GetDouble() > 0);
Assert.Single(value.GetProperty("LoadedStyles").EnumerateArray());
Assert.Single(value.GetProperty("DeclaredStyleLinks").EnumerateArray());
Assert.Equal(passes, page.LayoutPassCount); Assert.Equal(revision, page.Revision);
}
[Fact]
public void OfflineNavigationNeverReusesPreviousRemoteEvidence()
{
var page = new BrowserService(new Fixture()); page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.NotEqual("{}", page.ReadRemotePageProof());
page.Navigate("about:Start"); page.Layout(new(800, 500)); Assert.Equal("{}", page.ReadRemotePageProof());
}
[Fact]
public void MissingCertificateOrBodyEvidenceCannotBecomeRemoteProof()
{
var page = new BrowserService(new Fixture(verified: false));
Assert.Equal("Navigation failed", page.Navigate("https://example.test/").Title);
Assert.Equal("{}", page.ReadRemotePageProof());
page = new(new Fixture(hash: null)); page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.Equal("{}", page.ReadRemotePageProof());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ActualStylePaintAndProofRetainRequestedAndFinalUrls(bool redirectStyle)
{
var page = new BrowserService(new Fixture(redirectStyle: redirectStyle));
page.Navigate("https://example.test/");
var layout = page.Layout(new(800, 500));
Assert.Contains(layout.Commands, command => command.Kind == BrowserDrawKind.Text &&
command.Text.Contains("Actual", StringComparison.Ordinal) && command.Color == 0x123456);
var receipt = Assert.Single(page.LoadedResources, item => item.Kind == "style");
string final = redirectStyle ? "https://cdn.example.test/FinalStyle.css" : "https://example.test/Style.css";
Assert.Equal("https://example.test/Style.css", receipt.RequestedAddress);
Assert.Equal(final, receipt.Address);
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(14));
var style = Assert.Single(proof.RootElement.GetProperty("LoadedStyles").EnumerateArray());
Assert.Equal("https://example.test/Style.css", style.GetProperty("RequestedAddress").GetString());
Assert.Equal(final, style.GetProperty("Address").GetString());
Assert.Equal(final, style.GetProperty("FinalAddress").GetString());
Assert.Equal(style.GetProperty("RequestedAddress").GetString(),
Assert.Single(proof.RootElement.GetProperty("DeclaredStyleLinks").EnumerateArray()).GetString());
}
[Fact]
public void RedirectedStylesheetWithInvalidTlsDoesNotBecomeLoadedProof()
{
var page = new BrowserService(new Fixture(redirectStyle: true, styleVerified: false));
page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.DoesNotContain(page.LoadedResources, item => item.Kind == "style");
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(15));
Assert.Empty(proof.RootElement.GetProperty("LoadedStyles").EnumerateArray());
Assert.Single(proof.RootElement.GetProperty("DeclaredStyleLinks").EnumerateArray());
}
private sealed class Fixture(bool verified = true, string? hash = Hash, bool redirectStyle = false, bool styleVerified = true) : IWebTransport
{
public WebResponse Get(Uri address, BrowserLimits limits) => address.AbsolutePath == "/Style.css"
? new(redirectStyle ? new Uri("https://cdn.example.test/FinalStyle.css") : address, 200, "text/css", "p{color:#123456} input{width:240px;height:32px}", verified && styleVerified)
: new(address, 200, "text/html", "<html><head><title>Remote fixture</title><link rel='stylesheet' href='/Style.css'></head><body><p>Actual remote text</p><input name='q' value='Query'></body></html>", verified, BodySha256: hash);
}
}
using System.Text.Json;
namespace XFESystem.Browser.Tests;
public sealed class RemotePageProofTests
{
private const string Hash = "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef";
[Fact]
public void PublishedProofBindsActualLayoutSourceAndSnapshotWithoutInvalidatingPaint()
{
var page = new BrowserService(new Fixture()); page.Navigate("https://example.test/");
page.Layout(new(800, 500)); long passes = page.LayoutPassCount, revision = page.Revision;
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(12)); var value = proof.RootElement;
Assert.Equal(Hash, value.GetProperty("BodySha256").GetString());
Assert.Equal(12, value.GetProperty("SnapshotSequence").GetInt64());
Assert.Equal("Remote fixture", value.GetProperty("Title").GetString());
Assert.Contains(value.GetProperty("TextRuns").EnumerateArray(), item => item.GetProperty("Text").GetString()!.Contains("Actual"));
Assert.Contains(value.GetProperty("SearchInputs").EnumerateArray(), item => item.GetProperty("Name").GetString() == "q" && item.GetProperty("Bounds").GetProperty("Width").GetDouble() > 0);
Assert.Single(value.GetProperty("LoadedStyles").EnumerateArray());
Assert.Single(value.GetProperty("DeclaredStyleLinks").EnumerateArray());
Assert.Equal(passes, page.LayoutPassCount); Assert.Equal(revision, page.Revision);
}
[Fact]
public void OfflineNavigationNeverReusesPreviousRemoteEvidence()
{
var page = new BrowserService(new Fixture()); page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.NotEqual("{}", page.ReadRemotePageProof());
page.Navigate("about:Start"); page.Layout(new(800, 500)); Assert.Equal("{}", page.ReadRemotePageProof());
}
[Fact]
public void MissingCertificateOrBodyEvidenceCannotBecomeRemoteProof()
{
var page = new BrowserService(new Fixture(verified: false));
Assert.Equal("Navigation failed", page.Navigate("https://example.test/").Title);
Assert.Equal("{}", page.ReadRemotePageProof());
page = new(new Fixture(hash: null)); page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.Equal("{}", page.ReadRemotePageProof());
}
[Theory]
[InlineData(false)]
[InlineData(true)]
public void ActualStylePaintAndProofRetainRequestedAndFinalUrls(bool redirectStyle)
{
var page = new BrowserService(new Fixture(redirectStyle: redirectStyle));
page.Navigate("https://example.test/");
var layout = page.Layout(new(800, 500));
Assert.Contains(layout.Commands, command => command.Kind == BrowserDrawKind.Text &&
command.Text.Contains("Actual", StringComparison.Ordinal) && command.Color == 0x123456);
var receipt = Assert.Single(page.LoadedResources, item => item.Kind == "style");
string final = redirectStyle ? "https://cdn.example.test/FinalStyle.css" : "https://example.test/Style.css";
Assert.Equal("https://example.test/Style.css", receipt.RequestedAddress);
Assert.Equal(final, receipt.Address);
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(14));
var style = Assert.Single(proof.RootElement.GetProperty("LoadedStyles").EnumerateArray());
Assert.Equal("https://example.test/Style.css", style.GetProperty("RequestedAddress").GetString());
Assert.Equal(final, style.GetProperty("Address").GetString());
Assert.Equal(final, style.GetProperty("FinalAddress").GetString());
Assert.Equal(style.GetProperty("RequestedAddress").GetString(),
Assert.Single(proof.RootElement.GetProperty("DeclaredStyleLinks").EnumerateArray()).GetString());
}
[Fact]
public void RedirectedStylesheetWithInvalidTlsDoesNotBecomeLoadedProof()
{
var page = new BrowserService(new Fixture(redirectStyle: true, styleVerified: false));
page.Navigate("https://example.test/"); page.Layout(new(800, 500));
Assert.DoesNotContain(page.LoadedResources, item => item.Kind == "style");
using var proof = JsonDocument.Parse(page.ReadRemotePageProof(15));
Assert.Empty(proof.RootElement.GetProperty("LoadedStyles").EnumerateArray());
Assert.Single(proof.RootElement.GetProperty("DeclaredStyleLinks").EnumerateArray());
}
private sealed class Fixture(bool verified = true, string? hash = Hash, bool redirectStyle = false, bool styleVerified = true) : IWebTransport
{
public WebResponse Get(Uri address, BrowserLimits limits) => address.AbsolutePath == "/Style.css"
? new(redirectStyle ? new Uri("https://cdn.example.test/FinalStyle.css") : address, 200, "text/css", "p{color:#123456} input{width:240px;height:32px}", verified && styleVerified)
: new(address, 200, "text/html", "<html><head><title>Remote fixture</title><link rel='stylesheet' href='/Style.css'></head><body><p>Actual remote text</p><input name='q' value='Query'></body></html>", verified, BodySha256: hash);
}
}