Files
rea/tests/acceptance/browser/webBundleSchemaCoverageCliMcp.test.ts
T
morluto d23be5c089 fix(web): preserve static WebMCP schema property coverage
Reuse the shared object-member owner for literal spreads, retain known property names under unresolved membership, and distinguish complete, partial, unknown, and absent projections. Keep historical coverage omission unchanged and preserve own keys across prototype setters.

Refs #5075
2026-10-21 03:56:35 +08:00

134 lines
4.2 KiB
TypeScript

import { expect } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { parseEvidence } from "../../../src/domain/evidence.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { startFakeCdpBrowser } from "../../fixtures/fakeCdpBrowser.js";
import { cliTest } from "../../support/cli/cliFixture.js";
const capturedSource = `
const dynamic = globalThis.options;
document.modelContext.registerTool({
name: "spread",
inputSchema: { properties: { ...{ query: {} }, direct: {} } }
});
document.modelContext.registerTool({
name: "partial",
inputSchema: { properties: { known: {}, ...dynamic } }
});
document.modelContext.registerTool({
name: "empty-status", description: "same",
inputSchema: { properties: dynamic }
});
document.modelContext.registerTool({
name: "empty-status", description: "same",
inputSchema: { properties: {} }
});
document.modelContext.registerTool({
name: "own-after-prototype-setter",
inputSchema: {
properties: { own: {} },
__proto__: { properties: { inherited: {} } }
}
});
`;
const inspectDeclarations = (document: unknown) => {
const parsed =
toolContract("analyze_web_bundle").outputSchema.parse(document);
const parsedEvidence = parseEvidence(parsed);
return {
evidence: parsedEvidence,
declarations: parsed.normalized_result.observations.webmcp_declarations,
};
};
const expectCoverageCases = (
declarations: ReturnType<typeof inspectDeclarations>["declarations"],
) => {
expect(declarations).toEqual(
expect.arrayContaining([
expect.objectContaining({
name: "spread",
schema_property_names: ["direct", "query"],
schema_property_names_coverage: "complete",
}),
expect.objectContaining({
name: "partial",
schema_property_names: ["known"],
schema_property_names_coverage: "partial",
}),
expect.objectContaining({
name: "empty-status",
description: "same",
schema_property_names: [],
schema_property_names_coverage: "unknown",
}),
expect.objectContaining({
name: "empty-status",
description: "same",
schema_property_names: [],
schema_property_names_coverage: "complete",
}),
expect.objectContaining({
name: "own-after-prototype-setter",
schema_property_names: ["own"],
schema_property_names_coverage: "complete",
}),
]),
);
expect(
declarations.filter(({ name }) => name === "empty-status"),
).toHaveLength(2);
};
cliTest(
"preserves WebMCP schema-name coverage through compiled CLI and stdio MCP",
async ({ cli }) => {
const browser = await startFakeCdpBrowser({
commandResult: ({ method }) =>
method === "Debugger.getScriptSource"
? { scriptSource: capturedSource }
: undefined,
});
try {
const contract = toolContract("analyze_web_bundle");
const cliReply = await cli.run({
arguments: [
"analyze-web-bundle",
browser.endpoint,
"allowed-page",
"--allowed-origins",
browser.allowedOrigin,
"--observation-ms",
"0",
"--json",
],
});
expect(cliReply.exitCode, cliReply.stdout + cliReply.stderr).toBe(0);
const cliAnalysis = inspectDeclarations(cliReply.json);
expectCoverageCases(cliAnalysis.declarations);
const { call } = await connectLocalToolsMcp();
const input = {
cdp_endpoint: browser.endpoint,
allowed_origins: [browser.allowedOrigin],
target_id: "allowed-page",
observation_ms: 0,
include_script_sources: true,
fetch_source_maps: false,
};
const response = await call(contract.name, input);
expect(response.isError, JSON.stringify(response)).not.toBe(true);
const mcpAnalysis = inspectDeclarations(response.structuredContent);
expectCoverageCases(mcpAnalysis.declarations);
expect(mcpAnalysis.evidence.operation).toBe("analyze_web_bundle");
expect(mcpAnalysis.evidence.authority).toBe("external-service");
expect(mcpAnalysis.declarations).toEqual(cliAnalysis.declarations);
} finally {
await browser.close();
}
},
120_000,
);