Files
rea/tests/acceptance/applications/javascriptSourceSelectionCli.test.ts
morluto e8da7e1edd fix(artifacts): share bounded ASAR signature observations
Parse Pickle extents once at the ASAR boundary and carry finalized signature
facts through root and nested classification. Scan JSON whitespace within its
declared extent and decode budget without retaining the whitespace prefix.
Keep prefix storage bounded independently of stream chunk ownership.

Fill fixed ASAR prefixes and DMG trailers across short reads, preserve
cancellation through admission and stream EOF, and retain existing cleanup
ownership. Cover public CLI/MCP selection, root/nested inventory, malformed
JSON whitespace and padding, limits, native/XML consumers and descriptor faults.

Validation: 24 focused files / 170 tests; full check; docs check; test discovery.
Official inert ASAR fixtures and controlled descriptor faults were exercised;
real Electron execution, Windows and natural OS short-read frequency remain
unverified. Equivalent compact CLI tasks returned identical results and reply
token estimates; repeated timings do not establish a speedup.

Fixes #3574
Fixes #5188
2026-10-19 14:32:48 +08:00

227 lines
7.2 KiB
TypeScript

import { createPackageWithOptions } from "@electron/asar";
import { createHash } from "node:crypto";
import { readFile, writeFile } 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 {
asarHeaderBytes,
prependAsarJsonWhitespace,
} from "../../fixtures/asarHeader.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" }],
},
});
},
);
cliTest(
"accepts legal ASAR JSON whitespace while preserving selected-source identity",
{ timeout: 60_000 },
async ({ cli }) => {
const root = await createTestTempDirectory("rea-js-asar-whitespace-");
await writeJavaScriptSourceSelectionFixture(root);
const archive = join(
await createTestTempDirectory("rea-js-asar-whitespace-output-"),
"app.asar",
);
await createPackageWithOptions(root, archive, {});
for (const prefix of [" \t\r\n", " ".repeat(9_001)]) {
await prependAsarJsonWhitespace(archive, prefix);
const response = await cli.run({
arguments: [
"analyze-javascript-application",
archive,
"--source-path",
"main.mjs",
"--format",
"json",
],
});
expect(response.exitCode).toBe(0);
const evidence = parseEvidence(response.json);
const result = javascriptApplicationAnalysisResultSchema.parse(
evidence.normalized_result,
);
expect(result.statistics).toMatchObject({
nested_asar_containers: 0,
parsed_javascript_files: 1,
parse_failures: 0,
});
expect(result.semantic_graph.nodes).toContainEqual(
expect.objectContaining({ kind: "function", label: "selected" }),
);
expect(result.root_artifact_sha256).toBe(
createHash("sha256")
.update(await readFile(archive))
.digest("hex"),
);
}
},
);
cliTest(
"does not parse ASAR-looking JSON beyond the declared JSON extent",
async ({ cli }) => {
const archive = join(
await createTestTempDirectory("rea-js-asar-padding-output-"),
"opaque.asar",
);
await writeFile(
archive,
asarHeaderBytes(" ", {
padding: Buffer.from('{"files":{"main.mjs":{"size":0,"offset":"0"}}}'),
}),
);
const response = await cli.run({
arguments: [
"analyze-javascript-application",
archive,
"--format",
"json",
],
});
expect(response.exitCode).toBe(1);
expect(response.json).toMatchObject({
code: "artifact_operation_failed",
details: { reason: "format" },
});
},
);