mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-28 01:31:23 +00:00
test(mcp): route impact BFS frontier mocks through executeParameterized (U3 CI fix, #1907)
U3 moved the impact BFS frontier query from executeQuery to executeParameterized (bound params). Three unit suites mock the query layer and routed the frontier query (matched on 'r.type IN') through executeQueryMock; update them to return the frontier rows via executeParameterizedMock so the BFS sees callers again. Test-only — no production change. Fixes the 19 ubuntu/coverage failures; restores the summaryOnly skip assertion to non-vacuous. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
eeafab80f2
commit
71a4e3a4f0
3 changed files with 133 additions and 95 deletions
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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' }];
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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)', () => {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue