Files
rea/tests/fixtures/javascriptReturnShapes.ts
Ysopwnable e09cfe9b59 fix(javascript): invalidate references stored through property targets (#1445)
* fix(javascript): invalidate references stored through property targets

* refactor(javascript): share rest keys and return-shape fixtures
2026-10-10 13:47:15 +08:00

92 lines
3.6 KiB
TypeScript

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 } from "vitest";
import { toolContract } from "../../src/contracts/toolContracts.js";
import { javascriptApplicationAnalysisResultSchema } from "../../src/domain/javascript/javascriptApplicationAnalysis.js";
import { projectedExportReturnShapesSchema } from "../../src/domain/javascript/javascriptExportShapeComparisonSchemas.js";
import { createTestTempDirectory } from "./temporaryDirectory.js";
import type { TestCli } from "../support/cli/cliFixture.js";
const exportedReturnFields = (value: unknown) => {
const analysis = javascriptApplicationAnalysisResultSchema.parse(value);
const projections = analysis.graph.nodes
.flatMap(({ observations }) => observations)
.filter(
({ properties }) => properties.semantic_role === "export-return-shapes",
)
.map(({ properties }) =>
projectedExportReturnShapesSchema.parse(properties),
);
return (name: string) => {
const projection = projections.find(
({ exported_name }) => exported_name === name,
);
if (projection === undefined) throw new Error(`Missing export ${name}`);
expect(projection.return_shape_coverage).toMatchObject({
status: "complete",
projection_complete: true,
});
return projection.static_return_shapes.flatMap(({ fields }) => fields);
};
};
/** Select complete return fields from a real JavaScript application result. */
export type JavaScriptReturnFields = ReturnType<typeof exportedReturnFields>;
/** Exercise identical return assertions through the compiled CLI and stdio MCP. */
export const verifyJavaScriptReturnShapes = async (
cli: TestCli,
source: string,
check: (fields: JavaScriptReturnFields) => void,
): Promise<void> => {
const root = await createTestTempDirectory("rea-return-shapes-");
await writeFile(join(root, "app.js"), source);
const cliResponse = await cli.run({
arguments: [
"analyze-javascript-application",
root,
"--artifact-format",
"directory",
"--json",
],
});
expect(cliResponse.exitCode).toBe(0);
const contract = toolContract("analyze_javascript_application");
const cliEvidence = contract.outputSchema.parse(cliResponse.json);
check(exportedReturnFields(cliEvidence.normalized_result));
const client = new Client({ name: "return-shapes-e2e", 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",
});
transport.stderr?.on("data", () => undefined);
try {
await client.connect(transport);
const response = await client.callTool({
name: "analyze_javascript_application",
arguments: { input_path: root, format: "directory" },
});
expect(response.isError).not.toBe(true);
const evidence = contract.outputSchema.parse(response.structuredContent);
expect(evidence.evidence_id).toBe(cliEvidence.evidence_id);
check(exportedReturnFields(evidence.normalized_result));
const text = response.content.find((item) => item.type === "text");
if (text?.type !== "text") throw new Error("Missing MCP text result");
check(
exportedReturnFields(
contract.outputSchema.parse(JSON.parse(text.text)).normalized_result,
),
);
await client.ping();
} finally {
await client.close();
await transport.close();
}
};