Files
rea/tests/acceptance/applications/javascriptFunctionFingerprintsCli.test.ts
T
morlutoandTo3akaRin 6396851958 fix(javascript): retain directive values in function fingerprints
Retain directive literal values, including escaped strict-mode lookalikes, in semantic function fingerprints. Verify normalized duplicates and distinct directives through CLI/MCP.

Co-authored-by: To3akaRin <131595336+To3akaRin@users.noreply.github.com>
2026-10-14 15:20:05 +08:00

128 lines
4.9 KiB
TypeScript

import { mkdir, writeFile } from "node:fs/promises";
import { join } from "node:path";
import { expect } from "vitest";
import { toolContract } from "../../../src/contracts/toolContracts.js";
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { javaScriptSemanticTraceResultSchema } from "../../../src/domain/javascript/javascriptSemanticTraceSchemas.js";
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
import { cliTest } from "../../support/cli/cliFixture.js";
cliTest(
"function fingerprint seeds retain directive values and normalized duplicates through CLI and MCP",
async ({ cli }) => {
const root = await createTestTempDirectory("rea-function-fingerprints-");
const application = join(root, "application");
await mkdir(application);
await writeFile(
join(application, "app.cjs"),
String.raw`
throw new Error("This analysis fixture must not execute");
function strict(value) { "use strict"; return value; }
function ordinary(value) { "ordinary"; return value; }
function asm(value) { "use asm"; return value; }
function alpha(value) { "alpha"; return value; }
function beta(value) { "beta"; return value; }
function escaped(value) { "use\x20strict"; return value; }
function renamed(input) { 'use strict'; return input; }
`,
);
const analyzed = await cli.run({
arguments: [
"analyze-javascript-application",
application,
"--artifact-format",
"directory",
"--json",
],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(analyzed.exitCode, analyzed.stderr).toBe(0);
const evidence = toolContract(
"analyze_javascript_application",
).outputSchema.parse(analyzed.json);
const { semantic_graph: graph } =
javascriptApplicationAnalysisResultSchema.parse(
evidence.normalized_result,
);
const { client, call } = await connectLocalToolsMcp();
const response = await call("analyze_javascript_application", {
input_path: application,
format: "directory",
});
expect(response.isError).not.toBe(true);
const retained = toolContract(
"analyze_javascript_application",
).outputSchema.parse(response.structuredContent);
expect(retained.evidence_id).toBe(evidence.evidence_id);
const fingerprint = (name: string) => {
const node = graph.nodes.find(
(candidate) =>
candidate.kind === "function" && candidate.properties.name === name,
);
if (node === undefined) throw new Error(`Missing function ${name}`);
const result = graph.fingerprints.find(
({ function_node_id }) => function_node_id === node.node_id,
);
if (result === undefined) throw new Error(`Missing fingerprint ${name}`);
expect(result.status).toBe("complete");
return result;
};
for (const [selected, expectedNames] of [
["strict", ["strict", "renamed"]],
["ordinary", ["ordinary"]],
["asm", ["asm"]],
["alpha", ["alpha"]],
["beta", ["beta"]],
["escaped", ["escaped"]],
] as const) {
const query = {
seed: {
kind: "function",
fingerprint_sha256: fingerprint(selected).fingerprint_sha256,
},
direction: "forward-influence",
};
const traced = await call("trace_javascript_semantics", {
application: {
kind: "retained-evidence",
evidence_id: retained.evidence_id,
},
query,
});
expect(traced.isError, JSON.stringify(traced)).not.toBe(true);
const traceEvidence = toolContract(
"trace_javascript_semantics",
).outputSchema.parse(traced.structuredContent);
const trace = javaScriptSemanticTraceResultSchema.parse(
traceEvidence.normalized_result,
);
expect(trace.seed_node_ids).toEqual(
expectedNames.map((name) => fingerprint(name).function_node_id).sort(),
);
expect(trace.status).toBe(
expectedNames.length > 1 ? "ambiguous" : "found",
);
expect(trace.source_graph_id).toBe(graph.graph_id);
if (selected === "strict") {
const requestPath = join(root, "trace-request.json");
await writeFile(
requestPath,
JSON.stringify({ application: evidence, query }),
);
const cliTrace = await cli.run({
arguments: ["trace-javascript-semantics", requestPath, "--json"],
environment: { REA_LOG_LEVEL: "silent" },
});
expect(cliTrace.exitCode, cliTrace.stderr).toBe(0);
const cliEvidence = toolContract(
"trace_javascript_semantics",
).outputSchema.parse(cliTrace.json);
expect(cliEvidence.evidence_id).toBe(traceEvidence.evidence_id);
}
}
await client.ping();
},
);