Skip to content

Commit 40f861f

Browse files
committed
Phase 6: Comprehensive test suite for context graph
Add 7 test files covering: - graph-store.test.ts: CRUD, BFS traversal, cascading deletes, stale detection (27 tests) - extractor.test.ts: symbol resolution, ambiguity, self-ref prevention (8 tests) - session.test.ts: visit tracking, frontier, TTL, serialize/deserialize (16 tests) - context-query.integration.test.ts: end-to-end search + graph expansion (3 tests) - graph-indexing.integration.test.ts: full pipeline chunk → graph (5 tests) - graph-injection.test.ts: SQL injection prevention via parameterized queries (5 tests) - graph.perf.test.ts: benchmarks for BFS, extraction overhead, session summary (4 tests) All 410 tests pass (22 suites), zero regressions.
1 parent d316b4f commit 40f861f

7 files changed

Lines changed: 1372 additions & 0 deletions

File tree

tests/graph/extractor.test.ts

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/**
2+
* Tests for edge resolution: symbol matching, ambiguity handling.
3+
*/
4+
5+
import { resolveEdges } from '../../src/graph/extractor.js';
6+
import type { RawEdge } from '../../src/graph/types.js';
7+
8+
describe('resolveEdges', () => {
9+
it('should resolve same-file edges with weight 1.0', () => {
10+
const rawEdges: RawEdge[] = [
11+
{
12+
sourceChunkId: 'file_ts_L1',
13+
sourceFilePath: '/test/file.ts',
14+
targetSymbol: 'helper',
15+
edgeType: 'calls',
16+
},
17+
];
18+
19+
const symbolIndex = new Map([
20+
['helper', [{ id: 'file_ts_L20', filePath: '/test/file.ts' }]],
21+
]);
22+
23+
const resolved = resolveEdges(rawEdges, symbolIndex);
24+
expect(resolved).toHaveLength(1);
25+
expect(resolved[0]!.sourceId).toBe('file_ts_L1');
26+
expect(resolved[0]!.targetId).toBe('file_ts_L20');
27+
expect(resolved[0]!.weight).toBe(1.0);
28+
expect(resolved[0]!.edgeType).toBe('calls');
29+
});
30+
31+
it('should resolve cross-file edges with weight 0.8', () => {
32+
const rawEdges: RawEdge[] = [
33+
{
34+
sourceChunkId: 'a_ts_L1',
35+
sourceFilePath: '/test/a.ts',
36+
targetSymbol: 'externalFn',
37+
edgeType: 'calls',
38+
},
39+
];
40+
41+
const symbolIndex = new Map([
42+
['externalFn', [{ id: 'b_ts_L5', filePath: '/test/b.ts' }]],
43+
]);
44+
45+
const resolved = resolveEdges(rawEdges, symbolIndex);
46+
expect(resolved).toHaveLength(1);
47+
expect(resolved[0]!.weight).toBe(0.8);
48+
});
49+
50+
it('should prefer same-file matches for ambiguous symbols', () => {
51+
const rawEdges: RawEdge[] = [
52+
{
53+
sourceChunkId: 'a_ts_L1',
54+
sourceFilePath: '/test/a.ts',
55+
targetSymbol: 'process',
56+
edgeType: 'calls',
57+
},
58+
];
59+
60+
const symbolIndex = new Map([
61+
[
62+
'process',
63+
[
64+
{ id: 'b_ts_L10', filePath: '/test/b.ts' },
65+
{ id: 'a_ts_L50', filePath: '/test/a.ts' }, // Same file
66+
],
67+
],
68+
]);
69+
70+
const resolved = resolveEdges(rawEdges, symbolIndex);
71+
expect(resolved).toHaveLength(1);
72+
expect(resolved[0]!.targetId).toBe('a_ts_L50'); // Same file preferred
73+
expect(resolved[0]!.weight).toBe(1.0);
74+
});
75+
76+
it('should drop unresolvable edges', () => {
77+
const rawEdges: RawEdge[] = [
78+
{
79+
sourceChunkId: 'a_ts_L1',
80+
sourceFilePath: '/test/a.ts',
81+
targetSymbol: 'nonexistent',
82+
edgeType: 'calls',
83+
},
84+
];
85+
86+
const symbolIndex = new Map<string, Array<{ id: string; filePath: string }>>();
87+
88+
const resolved = resolveEdges(rawEdges, symbolIndex);
89+
expect(resolved).toHaveLength(0);
90+
});
91+
92+
it('should not create self-referencing edges', () => {
93+
const rawEdges: RawEdge[] = [
94+
{
95+
sourceChunkId: 'a_ts_L1',
96+
sourceFilePath: '/test/a.ts',
97+
targetSymbol: 'selfRef',
98+
edgeType: 'calls',
99+
},
100+
];
101+
102+
const symbolIndex = new Map([
103+
['selfRef', [{ id: 'a_ts_L1', filePath: '/test/a.ts' }]], // Same chunk
104+
]);
105+
106+
const resolved = resolveEdges(rawEdges, symbolIndex);
107+
expect(resolved).toHaveLength(0);
108+
});
109+
110+
it('should handle multiple edges', () => {
111+
const rawEdges: RawEdge[] = [
112+
{
113+
sourceChunkId: 'a_ts_L1',
114+
sourceFilePath: '/test/a.ts',
115+
targetSymbol: 'foo',
116+
edgeType: 'calls',
117+
},
118+
{
119+
sourceChunkId: 'a_ts_L1',
120+
sourceFilePath: '/test/a.ts',
121+
targetSymbol: 'bar',
122+
edgeType: 'imports',
123+
modulePath: './utils',
124+
},
125+
];
126+
127+
const symbolIndex = new Map([
128+
['foo', [{ id: 'a_ts_L20', filePath: '/test/a.ts' }]],
129+
['bar', [{ id: 'utils_ts_L1', filePath: '/test/utils.ts' }]],
130+
]);
131+
132+
const resolved = resolveEdges(rawEdges, symbolIndex);
133+
expect(resolved).toHaveLength(2);
134+
expect(resolved[0]!.edgeType).toBe('calls');
135+
expect(resolved[1]!.edgeType).toBe('imports');
136+
});
137+
138+
it('should include module path in metadata for imports', () => {
139+
const rawEdges: RawEdge[] = [
140+
{
141+
sourceChunkId: 'a_ts_L1',
142+
sourceFilePath: '/test/a.ts',
143+
targetSymbol: 'utils',
144+
edgeType: 'imports',
145+
modulePath: './utils/index',
146+
},
147+
];
148+
149+
const symbolIndex = new Map([
150+
['utils', [{ id: 'utils_ts_L1', filePath: '/test/utils.ts' }]],
151+
]);
152+
153+
const resolved = resolveEdges(rawEdges, symbolIndex);
154+
expect(resolved[0]!.metadata).toBe('./utils/index');
155+
});
156+
157+
it('should handle empty inputs', () => {
158+
const resolved = resolveEdges([], new Map());
159+
expect(resolved).toHaveLength(0);
160+
});
161+
});

0 commit comments

Comments
 (0)