import { expect, it } from "vitest"; import { createJavaScriptApplicationGraph, createJavaScriptApplicationNode, } from "./javascriptApplicationGraph.js"; import { artifactEvidence, completeCoverage, } from "./javascriptApplicationGraph.fixture.js"; import { compareJavaScriptApplicationVersions } from "./javascriptApplicationVersionComparison.js"; const LEFT_SHA = "a".repeat(64); const RIGHT_SHA = "b".repeat(64); const LIMITATION = "Source is outside the caller-selected source_paths; it was inventoried but not analyzed."; const evidenceId = (character: string) => `ev_${character.repeat(64)}`; const node = (sha256: string, path: string) => createJavaScriptApplicationNode({ kind: "javascript-asset", identity: { strategy: "content-digest", stability: "global-exact", sha256 }, observations: [ { label: path, properties: { path }, evidence: artifactEvidence(sha256, path), }, ], }); const unavailableScope = ( artifactSha: string, state: "unknown" | "unavailable" = "unavailable", ) => createJavaScriptApplicationNode({ kind: "unknown", identity: { strategy: "artifact-local-key", stability: "artifact-version", artifact_sha256: artifactSha, namespace: "static-parse-scope", key: "select-javascript-sources:lib.js", }, observations: [ { label: "lib.js parse scope", properties: { path: "lib.js", operation: "select-javascript-sources" }, evidence: { ...artifactEvidence(artifactSha, "lib.js"), state, confidence: "unknown", limitations: [LIMITATION], }, }, ], }); const observedAssetWithUnavailableFact = () => { const digest = "e".repeat(64); return createJavaScriptApplicationNode({ kind: "javascript-asset", identity: { strategy: "content-digest", stability: "global-exact", sha256: digest, }, observations: [ { label: "new.js", properties: { path: "new.js" }, evidence: artifactEvidence(digest, "new.js"), }, { label: "new.js parse scope", properties: { operation: "parse-javascript" }, evidence: { ...artifactEvidence(digest, "new.js"), state: "unavailable", confidence: "unknown", limitations: [LIMITATION], }, }, ], }); }; const graph = (nodes: ReturnType[]) => createJavaScriptApplicationGraph({ schema: "JavaScriptApplicationGraph", root_node_ids: [nodes[0]!.node_id], nodes, edges: [], coverage: completeCoverage, limitations: [], }); const compare = ( left: ReturnType[], right: ReturnType[], ) => compareJavaScriptApplicationVersions({ left: { evidenceId: evidenceId("1"), rootArtifactSha256: LEFT_SHA, graph: graph(left), }, right: { evidenceId: evidenceId("2"), rootArtifactSha256: RIGHT_SHA, graph: graph(right), }, leftNativeEvidence: [], rightNativeEvidence: [], }); it("keeps unmatched unavailable scopes unknown in either direction", () => { const leftRoot = node("d".repeat(64), "main.js"); const rightRoot = node("d".repeat(64), "main.js"); const leftScope = unavailableScope(LEFT_SHA); const rightScope = unavailableScope(RIGHT_SHA); const forward = compare([leftRoot], [rightRoot, rightScope]); const reverse = compare([leftRoot, leftScope], [rightRoot]); expect( forward.items.find( ({ right_node_id }) => right_node_id === rightScope.node_id, ), ).toMatchObject({ status: "unknown", dimensions: ["availability"], limitations: [LIMITATION], evidence_links: [evidenceId("1"), evidenceId("2")], }); expect( reverse.items.find( ({ left_node_id }) => left_node_id === leftScope.node_id, ), ).toMatchObject({ status: "unknown", dimensions: ["availability"], limitations: [LIMITATION], }); expect(forward.summary).toMatchObject({ added: 0, removed: 0, unknown: 1 }); expect(reverse.summary).toMatchObject({ added: 0, removed: 0, unknown: 1 }); }); it("keeps matched unavailable scopes unknown with their source limitation", () => { const leftRoot = node("d".repeat(64), "main.js"); const rightRoot = node("d".repeat(64), "main.js"); const leftScope = unavailableScope(LEFT_SHA); const rightScope = unavailableScope(LEFT_SHA); const result = compare([leftRoot, leftScope], [rightRoot, rightScope]); expect( result.items.find(({ left_node_id }) => left_node_id === leftScope.node_id), ).toMatchObject({ right_node_id: rightScope.node_id, status: "unknown", dimensions: [], limitations: [LIMITATION], }); expect(result.summary).toMatchObject({ unchanged: 1, unknown: 1 }); }); it("retains unknown scope facts and reports genuine entity changes", () => { const leftRoot = node("d".repeat(64), "main.js"); const rightRoot = node("d".repeat(64), "main.js"); const unknown = unavailableScope(RIGHT_SHA, "unknown"); const added = node("e".repeat(64), "new.js"); const removed = node("f".repeat(64), "removed.js"); const mixedEvidenceAddition = observedAssetWithUnavailableFact(); const unknownResult = compare([leftRoot], [rightRoot, unknown]); const addedResult = compare([leftRoot], [rightRoot, added]); const removedResult = compare([leftRoot, removed], [rightRoot]); const mixedResult = compare([leftRoot], [rightRoot, mixedEvidenceAddition]); expect( unknownResult.items.find( ({ right_node_id }) => right_node_id === unknown.node_id, ), ).toMatchObject({ status: "unknown", limitations: [LIMITATION] }); expect(unknownResult.summary).toMatchObject({ added: 0, unknown: 1 }); expect( addedResult.items.find( ({ right_node_id }) => right_node_id === added.node_id, ), ).toMatchObject({ status: "added", dimensions: ["availability"] }); expect(addedResult.summary.added).toBe(1); expect( mixedResult.items.find( ({ right_node_id }) => right_node_id === mixedEvidenceAddition.node_id, ), ).toMatchObject({ status: "added", limitations: [LIMITATION] }); expect(mixedResult.summary.added).toBe(1); expect( removedResult.items.find( ({ left_node_id }) => left_node_id === removed.node_id, ), ).toMatchObject({ status: "removed", dimensions: ["availability"] }); expect(removedResult.summary.removed).toBe(1); expect( compare( [leftRoot, node("c".repeat(64), "same.js")], [rightRoot, node("c".repeat(64), "same.js")], ).summary, ).toMatchObject({ unchanged: 2, added: 0, removed: 0, }); });