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()); } [Fact] public void VisibleMissingImageCannotBeHiddenByClippedOrOffscreenTelemetry() { var page = new BrowserService(new GeometryFixture("
")); try { page.Navigate("https://example.test/"); page.Layout(new(800, 500)); using var proof = JsonDocument.Parse(page.ReadRemotePageProof(20)); var missing = Assert.Single(proof.RootElement.GetProperty("MissingVisibleImages").EnumerateArray()); Assert.EndsWith("/missing.png", missing.GetProperty("Source").GetString(), StringComparison.Ordinal); Assert.True(proof.RootElement.GetProperty("DocumentHeight").GetDouble() >= 900); } finally { page.Release(); } } [Fact] public void CitationIdentityAndAffineSamplesComeFromTheActualLayout() { var page = new BrowserService(new GeometryFixture("
")); try { page.Navigate("https://example.test/"); page.Layout(new(800, 500)); long passes = page.LayoutPassCount, revision = page.Revision; using var proof = JsonDocument.Parse(page.ReadRemotePageProof(21)); var value = proof.RootElement; var cite = Assert.Single(value.GetProperty("TextRuns").EnumerateArray(), item => item.GetProperty("Text").GetString() == "citation"); var heading = Assert.Single(value.GetProperty("TextRuns").EnumerateArray(), item => item.GetProperty("Text").GetString() == "heading"); Assert.True(cite.GetProperty("ListItemId").GetInt32() > 0); Assert.Equal(cite.GetProperty("ListItemId").GetInt32(), heading.GetProperty("ListItemId").GetInt32()); Assert.Contains("cite|li", cite.GetProperty("Ancestors").GetString(), StringComparison.Ordinal); var arrow = Assert.Single(value.GetProperty("LayoutSamples").EnumerateArray(), item => item.GetProperty("Class").GetString() == "arrow"); Assert.InRange(arrow.GetProperty("Bounds").GetProperty("Width").GetDouble(), 14.14, 14.15); Assert.InRange(arrow.GetProperty("Transform")[0].GetDouble(), .176, .177); Assert.Equal(passes, page.LayoutPassCount); Assert.Equal(revision, page.Revision); } finally { page.Release(); } } [Fact] public void NavigationReadbackUsesPaintCommandsAndRetainsClippedLinksAsUnpainted() { var page = new BrowserService(new GeometryFixture("")); try { page.Navigate("https://example.test/"); page.Layout(new(800, 500)); using var proof = JsonDocument.Parse(page.ReadRemotePageProof(22)); var links = proof.RootElement.GetProperty("NavigationLinks").EnumerateArray().ToArray(); Assert.Equal(3, links.Length); Assert.True(links[0].GetProperty("Painted").GetBoolean()); Assert.False(links[2].GetProperty("Painted").GetBoolean()); Assert.True(links[0].GetProperty("MenuId").GetInt32() > 0); } finally { page.Release(); } } private sealed class GeometryFixture(string body) : IWebTransport { public WebResponse Get(Uri address, BrowserLimits limits) => address.AbsolutePath == "/" ? new(address, 200, "text/html", "Geometry" + body + "", true, BodySha256: Hash) : new(address, 404, "text/plain", "Missing", true); } 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", "Remote fixture

Actual remote text

", verified, BodySha256: hash); } }