From 93196449adfb5e887a1b5e3a00afa69abe76a0e2 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sun, 18 Oct 2026 08:25:28 +0800 Subject: [PATCH] test(javascript): verify endpoint certainty through CLI and MCP Exercise Map omission, escaped prototypes, opaque eval and candidate Evidence through the rebuilt CLI worker and public MCP transport. Preserve exact static fetch evidence and SQLite certainty controls. Co-authored-by: abdooo2235 --- .../javascriptEndpointCandidateCli.test.ts | 83 +++++++++++++++++- .../mcp/javascriptEndpointCandidate.test.ts | 84 +++++++++++++++++++ 2 files changed, 166 insertions(+), 1 deletion(-) create mode 100644 tests/boundary/mcp/javascriptEndpointCandidate.test.ts diff --git a/tests/acceptance/applications/javascriptEndpointCandidateCli.test.ts b/tests/acceptance/applications/javascriptEndpointCandidateCli.test.ts index 77823fc545..dd09dca96e 100644 --- a/tests/acceptance/applications/javascriptEndpointCandidateCli.test.ts +++ b/tests/acceptance/applications/javascriptEndpointCandidateCli.test.ts @@ -20,6 +20,14 @@ it("reports a candidate endpoint through the worker boundary in the public CLI", import * as vscode from "vscode"; client.post("session"); (client.get as any)("/api/items"); + const records = new Map([["/accounts", { name: "Alice" }]]); + console.log(records.get("/accounts")); + function inspect() { + const records = new Map(); + const alias = records; + return alias.get("/local-accounts"); + } + fetch("/network"); (new Headers().get as any)("content-type"); (new URLSearchParams() as any).delete("q"); ((vscode.workspace.getConfiguration("editor") as any).get as any)("tabSize"); @@ -32,6 +40,33 @@ it("reports a candidate endpoint through the worker boundary in the public CLI", (cache.open as any)("fixture-cache"); `, ); + for (const [name, source] of Object.entries({ + "escaped.mjs": `const box = {}; + box.prototype = Map.prototype; + function patch(b) { b.prototype.get = fetch; } + patch(box); new Map().get("/escaped");`, + "returned.mjs": + 'patch((() => Map.prototype)()); new Map().get("/returned");', + "opaque.mjs": + 'eval("Map.prototype.get = fetch"); new Map().get("/opaque");', + "private.mjs": + 'const box = {}; box.prototype = Map.prototype; new Map().get("/private");', + "returned-write.mjs": + 'const prototype = (() => Map.prototype)(); prototype.get = fetch; new Map().get("/returned-write");', + "prototype.mjs": + 'Object.getPrototypeOf(new Map()).get = fetch; new Map().get("/prototype");', + "indirect.mjs": + 'globalThis.eval("Map.prototype.get = fetch"); new Map().get("/indirect");', + "late-link.mjs": + 'function store() { box.child.prototype = Map.prototype; } const box = {}; const inner = {}; box.child = inner; store(); patch(inner); new Map().get("/late-link");', + "constructor.mjs": + 'new Map().constructor.prototype.get = fetch; new Map().get("/constructor");', + "getter.mjs": + 'const holder = { get prototype() { return Map.prototype; } }; holder.prototype.get = fetch; new Map().get("/getter");', + "assignment.mjs": + 'let run; (run = eval)("Map.prototype.get = fetch"); new Map().get("/assignment");', + })) + await writeFile(join(root, name), source); const { stdout } = await execute( process.execPath, ["scripts/rea.mjs", "analyze-javascript-application", root, "--json"], @@ -49,7 +84,53 @@ it("reports a candidate endpoint through the worker boundary in the public CLI", ) .sort(), ) - .toEqual(["/api/items", "session"]); + .toEqual([ + "/api/items", + "/assignment", + "/constructor", + "/escaped", + "/getter", + "/indirect", + "/late-link", + "/network", + "/opaque", + "/prototype", + "/returned", + "/returned-write", + "session", + ]); + for (const value of [ + "/api/items", + "/assignment", + "/constructor", + "/escaped", + "/getter", + "/indirect", + "/late-link", + "/opaque", + "/prototype", + "/returned", + "/returned-write", + ]) { + const escaped = result.graph.nodes.find( + ({ kind, observations }) => + kind === "endpoint" && + observations.some(({ properties }) => properties.value === value), + ); + expect(escaped?.observations[0]).toMatchObject({ + properties: { interpretation: "candidate" }, + evidence: { state: "inferred", confidence: "low" }, + }); + } + const request = result.graph.nodes.find( + ({ kind, observations }) => + kind === "endpoint" && + observations.some(({ properties }) => properties.value === "/network"), + ); + expect(request?.observations[0]?.evidence).toMatchObject({ + state: "observed", + confidence: "exact", + }); expect( result.graph.nodes .filter(({ kind }) => kind === "storage") diff --git a/tests/boundary/mcp/javascriptEndpointCandidate.test.ts b/tests/boundary/mcp/javascriptEndpointCandidate.test.ts new file mode 100644 index 0000000000..6c315cf0b7 --- /dev/null +++ b/tests/boundary/mcp/javascriptEndpointCandidate.test.ts @@ -0,0 +1,84 @@ +import { Client } from "@modelcontextprotocol/client"; +import { StdioClientTransport } from "@modelcontextprotocol/client/stdio"; +import { writeFile } from "node:fs/promises"; +import { join, resolve } from "node:path"; +import { expect, it } from "vitest"; + +import { parseEvidence } from "../../../src/domain/evidence.js"; +import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js"; +import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js"; + +it("preserves Map omission and uncertain endpoint Evidence through public MCP", async () => { + const root = await createTestTempDirectory("rea-map-endpoint-mcp-"); + await writeFile( + join(root, "app.mjs"), + ` + const records = new Map(); + const alias = records; + alias.get('/accounts'); + records.delete('/accounts'); + client.get('/uncertain'); + fetch('/network'); + `, + ); + const client = new Client({ name: "map-endpoint-regression", version: "1" }); + const transport = new StdioClientTransport({ + command: process.execPath, + args: [resolve("scripts/rea.mjs"), "mcp"], + cwd: process.cwd(), + env: { PATH: process.env.PATH ?? "" }, + stderr: "pipe", + }); + try { + await client.connect(transport); + const output = await client.callTool({ + name: "analyze_javascript_application", + arguments: { input_path: root }, + }); + expect(output.isError).not.toBe(true); + const evidence = parseEvidence(output.structuredContent); + const text = output.content.find((block) => block.type === "text"); + expect(text?.type === "text" ? JSON.parse(text.text) : undefined).toEqual( + evidence, + ); + const result = javascriptApplicationAnalysisResultSchema.parse( + evidence.normalized_result, + ); + const endpoints = result.graph.nodes.filter( + ({ kind }) => kind === "endpoint", + ); + expect( + endpoints + .flatMap(({ observations }) => + observations.map(({ properties }) => properties.value), + ) + .sort(), + ).toEqual(["/network", "/uncertain"]); + const candidate = endpoints.find( + ({ observations }) => observations[0]?.properties.value === "/uncertain", + ); + expect(candidate?.observations[0]).toMatchObject({ + properties: { interpretation: "candidate" }, + evidence: { + state: "inferred", + confidence: "low", + limitations: expect.arrayContaining([ + expect.stringContaining("only a candidate"), + ]), + }, + }); + expect( + result.graph.edges.find( + ({ target_node_id }) => target_node_id === candidate?.node_id, + )?.evidence, + ).toMatchObject({ state: "inferred", confidence: "low" }); + expect( + endpoints.find( + ({ observations }) => observations[0]?.properties.value === "/network", + )?.observations[0]?.evidence, + ).toMatchObject({ state: "observed", confidence: "exact" }); + } finally { + await client.close(); + await transport.close(); + } +}, 20_000);