Files
socraticode/tests/unit/graph-symbol-resolution.test.ts
Giancarlo Erra 59f630e1ca test+docs(impact): add symbol pipeline tests and Phase G docs
- 4 new unit test files (22 tests) for graph-symbols, graph-symbol-resolution,
  graph-entrypoints, symbol-graph-cache. All 799 tests pass.
- README/AGENTS/CLAUDE/GEMINI: document codebase_impact, codebase_flow,
  codebase_symbol, codebase_symbols and updated workflow guidance.
- DEVELOPER.md: new architectural section for symbol-level call graph,
  including sharded storage layout and resolution confidence levels.
- CHANGELOG: Unreleased section for the impact-analysis feature.
- Apply CodeRabbit fixes: dedupe in-flight cache loads, set ImpactResult.truncated
  when frontier extends beyond depth limit, drop redundant String() around Lang enum.
- Switch reverse-shard hash from SHA1 to SHA256 (Snyk hardening; non-cryptographic use).
2026-04-21 13:54:01 +01:00

129 lines
4.2 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-only
// Copyright (C) 2026 Giancarlo Erra - Altaire Limited
import { describe, expect, it } from "vitest";
import {
computeUnresolvedPct,
resolveCallSites,
} from "../../src/services/graph-symbol-resolution.js";
import type { CodeGraph, SymbolEdge, SymbolNode } from "../../src/types.js";
function mkGraph(): CodeGraph {
return {
nodes: [
{
relativePath: "src/a.ts",
imports: [],
exports: [],
dependencies: ["src/b.ts"],
dependents: [],
},
{
relativePath: "src/b.ts",
imports: [],
exports: [],
dependencies: ["src/c.ts"],
dependents: ["src/a.ts"],
},
{
relativePath: "src/c.ts",
imports: [],
exports: [],
dependencies: [],
dependents: ["src/b.ts"],
},
],
edges: [],
};
}
describe("graph-symbol-resolution", () => {
it("resolves a local call to unique confidence", () => {
const graph = mkGraph();
const symbolsByFile = new Map<string, SymbolNode[]>([
[
"src/a.ts",
[
{ id: "src/a.ts::foo#1", name: "foo", qualifiedName: "foo", kind: "function", file: "src/a.ts", line: 1, endLine: 3, language: "typescript" },
{ id: "src/a.ts::caller#5", name: "caller", qualifiedName: "caller", kind: "function", file: "src/a.ts", line: 5, endLine: 8, language: "typescript" },
],
],
]);
const edges: SymbolEdge[] = [
{
callerId: "src/a.ts::caller#5",
calleeName: "foo",
calleeCandidates: [],
confidence: "unresolved",
callSite: { file: "src/a.ts", line: 6 },
},
];
const outgoing = new Map<string, SymbolEdge[]>([["src/a.ts", edges]]);
resolveCallSites(graph, symbolsByFile, outgoing);
expect(edges[0].confidence).toBe("local");
expect(edges[0].calleeCandidates).toContain("src/a.ts::foo#1");
});
it("resolves an imported call by walking dependencies", () => {
const graph = mkGraph();
const symbolsByFile = new Map<string, SymbolNode[]>([
[
"src/a.ts",
[{ id: "src/a.ts::caller#1", name: "caller", qualifiedName: "caller", kind: "function", file: "src/a.ts", line: 1, endLine: 3, language: "typescript" }],
],
[
"src/b.ts",
[{ id: "src/b.ts::helper#1", name: "helper", qualifiedName: "helper", kind: "function", file: "src/b.ts", line: 1, endLine: 5, language: "typescript" }],
],
]);
const edges: SymbolEdge[] = [
{
callerId: "src/a.ts::caller#1",
calleeName: "helper",
calleeCandidates: [],
confidence: "unresolved",
callSite: { file: "src/a.ts", line: 2 },
},
];
const outgoing = new Map<string, SymbolEdge[]>([["src/a.ts", edges]]);
resolveCallSites(graph, symbolsByFile, outgoing);
expect(["unique", "multiple-candidates"]).toContain(edges[0].confidence);
expect(edges[0].calleeCandidates).toContain("src/b.ts::helper#1");
});
it("leaves a call unresolved when no symbol matches anywhere", () => {
const graph = mkGraph();
const symbolsByFile = new Map<string, SymbolNode[]>();
const edges: SymbolEdge[] = [
{
callerId: "src/a.ts::<module>#1",
calleeName: "doesNotExist",
calleeCandidates: [],
confidence: "unresolved",
callSite: { file: "src/a.ts", line: 1 },
},
];
const outgoing = new Map<string, SymbolEdge[]>([["src/a.ts", edges]]);
resolveCallSites(graph, symbolsByFile, outgoing);
expect(edges[0].confidence).toBe("unresolved");
expect(edges[0].calleeCandidates).toEqual([]);
});
it("computeUnresolvedPct returns 0 when no edges", () => {
expect(computeUnresolvedPct(new Map())).toBe(0);
});
it("computeUnresolvedPct reports correct percentage", () => {
const map = new Map<string, SymbolEdge[]>([
[
"src/a.ts",
[
{ callerId: "x", calleeName: "y", calleeCandidates: ["x"], confidence: "unique", callSite: { file: "x", line: 1 } },
{ callerId: "x", calleeName: "z", calleeCandidates: [], confidence: "unresolved", callSite: { file: "x", line: 2 } },
],
],
]);
expect(computeUnresolvedPct(map)).toBe(50);
});
});