mirror of
https://github.com/morluto/rea.git
synced 2026-11-08 15:56:35 +00:00
fix(javascript): separate endpoint syntax correspondence from target identity
This commit is contained in:
1 parent
6c307e9c86
commit
f6b525d3af
6 files changed
+451
-28
No files matched your search
@@ -0,0 +1,217 @@
|
||||
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: {
|
||||
...observation.evidence.coverage,
|
||||
status: "unknown",
|
||||
omitted_count: null,
|
||||
},
|
||||
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);
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user