mirror of
https://github.com/morluto/rea.git
synced 2026-10-11 21:39:55 +00:00
Analyze exact selected sources in first-request order before unrelated static scopes, preserving full artifact identity and byte verification. Retain selection in CLI/MCP Evidence and summaries, with explicit unknowns and partial coverage for unselected sources. Keep default full analysis. Fixes #1848 Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> Co-authored-by: Kaoru0822 <[email protected]>
148 lines
4.9 KiB
TypeScript
148 lines
4.9 KiB
TypeScript
import { createPackageWithOptions } from "@electron/asar";
|
|
import { createHash } from "node:crypto";
|
|
import { readFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
|
import { writeJavaScriptSourceSelectionFixture } from "../../fixtures/javascriptSourceSelection.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
cliTest.for([
|
|
{ format: "directory", command: "analyze" },
|
|
{ format: "asar", command: "analyze-javascript-application" },
|
|
])(
|
|
"analyzes selected $format sources through $command with complete artifact identity",
|
|
{ timeout: 60_000 },
|
|
async ({ format, command }, { cli }) => {
|
|
const directory = await createTestTempDirectory("rea-js-source-selection-");
|
|
const files = await writeJavaScriptSourceSelectionFixture(directory);
|
|
let input = directory;
|
|
if (format === "asar") {
|
|
input = join(
|
|
await createTestTempDirectory("rea-js-selected-asar-"),
|
|
"app.asar",
|
|
);
|
|
await createPackageWithOptions(directory, input, {});
|
|
}
|
|
const requested = ["main.mjs", "package.json", "main.mjs"];
|
|
const response = await cli.run({
|
|
arguments: [
|
|
command,
|
|
input,
|
|
...requested.flatMap((path) => ["--source-path", path]),
|
|
"--format",
|
|
"json",
|
|
],
|
|
});
|
|
expect(response.exitCode).toBe(0);
|
|
const evidence = parseEvidence(response.json);
|
|
const result = javascriptApplicationAnalysisResultSchema.parse(
|
|
evidence.normalized_result,
|
|
);
|
|
expect(result.source_selection).toEqual({
|
|
requested_paths: requested,
|
|
unselected_files: 4,
|
|
});
|
|
expect(evidence.parameters.source_paths).toEqual(requested);
|
|
expect(result.statistics).toMatchObject({
|
|
relevant_files: 6,
|
|
parsed_javascript_files: 1,
|
|
invalid_utf8_files: 0,
|
|
parse_failures: 0,
|
|
text_bytes_read:
|
|
files.get("main.mjs")!.length + files.get("package.json")!.length,
|
|
});
|
|
expect(result.graph.coverage.status).toBe("partial");
|
|
expect(result.semantic_graph.coverage).toMatchObject({
|
|
status: "partial",
|
|
truncated: false,
|
|
});
|
|
expect(
|
|
result.semantic_graph.nodes.some(
|
|
({ kind, label }) => kind === "function" && label === "selected",
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
result.semantic_graph.unknowns.some(
|
|
({ reason, detail }) =>
|
|
reason === "incomplete-module" &&
|
|
detail.includes("caller-selected source_paths"),
|
|
),
|
|
).toBe(true);
|
|
for (const [path, bytes] of files) {
|
|
const properties = result.graph.nodes
|
|
.flatMap(({ observations }) =>
|
|
observations.map(({ properties }) => properties),
|
|
)
|
|
.find(
|
|
(properties) =>
|
|
properties.path === path && properties.text_status !== undefined,
|
|
);
|
|
expect(properties?.text_status).toBe(
|
|
requested.includes(path) ? "included" : "outside-analysis-scope",
|
|
);
|
|
expect(properties?.parse_status).toBe(
|
|
path === "main.mjs" ? "complete" : null,
|
|
);
|
|
expect(await readFile(join(directory, path))).toEqual(bytes);
|
|
}
|
|
const fullResponse = await cli.run({
|
|
arguments: [command, input, "--format", "json"],
|
|
});
|
|
expect(fullResponse.exitCode).toBe(0);
|
|
const full = javascriptApplicationAnalysisResultSchema.parse(
|
|
parseEvidence(fullResponse.json).normalized_result,
|
|
);
|
|
expect(full.source_selection).toBeUndefined();
|
|
expect(full.statistics.invalid_utf8_files).toBe(1);
|
|
expect(full.statistics.parse_failures).toBeGreaterThan(0);
|
|
expect([
|
|
result.root_artifact_sha256,
|
|
result.inventory_manifest_id,
|
|
result.inventory_graph_sha256,
|
|
]).toEqual([
|
|
full.root_artifact_sha256,
|
|
full.inventory_manifest_id,
|
|
full.inventory_graph_sha256,
|
|
]);
|
|
if (format === "asar")
|
|
expect(result.root_artifact_sha256).toBe(
|
|
createHash("sha256")
|
|
.update(await readFile(input))
|
|
.digest("hex"),
|
|
);
|
|
},
|
|
);
|
|
|
|
cliTest(
|
|
"rejects source selection when an explicit provider chooses the native lane",
|
|
async ({ cli }) => {
|
|
const directory = await createTestTempDirectory(
|
|
"rea-js-source-native-refusal-",
|
|
);
|
|
await writeJavaScriptSourceSelectionFixture(directory);
|
|
const response = await cli.run({
|
|
arguments: [
|
|
"analyze",
|
|
directory,
|
|
"--provider",
|
|
"ghidra",
|
|
"--source-path",
|
|
"main.mjs",
|
|
"--format",
|
|
"json",
|
|
],
|
|
});
|
|
expect(response.exitCode).toBe(1);
|
|
expect(response.json).toMatchObject({
|
|
code: "invalid_request",
|
|
details: {
|
|
issues: [{ path: ["source_paths"], reason: "invalid_value" }],
|
|
},
|
|
});
|
|
},
|
|
);
|