mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 02:45:58 +00:00
219 lines
6.7 KiB
TypeScript
219 lines
6.7 KiB
TypeScript
import { expect, it } from "vitest";
|
|
|
|
import {
|
|
artifactEvidence,
|
|
completeCoverage,
|
|
fixtureNode,
|
|
} from "./javascriptApplicationGraph.fixture.js";
|
|
import { createJavaScriptApplicationGraph } from "./javascriptApplicationGraph.js";
|
|
import { compareJavaScriptApplicationVersions } from "./javascriptApplicationVersionComparison.js";
|
|
import type { ApplicationNode } from "./javascriptApplicationGraphSchemas.js";
|
|
|
|
const endpoint = (options: {
|
|
readonly sha: string;
|
|
readonly path?: string;
|
|
readonly line?: number;
|
|
readonly partial?: boolean;
|
|
readonly kind?: string;
|
|
readonly method?: string;
|
|
readonly candidate?: boolean;
|
|
readonly value?: string;
|
|
}): ApplicationNode => {
|
|
const path = options.path ?? "main.js";
|
|
const line = options.line ?? 1;
|
|
return fixtureNode(
|
|
"endpoint",
|
|
{
|
|
strategy: "artifact-local-key",
|
|
stability: "artifact-version",
|
|
artifact_sha256: options.sha,
|
|
namespace: options.kind ?? "fetch",
|
|
key: JSON.stringify([path, line, options.partial, options.candidate]),
|
|
},
|
|
"API_BASE",
|
|
{
|
|
endpoint_kind: options.kind ?? "fetch",
|
|
value: options.value ?? "API_BASE",
|
|
...(options.partial ? { resolution: "partial" } : {}),
|
|
...(options.candidate ? { interpretation: "candidate" } : {}),
|
|
...(options.method === undefined ? {} : { method: options.method }),
|
|
},
|
|
{
|
|
...artifactEvidence(options.sha, path, "ast-static-analysis"),
|
|
location: {
|
|
available: true,
|
|
value: {
|
|
kind: "source-range",
|
|
source: path,
|
|
start: { line, column: 0 },
|
|
end: { line, column: 15 },
|
|
},
|
|
},
|
|
},
|
|
);
|
|
};
|
|
|
|
const compare = (left: ApplicationNode, right: ApplicationNode) => {
|
|
const graph = (node: ApplicationNode) =>
|
|
createJavaScriptApplicationGraph({
|
|
schema: "JavaScriptApplicationGraph",
|
|
root_node_ids: [node.node_id],
|
|
nodes: [node],
|
|
edges: [],
|
|
coverage: completeCoverage,
|
|
limitations: [],
|
|
});
|
|
return compareJavaScriptApplicationVersions({
|
|
left: {
|
|
evidenceId: `ev_${"1".repeat(64)}`,
|
|
rootArtifactSha256: "a".repeat(64),
|
|
graph: graph(left),
|
|
},
|
|
right: {
|
|
evidenceId: `ev_${"2".repeat(64)}`,
|
|
rootArtifactSha256: "b".repeat(64),
|
|
graph: graph(right),
|
|
},
|
|
leftNativeEvidence: [],
|
|
rightNativeEvidence: [],
|
|
});
|
|
};
|
|
|
|
it.each(["moved-partial", "exact", "candidate"] as const)(
|
|
"does not select endpoint correspondence from equal display text for %s",
|
|
(control) => {
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64), partial: true }),
|
|
endpoint({
|
|
sha: "b".repeat(64),
|
|
path: control === "moved-partial" ? "other.js" : "main.js",
|
|
partial: control === "moved-partial",
|
|
candidate: control === "candidate",
|
|
}),
|
|
);
|
|
expect(result.items).toHaveLength(2);
|
|
expect(
|
|
result.items.every(
|
|
({ status, match }) =>
|
|
status === "unknown" && match.status === "unmatched",
|
|
),
|
|
).toBe(true);
|
|
},
|
|
);
|
|
|
|
it("aligns equal partial source sites while keeping target identity unknown across binding context changes", () => {
|
|
// Different source digests can hide changed bindings even when this call site stays fixed.
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64), partial: true }),
|
|
endpoint({ sha: "b".repeat(64), partial: true }),
|
|
);
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0]).toMatchObject({
|
|
status: "unknown",
|
|
match: { status: "matched", basis: "semantic-key" },
|
|
limitations: expect.arrayContaining([
|
|
expect.stringContaining("target identity remains unresolved"),
|
|
]),
|
|
});
|
|
});
|
|
|
|
it.each([false, true])(
|
|
"preserves known metadata changes on an established endpoint pair (partial=%s)",
|
|
(partial) => {
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64), partial, method: "GET" }),
|
|
endpoint({ sha: "b".repeat(64), partial, method: "POST" }),
|
|
);
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0]).toMatchObject({
|
|
status: "changed",
|
|
dimensions: ["properties"],
|
|
match: { status: "matched", basis: "semantic-key" },
|
|
});
|
|
},
|
|
);
|
|
|
|
it("keeps exact endpoint target matching portable across source relocation", () => {
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64) }),
|
|
endpoint({ sha: "b".repeat(64), path: "moved.js", line: 4 }),
|
|
);
|
|
expect(result.items).toHaveLength(1);
|
|
expect(result.items[0]).toMatchObject({
|
|
status: "unchanged",
|
|
dimensions: ["location"],
|
|
match: { status: "matched", basis: "semantic-key" },
|
|
});
|
|
});
|
|
|
|
it("keeps unrelated unresolved endpoint kinds from hiding exact target absence", () => {
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64) }),
|
|
endpoint({ sha: "b".repeat(64), kind: "route", partial: true }),
|
|
);
|
|
expect(
|
|
result.items.find(({ left_node_id }) => left_node_id !== null),
|
|
).toMatchObject({ status: "removed", match: { status: "unmatched" } });
|
|
expect(
|
|
result.items.find(({ right_node_id }) => right_node_id !== null),
|
|
).toMatchObject({ status: "unknown", match: { status: "unmatched" } });
|
|
});
|
|
|
|
it.each([null, "unresolved"] as const)(
|
|
"preserves explicit unknown endpoint resolution %s",
|
|
(resolution) => {
|
|
const right = endpoint({ sha: "b".repeat(64) });
|
|
const result = compare(endpoint({ sha: "a".repeat(64) }), {
|
|
...right,
|
|
observations: right.observations.map((observation) => ({
|
|
...observation,
|
|
properties: { ...observation.properties, resolution },
|
|
})),
|
|
});
|
|
expect(result.items).toHaveLength(2);
|
|
expect(
|
|
result.items.every(
|
|
({ status, match }) =>
|
|
status === "unknown" && match.status === "unmatched",
|
|
),
|
|
).toBe(true);
|
|
},
|
|
);
|
|
|
|
it.each(["unknown", "unavailable"] as const)(
|
|
"keeps %s endpoint Evidence from proving another target absent",
|
|
(state) => {
|
|
const right = endpoint({ sha: "b".repeat(64), value: "unestablished" });
|
|
const uncertain: ApplicationNode = {
|
|
...right,
|
|
observations: right.observations.map((observation) => ({
|
|
...observation,
|
|
evidence: {
|
|
...observation.evidence,
|
|
state,
|
|
authority: "unknown",
|
|
confidence: "unknown",
|
|
coverage: {
|
|
status: "unknown",
|
|
truncated: false,
|
|
omitted_count: null,
|
|
limits: [],
|
|
},
|
|
limitations: ["Endpoint target was not established."],
|
|
},
|
|
})),
|
|
};
|
|
const result = compare(
|
|
endpoint({ sha: "a".repeat(64), value: "known" }),
|
|
uncertain,
|
|
);
|
|
expect(result.items).toHaveLength(2);
|
|
expect(
|
|
result.items.every(
|
|
({ status, match }) =>
|
|
status === "unknown" && match.status === "unmatched",
|
|
),
|
|
).toBe(true);
|
|
},
|
|
);
|