Skip to content

Commit 2cfe94b

Browse files
Copilotdata-douser
andauthored
Design 3 TDD: add cache lookup tests for cacheKey/queryName/language/limit; fix env-builder to not overwrite pre-existing vars
TDD tests for Design 3 (query_results_cache_lookup): - Exact cacheKey lookup returns metadata including resultCount - Non-existent cacheKey returns cached:false - Language-only filter correctly returns matching entries - databasePath filter returns matching entries - limit parameter restricts returned entries Environment builder fix (addresses PR review): - Only set ENABLE_ANNOTATION_TOOLS when not already defined in env - Only set MONITORING_STORAGE_LOCATION when not already defined in env - Added test: additionalEnv MONITORING_STORAGE_LOCATION override is preserved Agent-Logs-Url: https://github.com/advanced-security/codeql-development-mcp-server/sessions/97593c7f-7fd2-48f4-9f4c-0e97dfa47902 Co-authored-by: data-douser <70299490+data-douser@users.noreply.github.com>
1 parent 1eb9afa commit 2cfe94b

File tree

3 files changed

+199
-2
lines changed

3 files changed

+199
-2
lines changed

extensions/vscode/src/bridge/environment-builder.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,9 +151,17 @@ export class EnvironmentBuilder extends DisposableObject {
151151
// The setting controls ENABLE_ANNOTATION_TOOLS and defaults
152152
// MONITORING_STORAGE_LOCATION to the scratch directory so tools work
153153
// out-of-the-box without manual env var configuration.
154+
// Only set these when not already defined, to avoid overriding values
155+
// inherited from a parent process or earlier env setup.
154156
const enableAnnotations = config.get<boolean>('enableAnnotationTools', true);
155-
env.ENABLE_ANNOTATION_TOOLS = enableAnnotations ? 'true' : 'false';
156-
if (enableAnnotations && env.CODEQL_MCP_SCRATCH_DIR) {
157+
if (!('ENABLE_ANNOTATION_TOOLS' in env)) {
158+
env.ENABLE_ANNOTATION_TOOLS = enableAnnotations ? 'true' : 'false';
159+
}
160+
if (
161+
enableAnnotations &&
162+
env.CODEQL_MCP_SCRATCH_DIR &&
163+
!('MONITORING_STORAGE_LOCATION' in env)
164+
) {
157165
env.MONITORING_STORAGE_LOCATION = env.CODEQL_MCP_SCRATCH_DIR;
158166
}
159167

extensions/vscode/test/bridge/environment-builder.test.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,6 +278,39 @@ describe('EnvironmentBuilder', () => {
278278
expect(env.ENABLE_ANNOTATION_TOOLS).toBe('true');
279279
});
280280

281+
it('should not overwrite MONITORING_STORAGE_LOCATION if already set in parent env', async () => {
282+
const vscode = await import('vscode');
283+
const origFolders = vscode.workspace.workspaceFolders;
284+
const originalGetConfig = vscode.workspace.getConfiguration;
285+
286+
try {
287+
(vscode.workspace.workspaceFolders as any) = [
288+
{ uri: { fsPath: '/mock/workspace' }, name: 'ws', index: 0 },
289+
];
290+
// Simulate parent process env with MONITORING_STORAGE_LOCATION already set
291+
vscode.workspace.getConfiguration = () => ({
292+
get: (_key: string, defaultVal?: any) => {
293+
if (_key === 'additionalEnv') return { MONITORING_STORAGE_LOCATION: '/custom/storage/path' };
294+
if (_key === 'additionalDatabaseDirs') return [];
295+
if (_key === 'additionalQueryRunResultsDirs') return [];
296+
if (_key === 'additionalMrvaRunResultsDirs') return [];
297+
return defaultVal;
298+
},
299+
has: () => false,
300+
inspect: () => undefined as any,
301+
update: () => Promise.resolve(),
302+
}) as any;
303+
304+
builder.invalidate();
305+
const env = await builder.build();
306+
// additionalEnv should override the default MONITORING_STORAGE_LOCATION
307+
expect(env.MONITORING_STORAGE_LOCATION).toBe('/custom/storage/path');
308+
} finally {
309+
(vscode.workspace.workspaceFolders as any) = origFolders;
310+
vscode.workspace.getConfiguration = originalGetConfig;
311+
}
312+
});
313+
281314
it('should set ENABLE_ANNOTATION_TOOLS=false when setting is disabled', async () => {
282315
const vscode = await import('vscode');
283316
const originalGetConfig = vscode.workspace.getConfiguration;

server/test/src/tools/cache-tools.test.ts

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,162 @@ describe('Cache Tools', () => {
350350
expect(result.content[0].text).toContain('At least one filter');
351351
});
352352

353+
it('should lookup cache entry by exact cacheKey', async () => {
354+
const store = sessionDataManager.getStore();
355+
store.putCacheEntry({
356+
cacheKey: 'exact-key-abc',
357+
queryName: 'PrintAST',
358+
queryPath: '/print-ast.ql',
359+
databasePath: '/db1',
360+
language: 'javascript',
361+
codeqlVersion: '2.25.0',
362+
outputFormat: 'graphtext',
363+
resultContent: 'AST output',
364+
resultCount: 5,
365+
});
366+
367+
registerCacheTools(mockServer);
368+
369+
const lookupHandler = (mockServer.tool as any).mock.calls.find(
370+
(call: any) => call[0] === 'query_results_cache_lookup',
371+
)[3];
372+
373+
const result = await lookupHandler({ cacheKey: 'exact-key-abc' });
374+
const parsed = JSON.parse(result.content[0].text);
375+
expect(parsed.cached).toBe(true);
376+
expect(parsed.cacheKey).toBe('exact-key-abc');
377+
expect(parsed.queryName).toBe('PrintAST');
378+
expect(parsed.language).toBe('javascript');
379+
expect(parsed.resultCount).toBe(5);
380+
});
381+
382+
it('should return cached:false for non-existent cacheKey lookup', async () => {
383+
registerCacheTools(mockServer);
384+
385+
const lookupHandler = (mockServer.tool as any).mock.calls.find(
386+
(call: any) => call[0] === 'query_results_cache_lookup',
387+
)[3];
388+
389+
const result = await lookupHandler({ cacheKey: 'nonexistent-key' });
390+
const parsed = JSON.parse(result.content[0].text);
391+
expect(parsed.cached).toBe(false);
392+
expect(parsed.cacheKey).toBe('nonexistent-key');
393+
});
394+
395+
it('should lookup cache entries by language filter', async () => {
396+
const store = sessionDataManager.getStore();
397+
store.putCacheEntry({
398+
cacheKey: 'js-entry-1',
399+
queryName: 'PrintAST',
400+
queryPath: '/print-ast.ql',
401+
databasePath: '/db1',
402+
language: 'javascript',
403+
codeqlVersion: '2.25.0',
404+
outputFormat: 'graphtext',
405+
resultContent: 'content1',
406+
});
407+
store.putCacheEntry({
408+
cacheKey: 'js-entry-2',
409+
queryName: 'CallGraphTo',
410+
queryPath: '/cg.ql',
411+
databasePath: '/db2',
412+
language: 'javascript',
413+
codeqlVersion: '2.25.0',
414+
outputFormat: 'graphtext',
415+
resultContent: 'content2',
416+
});
417+
store.putCacheEntry({
418+
cacheKey: 'cpp-entry-1',
419+
queryName: 'PrintAST',
420+
queryPath: '/print-ast.ql',
421+
databasePath: '/db3',
422+
language: 'cpp',
423+
codeqlVersion: '2.25.0',
424+
outputFormat: 'graphtext',
425+
resultContent: 'content3',
426+
});
427+
428+
registerCacheTools(mockServer);
429+
430+
const lookupHandler = (mockServer.tool as any).mock.calls.find(
431+
(call: any) => call[0] === 'query_results_cache_lookup',
432+
)[3];
433+
434+
const result = await lookupHandler({ language: 'javascript' });
435+
const parsed = JSON.parse(result.content[0].text);
436+
expect(parsed.cached).toBe(true);
437+
expect(parsed.count).toBe(2);
438+
expect(parsed.entries).toHaveLength(2);
439+
// All entries should be JavaScript
440+
for (const entry of parsed.entries) {
441+
expect(entry.language).toBe('javascript');
442+
}
443+
});
444+
445+
it('should lookup cache entries by databasePath filter', async () => {
446+
const store = sessionDataManager.getStore();
447+
store.putCacheEntry({
448+
cacheKey: 'db1-entry',
449+
queryName: 'PrintAST',
450+
queryPath: '/print-ast.ql',
451+
databasePath: '/db1',
452+
language: 'javascript',
453+
codeqlVersion: '2.25.0',
454+
outputFormat: 'graphtext',
455+
resultContent: 'content1',
456+
});
457+
store.putCacheEntry({
458+
cacheKey: 'db2-entry',
459+
queryName: 'PrintAST',
460+
queryPath: '/print-ast.ql',
461+
databasePath: '/db2',
462+
language: 'javascript',
463+
codeqlVersion: '2.25.0',
464+
outputFormat: 'graphtext',
465+
resultContent: 'content2',
466+
});
467+
468+
registerCacheTools(mockServer);
469+
470+
const lookupHandler = (mockServer.tool as any).mock.calls.find(
471+
(call: any) => call[0] === 'query_results_cache_lookup',
472+
)[3];
473+
474+
const result = await lookupHandler({ databasePath: '/db1' });
475+
const parsed = JSON.parse(result.content[0].text);
476+
expect(parsed.cached).toBe(true);
477+
expect(parsed.count).toBe(1);
478+
expect(parsed.entries[0].databasePath).toBe('/db1');
479+
});
480+
481+
it('should respect limit parameter in cache lookup', async () => {
482+
const store = sessionDataManager.getStore();
483+
for (let i = 0; i < 5; i++) {
484+
store.putCacheEntry({
485+
cacheKey: `limit-entry-${i}`,
486+
queryName: 'Q',
487+
queryPath: '/q.ql',
488+
databasePath: `/db${i}`,
489+
language: 'javascript',
490+
codeqlVersion: '2.25.0',
491+
outputFormat: 'graphtext',
492+
resultContent: `content${i}`,
493+
});
494+
}
495+
496+
registerCacheTools(mockServer);
497+
498+
const lookupHandler = (mockServer.tool as any).mock.calls.find(
499+
(call: any) => call[0] === 'query_results_cache_lookup',
500+
)[3];
501+
502+
const result = await lookupHandler({ language: 'javascript', limit: 2 });
503+
const parsed = JSON.parse(result.content[0].text);
504+
expect(parsed.cached).toBe(true);
505+
expect(parsed.count).toBe(2);
506+
expect(parsed.entries).toHaveLength(2);
507+
});
508+
353509
it('should derive totalResultCount from SARIF content when resultCount is null', async () => {
354510
const sarif = {
355511
version: '2.1.0',

0 commit comments

Comments
 (0)