Files
rea/tests/boundary/browser/cdpResourceCompleteness.test.ts
af6845cddb test(browser): preserve resource uncertainty through MCP (#3416)
Preserve resource uncertainty through production MCP registration and the real SDK. Test surviving resource fields/exclusions, forward/reverse/self comparisons, and complete-empty controls. Production behavior is unchanged.

Baron approved the unchanged head 07ea877a388ee2cd0586f04b1fb75bd104b484be; current required CI passes.

Original contribution by @ShuaixinHuang.

Co-authored-by: ShuaixinHuang <1836656514@qq.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
2026-10-16 01:01:18 +08:00

470 lines
15 KiB
TypeScript

import { writeFile } from "node:fs/promises";
import { join } from "node:path";
import { describe, expect, it, onTestFinished } from "vitest";
import { CdpBrowserProvider } from "../../../src/browser/CdpBrowserProvider.js";
import { inspectWebPageInputSchema } from "../../../src/domain/browserObservation.js";
import {
webPageInspectionSchema,
type WebPageInspection,
} from "../../../src/domain/browserObservationSchemas.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { compareWebCaptures } from "../../../src/domain/webCaptureDiff.js";
import { compareWebCapturesInputSchema } from "../../../src/domain/webCaptureDiffSchemas.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { runCliJson } from "../../fixtures/cliJsonProcess.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { trackBrowser } from "./cdpBrowserProvider.support.js";
import { createApplicationMcpHarness } from "../../fixtures/applicationMcpHarness.js";
type ResourceMode =
| "valid"
| "missing"
| "null"
| "wrong-type"
| "invalid-entry"
| "child-missing"
| "child-invalid-entry"
| "child-wrong-type"
| "empty";
const startBrowser = async () => {
const state = {
mode: "valid" as ResourceMode,
peerFields: undefined as Record<string, unknown> | undefined,
};
const browser = await startFakeCdpBrowser({
commandResult(command, origin) {
const malformedChildren =
state.mode === "child-invalid-entry" ||
state.mode === "child-wrong-type";
if (command.method === "Page.getFrameTree" && malformedChildren)
return {
frameTree: {
frame: { id: "frame-main", url: origin + "/app" },
childFrames:
state.mode === "child-wrong-type"
? {}
: [
{
frame: {
id: "child",
parentId: "frame-main",
url: origin + "/child",
},
},
null,
],
},
};
if (command.method !== "Page.getResourceTree") return undefined;
const resource = (name: string) => ({
url: origin + "/" + name,
type: "Script",
mimeType: "text/javascript",
contentSize: 128,
...(name === "peer.js" ? state.peerFields : undefined),
});
let resources: unknown = [resource("keep.js"), resource("peer.js")];
if (state.mode === "missing") resources = undefined;
if (state.mode === "null") resources = null;
if (state.mode === "wrong-type") resources = {};
if (state.mode === "invalid-entry")
resources = [resource("keep.js"), null];
if (state.mode === "empty") resources = [];
const malformedChildFrames =
state.mode === "child-wrong-type"
? {}
: state.mode === "child-invalid-entry"
? [
{
frame: {
id: "child",
parentId: "frame-main",
url: origin + "/child",
},
resources: [resource("child.js")],
},
null,
]
: undefined;
return {
frameTree: {
frame: { id: "frame-main", url: origin + "/app" },
resources,
...(malformedChildFrames === undefined
? {}
: { childFrames: malformedChildFrames }),
...(state.mode === "child-missing"
? {
childFrames: [
{
frame: {
id: "child",
parentId: "frame-main",
url: origin + "/child",
},
},
],
}
: {}),
},
};
},
});
trackBrowser(browser);
const inspect = async () => {
const result = await new CdpBrowserProvider().inspectPage(
inspectWebPageInputSchema.parse({
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
}),
);
if (!result.ok) throw result.error;
return result.value;
};
return {
state,
inspect,
origin: browser.allowedOrigin,
endpoint: browser.endpoint,
};
};
const compareResources = (
before: WebPageInspection,
after: WebPageInspection,
) =>
compareWebCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: before },
after: { inspection: after },
}),
).dimensions.resources;
describe("resource inventory omissions through CDP", () => {
it.each([
{ fields: { type: undefined }, retained: false },
{ fields: { type: null }, retained: false },
{ fields: { type: 17 }, retained: false },
{ fields: { type: "" }, retained: false },
{ fields: { mimeType: undefined }, retained: false },
{ fields: { mimeType: null }, retained: false },
{ fields: { mimeType: 17 }, retained: false },
{ fields: { contentSize: -1 }, retained: true },
{ fields: { contentSize: null }, retained: true },
{ fields: { contentSize: "128" }, retained: true },
])(
"keeps malformed resource fields uncertain: $fields",
async ({ fields, retained }) => {
const { state, inspect, origin } = await startBrowser();
const before = await inspect();
state.peerFields = fields;
const after = await inspect();
for (const [left, right] of [
[before, after],
[after, before],
[after, after],
] as const)
expect(compareResources(left, right)).toMatchObject({
status: "unknown",
total_changes: 0,
changes: [],
});
expect(after.completeness.excluded).toContainEqual({
section: "resources",
reason: "invalid_protocol_value",
count: 1,
});
expect(after.resources.map(({ url }) => url).sort()).toEqual(
retained
? [origin + "/keep.js", origin + "/peer.js"]
: [origin + "/keep.js"],
);
if (retained)
expect(
after.resources.find(({ url }) => url.endsWith("/peer.js")),
).toMatchObject({
type: "Script",
mime_type: "text/javascript",
content_size: null,
});
},
);
it.each([
{
fields: { type: "Other", mimeType: "", contentSize: undefined },
size: null,
},
{ fields: { contentSize: 0 }, size: 0 },
{ fields: { contentSize: 1.5 }, size: 1.5 },
])(
"accepts valid optional and empty fields: $fields",
async ({ fields, size }) => {
const { state, inspect } = await startBrowser();
state.peerFields = fields;
const capture = await inspect();
expect(capture.resources).toHaveLength(2);
expect(
capture.resources.find(({ url }) => url.endsWith("/peer.js"))
?.content_size,
).toBe(size);
expect(capture.completeness.unavailable_sections).not.toContain(
"resources",
);
expect(compareResources(capture, capture).status).toBe("unchanged");
},
);
it.each([
"missing",
"null",
"wrong-type",
"invalid-entry",
"child-missing",
"child-invalid-entry",
"child-wrong-type",
] as const)(
"does not claim a resource removal or equality for %s",
async (mode) => {
const { state, inspect, origin } = await startBrowser();
const before = await inspect();
expect(before.resources).toHaveLength(2);
expect(before.completeness.unavailable_sections).not.toContain(
"resources",
);
state.mode = mode;
const after = await inspect();
for (const [left, right] of [
[before, after],
[after, before],
[after, after],
] as const)
expect(compareResources(left, right)).toMatchObject({
status: "unknown",
total_changes: 0,
changes: [],
});
expect(after.completeness.unavailable_sections).toContain("resources");
expect(after.completeness.excluded).toContainEqual({
section: "resources",
reason: "invalid_protocol_value",
count: mode === "invalid-entry" ? 1 : null,
});
if (mode === "child-invalid-entry" || mode === "child-wrong-type") {
expect(after.completeness.excluded).toContainEqual({
section: "frames",
reason: "invalid_protocol_value",
count: null,
});
expect(after.frames.map(({ frame_id }) => frame_id)).toContain(
"frame-main",
);
if (mode === "child-invalid-entry")
expect(after.frames.map(({ frame_id }) => frame_id)).toContain(
"child",
);
}
const expected =
mode === "invalid-entry"
? [origin + "/keep.js"]
: mode === "child-invalid-entry"
? [origin + "/child.js", origin + "/keep.js", origin + "/peer.js"]
: mode === "child-missing"
? [origin + "/keep.js", origin + "/peer.js"]
: mode === "child-wrong-type"
? [origin + "/keep.js", origin + "/peer.js"]
: [];
expect(after.resources.map(({ url }) => url).sort()).toEqual(expected);
},
);
it("distinguishes a complete empty inventory from an unavailable collection", async () => {
const { state, inspect } = await startBrowser();
const before = await inspect();
state.mode = "empty";
const after = await inspect();
expect(after.resources).toEqual([]);
expect(after.completeness.unavailable_sections).not.toContain("resources");
expect(compareResources(before, after)).toMatchObject({
status: "changed",
total_changes: 2,
changes: [{ change: "removed" }, { change: "removed" }],
});
expect(compareResources(after, after).status).toBe("unchanged");
});
});
describe("resource uncertainty in production MCP registration", () => {
it("delivers partial resources and preserves comparison uncertainty through the SDK", async () => {
const { state, origin, endpoint } = await startBrowser();
const harness = await createApplicationMcpHarness({
serverOptions: { browserObservation: new CdpBrowserProvider() },
});
onTestFinished(harness.close);
const capture = async () => {
const reply = await harness.client.callTool({
name: "inspect_web_page",
arguments: {
cdp_endpoint: endpoint,
allowed_origins: [origin],
target_id: "allowed-page",
observation_ms: 0,
},
});
expect(reply.isError).not.toBe(true);
expect(reply.structuredContent).toMatchObject({
operation: "inspect_web_page",
});
return webPageInspectionSchema.parse(
parseEvidence(reply.structuredContent).normalized_result,
);
};
const expectedResource = (
name: string,
contentSize: number | null = 128,
) => ({
resource_key: expect.any(String),
url: `${origin}/${name}`,
origin,
type: "Script",
mime_type: "text/javascript",
content_size: contentSize,
});
const resourceExclusions = (inspection: WebPageInspection) =>
inspection.completeness.excluded.filter(
({ section }) => section === "resources",
);
const before = await capture();
expect(
before.resources.toSorted((left, right) =>
left.url.localeCompare(right.url),
),
).toEqual([expectedResource("keep.js"), expectedResource("peer.js")]);
expect(before.completeness.unavailable_sections).not.toContain("resources");
expect(resourceExclusions(before)).toEqual([]);
for (const { mode, fields, resources, excludedCount } of [
{
mode: "missing",
fields: undefined,
resources: [],
excludedCount: null,
},
{
mode: "invalid-entry",
fields: undefined,
resources: [expectedResource("keep.js")],
excludedCount: 1,
},
{
mode: "valid",
fields: { type: undefined },
resources: [expectedResource("keep.js")],
excludedCount: 1,
},
{
mode: "valid",
fields: { contentSize: -1 },
resources: [
expectedResource("keep.js"),
expectedResource("peer.js", null),
],
excludedCount: 1,
},
{ mode: "empty", fields: undefined, resources: [], excludedCount: 0 },
] as const) {
state.mode = mode;
state.peerFields = fields;
const after = await capture();
expect(
after.resources.toSorted((left, right) =>
left.url.localeCompare(right.url),
),
).toEqual(resources);
expect(resourceExclusions(after)).toEqual(
mode === "empty"
? []
: [
{
section: "resources",
reason: "invalid_protocol_value",
count: excludedCount,
},
],
);
if (mode !== "empty")
expect(after.completeness.unavailable_sections).toContain("resources");
else
expect(after.completeness.unavailable_sections).not.toContain(
"resources",
);
for (const [left, right] of [
[before, after],
[after, before],
[after, after],
] as const) {
const reply = await harness.client.callTool({
name: "compare_web_captures",
arguments: {
before: { inspection: left },
after: { inspection: right },
},
});
expect(reply.isError).not.toBe(true);
expect(reply.structuredContent).toMatchObject({
operation: "compare_web_captures",
normalized_result: {
dimensions: {
resources: {
status:
mode !== "empty"
? "unknown"
: left === right
? "unchanged"
: "changed",
total_changes: mode === "empty" && left !== right ? 2 : 0,
},
},
},
});
}
}
});
});
describe("resource uncertainty in public CLI Evidence", () => {
it("preserves partial membership while complete inventories still prove removal", async () => {
const { state, inspect } = await startBrowser();
const root = await createTestTempDirectory("rea-resource-comparison-");
const before = join(root, "before.json");
const after = join(root, "after.json");
await writeFile(before, JSON.stringify({ inspection: await inspect() }));
for (const { mode, fields } of [
{ mode: "invalid-entry", fields: undefined },
{ mode: "valid", fields: { type: undefined } },
{ mode: "valid", fields: { contentSize: -1 } },
{ mode: "empty", fields: undefined },
] as const) {
state.mode = mode;
state.peerFields = fields;
await writeFile(after, JSON.stringify({ inspection: await inspect() }));
expect(
await runCliJson(["compare-web-captures", before, after, "--json"]),
).toMatchObject({
operation: "compare_web_captures",
normalized_result: {
dimensions: {
resources: {
status: mode === "empty" ? "changed" : "unknown",
total_changes: mode === "empty" ? 2 : 0,
},
},
},
});
}
});
});