mirror of
https://github.com/morluto/rea.git
synced 2026-11-13 10:12:50 +00:00
test: overhaul deterministic suite architecture
This commit is contained in:
1 parent
7f2348619a
commit
513d9b1555
371 files changed
+30879
-24389
No files matched your search
@@ -0,0 +1,331 @@
|
||||
import { mkdir, rm } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { createPackageWithOptions } from "@electron/asar";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
import { reconstructJavaScriptArtifact } from "../../../src/application/JavaScriptArtifactReconstruction.js";
|
||||
import { scanArtifactInventory } from "../../../src/application/ArtifactInventory.js";
|
||||
import { parseJavaScriptApplicationGraph } from "../../../src/domain/javascriptApplicationGraph.js";
|
||||
import { javaScriptApplicationAnalysisResultV2Schema } from "../../../src/domain/javascriptApplicationAnalysis.js";
|
||||
import { createJavaScriptSemanticGraph } from "../../../src/domain/javascriptSemanticGraph.js";
|
||||
import { parseJavaScriptSemanticGraph } from "../../../src/domain/javascriptSemanticGraphSerialization.js";
|
||||
import { writeJavaScriptArtifactFixture } from "../../fixtures/javascriptArtifactApplication.js";
|
||||
|
||||
it("reconstructs package, Electron roles, Webpack/Rspack modules, and cross-layer facts without execution", async () => {
|
||||
const root = await fixtureDirectory();
|
||||
Reflect.deleteProperty(globalThis, "__rea_bundle_executed");
|
||||
|
||||
const result = await reconstructJavaScriptArtifact({
|
||||
input_path: root,
|
||||
source_map_read_approved: true,
|
||||
});
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
const semanticGraph = parseJavaScriptSemanticGraph(result.semantic_graph);
|
||||
|
||||
expect(Reflect.get(globalThis, "__rea_bundle_executed")).toBeUndefined();
|
||||
expect(result.input_path).toBe(root);
|
||||
expect(result.statistics).toMatchObject({
|
||||
modules: 4,
|
||||
parse_failures: 0,
|
||||
omitted_text_files: 0,
|
||||
policy_filtered_text_files: 0,
|
||||
});
|
||||
expect(graph.nodes.map(({ kind }) => kind)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"package",
|
||||
"artifact",
|
||||
"electron-main",
|
||||
"electron-preload",
|
||||
"electron-renderer",
|
||||
"javascript-asset",
|
||||
"javascript-chunk",
|
||||
"javascript-module",
|
||||
"worker",
|
||||
"service-worker",
|
||||
"endpoint",
|
||||
"storage",
|
||||
"source-map",
|
||||
"source-module",
|
||||
"native-addon",
|
||||
]),
|
||||
);
|
||||
expect(graph.nodes.filter(({ kind }) => kind === "javascript-chunk")).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({ bundler: "webpack" }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({ bundler: "rspack" }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(
|
||||
graph.nodes.filter(({ kind }) => kind === "javascript-module"),
|
||||
).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
module_key: "1",
|
||||
structural_fingerprint_algorithm: "babel-ast-v1",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(graph.edges.map(({ relation }) => relation)).toEqual(
|
||||
expect.arrayContaining([
|
||||
"contains",
|
||||
"loads",
|
||||
"imports",
|
||||
"maps_to",
|
||||
"exposes",
|
||||
"calls",
|
||||
"persists_to",
|
||||
]),
|
||||
);
|
||||
assertSemanticLinks(result, graph, semanticGraph);
|
||||
});
|
||||
|
||||
it("produces deterministic ASAR graphs with unpacked native linkage and complete paths/digests", async () => {
|
||||
const root = await createTestTempDirectory("rea-javascript-asar-");
|
||||
const source = join(root, "source");
|
||||
await mkdir(source);
|
||||
await writeJavaScriptArtifactFixture(source);
|
||||
const archive = join(root, "app.asar");
|
||||
await createPackageWithOptions(source, archive, { unpack: "**/*.node" });
|
||||
|
||||
const first = await reconstructJavaScriptArtifact({
|
||||
input_path: archive,
|
||||
format: "asar",
|
||||
source_map_read_approved: true,
|
||||
});
|
||||
const second = await reconstructJavaScriptArtifact({
|
||||
input_path: archive,
|
||||
format: "asar",
|
||||
source_map_read_approved: true,
|
||||
});
|
||||
|
||||
expect(first.graph).toEqual(second.graph);
|
||||
expect(first.inventory_graph_sha256).toBe(second.inventory_graph_sha256);
|
||||
expect(first.graph.graph_id).toBe(second.graph.graph_id);
|
||||
expect(first.graph.nodes).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
kind: "asar-entry",
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
entry_sha256: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
||||
inventory_artifact_id:
|
||||
expect.stringMatching(/^art_[a-f0-9]{64}$/u),
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: "native-addon",
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
path: "native/addon.node",
|
||||
unpacked: true,
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
expect(first.graph.edges).toContainEqual(
|
||||
expect.objectContaining({
|
||||
relation: "loads",
|
||||
properties: expect.objectContaining({
|
||||
resolved_path: "native/addon.node",
|
||||
}),
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("keeps ASAR analysis usable when unpacked native companion bytes are absent", async () => {
|
||||
const root = await createTestTempDirectory("rea-javascript-asar-missing-");
|
||||
const source = join(root, "source");
|
||||
await mkdir(source);
|
||||
await writeJavaScriptArtifactFixture(source);
|
||||
const archive = join(root, "app.asar");
|
||||
await createPackageWithOptions(source, archive, { unpack: "**/*.node" });
|
||||
await rm(join(`${archive}.unpacked`, "native", "addon.node"));
|
||||
|
||||
const snapshot = await scanArtifactInventory(archive, {
|
||||
maxEntries: 8_000,
|
||||
maxTotalBytes: 512 * 1024 * 1024,
|
||||
maxEntryBytes: 128 * 1024 * 1024,
|
||||
maxCompressionRatio: 1_000,
|
||||
maxDepth: 64,
|
||||
maxPathBytes: 4_096,
|
||||
});
|
||||
const missingNative = snapshot.occurrences.find(
|
||||
({ logical_path }) => logical_path === "native/addon.node",
|
||||
);
|
||||
|
||||
expect(missingNative).toMatchObject({
|
||||
artifact_id: null,
|
||||
hash_status: "unavailable",
|
||||
limitations: expect.arrayContaining([
|
||||
"ASAR unpacked companion bytes were unavailable; no content hash or child artifact was produced.",
|
||||
]),
|
||||
});
|
||||
|
||||
const result = await reconstructJavaScriptArtifact({
|
||||
input_path: archive,
|
||||
format: "asar",
|
||||
source_map_read_approved: true,
|
||||
});
|
||||
|
||||
expect(result.statistics.parsed_javascript_files).toBeGreaterThan(0);
|
||||
expect(result.graph.nodes).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ kind: "javascript-asset" }),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it("recurses into filesystem-backed ASAR containers without losing container-relative paths", async () => {
|
||||
const root = await createTestTempDirectory("rea-javascript-nested-asar-");
|
||||
const source = await fixtureDirectory();
|
||||
const outer = join(root, "outer");
|
||||
const resources = join(outer, "resources");
|
||||
await mkdir(resources, { recursive: true });
|
||||
await createPackageWithOptions(source, join(resources, "app.asar"), {
|
||||
unpack: "**/*.node",
|
||||
});
|
||||
|
||||
const result = await reconstructJavaScriptArtifact({
|
||||
input_path: outer,
|
||||
source_map_read_approved: true,
|
||||
});
|
||||
|
||||
expect(result.statistics.nested_asar_containers).toBe(1);
|
||||
expect(result.statistics.modules).toBe(4);
|
||||
expect(result.graph.nodes).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
kind: "artifact",
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
label: "resources/app.asar",
|
||||
properties: expect.objectContaining({ format: "asar" }),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
expect.objectContaining({
|
||||
kind: "javascript-asset",
|
||||
observations: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
properties: expect.objectContaining({
|
||||
path: "resources/app.asar/main.js",
|
||||
}),
|
||||
}),
|
||||
]),
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
const assertSemanticLinks = (
|
||||
result: Awaited<ReturnType<typeof reconstructJavaScriptArtifact>>,
|
||||
graph: ReturnType<typeof parseJavaScriptApplicationGraph>,
|
||||
semanticGraph: ReturnType<typeof parseJavaScriptSemanticGraph>,
|
||||
) => {
|
||||
expect(semanticGraph.application_graph_id).toBe(graph.graph_id);
|
||||
const { graph_id: _semanticGraphId, ...semanticGraphInput } = semanticGraph;
|
||||
const mismatchedSemanticGraph = createJavaScriptSemanticGraph({
|
||||
...semanticGraphInput,
|
||||
application_graph_id: `jag_${"f".repeat(64)}`,
|
||||
});
|
||||
const { electron_summary: summary, ...analysisResult } = result;
|
||||
expect(() =>
|
||||
javaScriptApplicationAnalysisResultV2Schema.parse({
|
||||
...analysisResult,
|
||||
schema_version: 2,
|
||||
summary,
|
||||
semantic_graph: mismatchedSemanticGraph,
|
||||
}),
|
||||
).toThrow(/must commit the containing application graph/u);
|
||||
expect(
|
||||
semanticGraph.nodes.some(
|
||||
({ application_node_ids: identifiers }) => identifiers.length > 0,
|
||||
),
|
||||
).toBe(true);
|
||||
expect(semanticGraph.relations.length).toBeGreaterThan(0);
|
||||
const roles = ["electron-main", "electron-preload"].map((kind) =>
|
||||
graph.nodes.find((node) => node.kind === kind),
|
||||
);
|
||||
expect(roles[0]?.observations[0]?.properties).toMatchObject({
|
||||
declared_path: "main.js",
|
||||
resolution_context: "package-entrypoint",
|
||||
resolved_path: "main.js",
|
||||
resolution_status: "resolved",
|
||||
limitations: [],
|
||||
});
|
||||
expect(roles[1]?.observations[0]?.properties).toMatchObject({
|
||||
declared_path: "preload.js",
|
||||
resolution_context: "filesystem-expression",
|
||||
resolved_path: "preload.js",
|
||||
resolution_status: "resolved",
|
||||
limitations: [],
|
||||
});
|
||||
const mainAsset = graph.nodes.find(
|
||||
(node) =>
|
||||
node.kind === "javascript-asset" &&
|
||||
node.observations.some(
|
||||
({ evidence }) =>
|
||||
evidence.location.available &&
|
||||
evidence.location.value.kind === "artifact-path" &&
|
||||
evidence.location.value.path === "main.js",
|
||||
),
|
||||
);
|
||||
if (mainAsset === undefined)
|
||||
throw new Error("Expected main JavaScript asset");
|
||||
const mainSemanticNodes = semanticGraph.nodes.filter(
|
||||
({ identity }) => identity.module_path === "main.js",
|
||||
);
|
||||
const linkedMainSemanticNodes = mainSemanticNodes.filter(
|
||||
({ application_node_ids: identifiers }) =>
|
||||
identifiers.includes(mainAsset.node_id),
|
||||
);
|
||||
expect(linkedMainSemanticNodes.length).toBeGreaterThan(0);
|
||||
expect(linkedMainSemanticNodes.length).toBeLessThan(mainSemanticNodes.length);
|
||||
for (const role of roles) {
|
||||
expect(role).toBeDefined();
|
||||
expect(
|
||||
graph.edges.some(
|
||||
(edge) =>
|
||||
edge.source_node_id === role?.node_id && edge.relation === "maps_to",
|
||||
),
|
||||
).toBe(true);
|
||||
}
|
||||
const endpointJson = JSON.stringify(
|
||||
graph.nodes.filter(({ kind }) => kind === "endpoint"),
|
||||
);
|
||||
expect(endpointJson).toContain("token=%5BREDACTED%5D");
|
||||
expect(endpointJson).not.toContain("fixture-secret");
|
||||
};
|
||||
|
||||
const fixtureDirectory = async (): Promise<string> => {
|
||||
const root = await createTestTempDirectory("rea-javascript-artifact-");
|
||||
await writeJavaScriptArtifactFixture(root);
|
||||
return root;
|
||||
};
|
||||
Reference in new issue
Block a user