138 lines
3.9 KiB
TypeScript
138 lines
3.9 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import {
|
|
computeJavaScriptApplicationGraphSha256,
|
|
createJavaScriptApplicationGraph,
|
|
javascriptApplicationGraphSchema,
|
|
parseJavaScriptApplicationGraph,
|
|
serializeJavaScriptApplicationGraph,
|
|
type ApplicationNode,
|
|
type JavaScriptApplicationGraph,
|
|
} from "../../../src/domain/javascriptApplicationGraph.js";
|
|
import {
|
|
JAVASCRIPT_APPLICATION_NODE_KINDS,
|
|
JAVASCRIPT_APPLICATION_RELATIONS,
|
|
} from "../../../src/domain/javascriptApplicationGraphSchemas.js";
|
|
import { buildSyntheticJavaScriptApplicationGraph } from "../../fixtures/javascriptApplicationGraph.js";
|
|
|
|
const nodeByLabel = (
|
|
graph: JavaScriptApplicationGraph,
|
|
label: string,
|
|
): ApplicationNode => {
|
|
const node = graph.nodes.find(({ observations }) =>
|
|
observations.some((observation) => observation.label === label),
|
|
);
|
|
if (node === undefined) throw new TypeError(`Missing fixture node: ${label}`);
|
|
return node;
|
|
};
|
|
|
|
describe("JavaScript Application Graph", () => {
|
|
it("defines the complete provider-neutral v1 node and relation vocabulary", () => {
|
|
expect(JAVASCRIPT_APPLICATION_NODE_KINDS).toEqual([
|
|
"package",
|
|
"installer",
|
|
"artifact",
|
|
"asar-entry",
|
|
"electron-main",
|
|
"electron-preload",
|
|
"electron-renderer",
|
|
"electron-utility",
|
|
"javascript-asset",
|
|
"javascript-chunk",
|
|
"javascript-module",
|
|
"source-map",
|
|
"source-module",
|
|
"browser-window",
|
|
"frame",
|
|
"target",
|
|
"context-bridge-api",
|
|
"ipc-channel",
|
|
"ipc-handler",
|
|
"worker",
|
|
"service-worker",
|
|
"endpoint",
|
|
"storage",
|
|
"native-addon",
|
|
"native-export",
|
|
"managed-assembly",
|
|
"managed-module",
|
|
"managed-type",
|
|
"managed-method",
|
|
"managed-field",
|
|
"managed-pinvoke-import",
|
|
"managed-native-implementation",
|
|
"runtime-script-instance",
|
|
"unknown",
|
|
]);
|
|
expect(JAVASCRIPT_APPLICATION_RELATIONS).toEqual([
|
|
"contains",
|
|
"loads",
|
|
"imports",
|
|
"maps_to",
|
|
"exposes",
|
|
"sends",
|
|
"invokes",
|
|
"handles",
|
|
"calls",
|
|
"persists_to",
|
|
"observed_as",
|
|
"changed_from",
|
|
]);
|
|
});
|
|
|
|
it("round-trips one canonical, versioned, byte-stable graph", () => {
|
|
const graph = buildSyntheticJavaScriptApplicationGraph();
|
|
const serialized = serializeJavaScriptApplicationGraph(graph);
|
|
const decoded: unknown = JSON.parse(serialized);
|
|
|
|
expect(parseJavaScriptApplicationGraph(decoded)).toEqual(graph);
|
|
expect(javascriptApplicationGraphSchema.parse(decoded)).toEqual(graph);
|
|
expect(serializeJavaScriptApplicationGraph(decoded)).toBe(serialized);
|
|
expect(computeJavaScriptApplicationGraphSha256(decoded)).toMatch(
|
|
/^[a-f0-9]{64}$/u,
|
|
);
|
|
|
|
const { graph_id: _graphId, ...semantic } = graph;
|
|
expect(
|
|
createJavaScriptApplicationGraph({
|
|
...semantic,
|
|
nodes: semantic.nodes.toReversed(),
|
|
edges: semantic.edges.toReversed(),
|
|
}),
|
|
).toEqual(graph);
|
|
});
|
|
|
|
it("represents the synthetic ASAR to preload to IPC to native chain", () => {
|
|
const graph = buildSyntheticJavaScriptApplicationGraph();
|
|
const labels = [
|
|
"resources/app.asar",
|
|
"desktop preload",
|
|
"desktopApi",
|
|
"project:open",
|
|
"project open handler",
|
|
"synthetic.node",
|
|
"openProject",
|
|
];
|
|
const chain = labels.map((label) => nodeByLabel(graph, label));
|
|
const expectedRelations = [
|
|
"contains",
|
|
"exposes",
|
|
"invokes",
|
|
"handles",
|
|
"loads",
|
|
"contains",
|
|
];
|
|
|
|
for (let index = 0; index < expectedRelations.length; index += 1)
|
|
expect(graph.edges).toContainEqual(
|
|
expect.objectContaining({
|
|
source_node_id: chain[index]?.node_id,
|
|
target_node_id: chain[index + 1]?.node_id,
|
|
relation: expectedRelations[index],
|
|
}),
|
|
);
|
|
expect(chain.at(-2)?.kind).toBe("native-addon");
|
|
expect(chain.at(-1)?.kind).toBe("native-export");
|
|
});
|
|
});
|