diff --git a/gitnexus/test/unit/calltool-dispatch.test.ts b/gitnexus/test/unit/calltool-dispatch.test.ts index 55e9b9a6a..f645c1b19 100644 --- a/gitnexus/test/unit/calltool-dispatch.test.ts +++ b/gitnexus/test/unit/calltool-dispatch.test.ts @@ -649,19 +649,26 @@ describe('LocalBackend.callTool', () => { it('impact byDepth items include a processes field (default empty when no processes)', async () => { // Resolver returns target; BFS returns one frontier caller; no STEP_IN_PROCESS rows. - (executeParameterized as any).mockResolvedValue([ - { id: 'func:main', name: 'main', type: 'Function', filePath: 'src/index.ts' }, - ]); - (executeQuery as any).mockResolvedValue([ - { - id: 'func:caller', - name: 'caller', - type: 'Function', - filePath: 'src/uses-main.ts', - relType: 'CALLS', - confidence: 0.9, - }, - ]); + (executeParameterized as any).mockImplementation((_repoId: string, cypher: string) => { + // BFS frontier query is now parameterized (#1907 U3). + if (cypher.includes('r.type IN') && !cypher.includes('STEP_IN_PROCESS')) { + return Promise.resolve([ + { + id: 'func:caller', + name: 'caller', + type: 'Function', + filePath: 'src/uses-main.ts', + relType: 'CALLS', + confidence: 0.9, + }, + ]); + } + // Symbol resolution. + return Promise.resolve([ + { id: 'func:main', name: 'main', type: 'Function', filePath: 'src/index.ts' }, + ]); + }); + (executeQuery as any).mockResolvedValue([]); const result = await backend.callTool('impact', { target: 'main', direction: 'upstream' }); const d1 = result.byDepth?.[1] || result.byDepth?.['1'] || []; @@ -674,6 +681,19 @@ describe('LocalBackend.callTool', () => { it('impact populates byDepth processes when STEP_IN_PROCESS rows exist', async () => { (executeParameterized as any).mockImplementation((_repoId: string, cypher: string) => { + // BFS frontier query is now parameterized (#1907 U3). + if (cypher.includes('r.type IN') && !cypher.includes('STEP_IN_PROCESS')) { + return Promise.resolve([ + { + id: 'func:caller', + name: 'caller', + type: 'Function', + filePath: 'src/uses-main.ts', + relType: 'CALLS', + confidence: 0.9, + }, + ]); + } // Symbol resolver name-lookup if (cypher.includes('WHERE n.name =')) { return Promise.resolve([ @@ -739,6 +759,20 @@ describe('LocalBackend.callTool', () => { it('impact summaryOnly:true skips the per-symbol STEP_IN_PROCESS enrichment pass', async () => { // Resolver returns target; BFS returns one caller; aggregation returns one process row. (executeParameterized as any).mockImplementation((_repoId: string, cypher: string) => { + // BFS frontier query is now parameterized (#1907 U3) — return a caller so + // the per-symbol-skip assertion below is meaningful (not vacuous). + if (cypher.includes('r.type IN') && !cypher.includes('STEP_IN_PROCESS')) { + return Promise.resolve([ + { + id: 'func:caller', + name: 'caller', + type: 'Function', + filePath: 'src/a.ts', + relType: 'CALLS', + confidence: 0.9, + }, + ]); + } if (cypher.includes('WHERE n.name =')) { return Promise.resolve([ { id: 'func:main', name: 'main', type: 'Function', filePath: 'src/index.ts' }, @@ -811,6 +845,19 @@ describe('LocalBackend.callTool', () => { await backend.init(); (executeParameterized as any).mockImplementation((_repoId: string, cypher: string) => { + // BFS frontier query is now parameterized (#1907 U3). + if (cypher.includes('r.type IN') && !cypher.includes('STEP_IN_PROCESS')) { + return Promise.resolve([ + { + id: 'func:caller', + name: 'caller', + type: 'Function', + filePath: 'src/uses-main.ts', + relType: 'CALLS', + confidence: 0.9, + }, + ]); + } // UID resolver if (cypher.includes('WHERE n.id = $uid')) { return Promise.resolve([ diff --git a/gitnexus/test/unit/impact-batching-grouping.test.ts b/gitnexus/test/unit/impact-batching-grouping.test.ts index 098d72cd7..e600042de 100644 --- a/gitnexus/test/unit/impact-batching-grouping.test.ts +++ b/gitnexus/test/unit/impact-batching-grouping.test.ts @@ -68,30 +68,9 @@ describe('impact: batching and grouping', () => { const chunkSizes: number[] = []; let chunkCallIndex = 0; - executeQueryMock.mockImplementation(async (...args: any[]) => { - const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); - // Depth traversal query (find related nodes) -- return 250 impacted ids - if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { - const res: any[] = []; - for (let i = 0; i < 250; i++) { - res.push({ - id: `node-${i}`, - name: `n${i}`, - filePath: `file-${i}.js`, - relType: 'CALLS', - confidence: null, - }); - } - return res; - } - - // NOTE: process-chunk enrichment previously used executeQuery; our - // implementation now calls executeParameterized for those chunks. We - // still keep this branch to support any legacy calls, but primary - // chunk tracking will be handled via executeParameterizedMock below. - - return []; - }); + // BFS frontier query is now parameterized (#1907 U3) — handled in + // executeParameterizedMock below; executeQuery is unused by the impact path. + executeQueryMock.mockImplementation(async () => []); // Handle parameterized calls (including chunked STEP_IN_PROCESS queries) executeParameterizedMock.mockImplementation(async (...args: any[]) => { @@ -117,6 +96,20 @@ describe('impact: batching and grouping', () => { }, ]; } + // BFS frontier query (parameterized #1907 U3): return the 250 impacted ids. + if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { + const res: any[] = []; + for (let i = 0; i < 250; i++) { + res.push({ + id: `node-${i}`, + name: `n${i}`, + filePath: `file-${i}.js`, + relType: 'CALLS', + confidence: null, + }); + } + return res; + } // Default target resolution return [{ id: 'sym1', name: 'Target', filePath: 'f' }]; }); @@ -151,6 +144,19 @@ describe('impact: batching and grouping', () => { executeParameterizedMock.mockImplementation(async (...args: any[]) => { const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); + // BFS frontier query (parameterized #1907 U3): return 6 impacted nodes. + if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { + const res: any[] = []; + for (let i = 0; i < 6; i++) + res.push({ + id: `node-${i}`, + name: `n${i}`, + filePath: `file-${i}.js`, + relType: 'CALLS', + confidence: null, + }); + return res; + } if (!query.includes('STEP_IN_PROCESS')) return [{ id: 'symA', name: 'TargetA', filePath: 'f' }]; // For STEP_IN_PROCESS in this test, return grouping rows @@ -190,25 +196,9 @@ describe('impact: batching and grouping', () => { ]; }); - // Prepare impacted nodes: smaller set for clarity (6 nodes -> chunk size default 100 so single chunk) - executeQueryMock.mockImplementation(async (...args: any[]) => { - const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); - if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { - // return 6 nodes - const res: any[] = []; - for (let i = 0; i < 6; i++) - res.push({ - id: `node-${i}`, - name: `n${i}`, - filePath: `file-${i}.js`, - relType: 'CALLS', - confidence: null, - }); - return res; - } - - return []; - }); + // BFS frontier query is now parameterized (#1907 U3) — handled in + // executeParameterizedMock above; executeQuery is unused by the impact path. + executeQueryMock.mockImplementation(async () => []); const params = { target: 'TargetA', direction: 'downstream', maxDepth: 1 } as any; const res = await (backend as any)._impactImpl(repoHandle, params); @@ -243,23 +233,9 @@ describe('impact: batching and grouping', () => { (backend as any).repos.set(repoHandle.id, repoHandle); (backend as any).ensureInitialized = vi.fn().mockResolvedValue(undefined); - // Depth traversal returns 500 impacted nodes - executeQueryMock.mockImplementation(async (...args: any[]) => { - const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); - if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { - const res: any[] = []; - for (let i = 0; i < 500; i++) - res.push({ - id: `node-${i}`, - name: `n${i}`, - filePath: `file-${i}.js`, - relType: 'CALLS', - confidence: null, - }); - return res; - } - return []; - }); + // BFS frontier query is now parameterized (#1907 U3) — handled in + // executeParameterizedMock below; executeQuery is unused by the impact path. + executeQueryMock.mockImplementation(async () => []); const chunkSizes: number[] = []; @@ -294,6 +270,19 @@ describe('impact: batching and grouping', () => { return [{ name: 'ModuleA' }]; } + // BFS frontier query (parameterized #1907 U3): return 500 impacted nodes. + if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { + const res: any[] = []; + for (let i = 0; i < 500; i++) + res.push({ + id: `node-${i}`, + name: `n${i}`, + filePath: `file-${i}.js`, + relType: 'CALLS', + confidence: null, + }); + return res; + } // Default: target resolution return [{ id: 'symX', name: 'TargetX', filePath: 'f' }]; }); diff --git a/gitnexus/test/unit/impact-pagination.test.ts b/gitnexus/test/unit/impact-pagination.test.ts index a9d604c26..163159240 100644 --- a/gitnexus/test/unit/impact-pagination.test.ts +++ b/gitnexus/test/unit/impact-pagination.test.ts @@ -46,30 +46,35 @@ function makeBackend() { return { backend, repoHandle }; } +// The BFS frontier query is now parameterized (bound $frontierIds/$relTypes, +// #1907 U3), so the caller rows come back through executeParameterizedMock +// (matched on `r.type IN`) rather than executeQueryMock. Symbol resolution and +// the label-enrichment UNION still fall through to the default symbol row. function setupMultiDepthHub(d1Count: number, d2Count: number) { let depth = 0; executeParameterizedMock.mockImplementation(async (...args: any[]) => { const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); if (query.includes('STEP_IN_PROCESS')) return []; if (query.includes('MEMBER_OF')) return []; + if (query.includes('r.type IN')) { + depth++; + const count = depth === 1 ? d1Count : depth === 2 ? d2Count : 0; + const res: any[] = []; + for (let i = 0; i < count; i++) { + res.push({ + id: `d${depth}-caller-${i}`, + name: `d${depth}caller${i}`, + filePath: `src/d${depth}-caller-${i}.ts`, + relType: 'CALLS', + confidence: null, + }); + } + return res; + } return [{ id: 'hub1', name: 'HubSymbol', filePath: 'hub.ts' }]; }); - executeQueryMock.mockImplementation(async () => { - depth++; - const count = depth === 1 ? d1Count : depth === 2 ? d2Count : 0; - const res: any[] = []; - for (let i = 0; i < count; i++) { - res.push({ - id: `d${depth}-caller-${i}`, - name: `d${depth}caller${i}`, - filePath: `src/d${depth}-caller-${i}.ts`, - relType: 'CALLS', - confidence: null, - }); - } - return res; - }); + executeQueryMock.mockImplementation(async () => []); } function setupHubSymbol(count: number) { @@ -77,12 +82,7 @@ function setupHubSymbol(count: number) { const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); if (query.includes('STEP_IN_PROCESS')) return []; if (query.includes('MEMBER_OF')) return []; - return [{ id: 'hub1', name: 'HubSymbol', filePath: 'hub.ts' }]; - }); - - executeQueryMock.mockImplementation(async (...args: any[]) => { - const query = typeof args[1] === 'string' ? args[1] : String(args[0] ?? ''); - if (query.includes('r.type IN') && !query.includes('STEP_IN_PROCESS')) { + if (query.includes('r.type IN')) { const res: any[] = []; for (let i = 0; i < count; i++) { res.push({ @@ -95,8 +95,10 @@ function setupHubSymbol(count: number) { } return res; } - return []; + return [{ id: 'hub1', name: 'HubSymbol', filePath: 'hub.ts' }]; }); + + executeQueryMock.mockImplementation(async () => []); } describe('impact: pagination and summaryOnly (#414)', () => {