import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { mkdtemp, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { executeGetCodebaseOverview } from '../tools/codecontext/get_codebase_overview.js'; import { executeGetFileAnalysis } from '../tools/codecontext/get_file_analysis.js'; import { executeGetSymbolInfo } from '../tools/codecontext/get_symbol_info.js'; import { executeSearchSymbols } from '../tools/codecontext/search_symbols.js'; import { executeGetDependencies } from '../tools/codecontext/get_dependencies.js'; import { executeWatchChanges } from '../tools/codecontext/watch_changes.js'; import { executeGetSemanticNeighborhoods } from '../tools/codecontext/get_semantic_neighborhoods.js'; import { executeGetFrameworkAnalysis } from '../tools/codecontext/get_framework_analysis.js'; // ---- fixtures --------------------------------------------------------------- let projectDir: string; beforeEach(async () => { projectDir = await mkdtemp(join(tmpdir(), 'codecontext-tools-test-')); }); afterEach(async () => { await rm(projectDir, { recursive: true, force: true }); vi.restoreAllMocks(); }); function mockJSONResponse(body: unknown, status = 200): Response { return new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' }, }); } // Stub fetcher that records every call and returns a canned successful body. // Each test inspects fetcher.mock.calls[0] to assert URL + body shape. function makeStub() { return vi.fn().mockResolvedValue( mockJSONResponse({ result: 'wrapped ok', error: null }), ); } function parsePOST(fetcher: ReturnType): { url: string; body: Record; } { expect(fetcher).toHaveBeenCalledTimes(1); const [url, init] = fetcher.mock.calls[0]! as [string, { body: string }]; return { url, body: JSON.parse(init.body) }; } // ---- per-wrapper smoke tests ----------------------------------------------- describe('codecontext wrappers — toolName + args forwarding', () => { it('get_codebase_overview posts to /v1/get_codebase_overview with include_stats default true', async () => { const fetcher = makeStub(); await executeGetCodebaseOverview({}, projectDir, fetcher as unknown as typeof fetch); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_codebase_overview$/); expect(body).toMatchObject({ include_stats: true, target_dir: projectDir }); }); it('get_file_analysis forwards file_path', async () => { const fetcher = makeStub(); await executeGetFileAnalysis( { file_path: 'apps/server/src/index.ts' }, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_file_analysis$/); expect(body).toMatchObject({ file_path: join(projectDir, 'apps/server/src/index.ts'), target_dir: projectDir, }); }); it('get_symbol_info forwards symbol_name and omits optional fields when unset', async () => { const fetcher = makeStub(); await executeGetSymbolInfo( { symbol_name: 'buildSystemPrompt' }, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_symbol_info$/); expect(body).toMatchObject({ symbol_name: 'buildSystemPrompt', target_dir: projectDir }); expect(body).not.toHaveProperty('file_path'); expect(body).not.toHaveProperty('framework_type'); }); it('search_symbols defaults limit to 20 and forwards filters when set', async () => { const fetcher = makeStub(); await executeSearchSymbols( { query: 'User', symbol_type: 'class' }, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/search_symbols$/); expect(body).toMatchObject({ query: 'User', symbol_type: 'class', limit: 20, target_dir: projectDir, }); }); it('get_dependencies defaults direction to "both"', async () => { const fetcher = makeStub(); await executeGetDependencies({}, projectDir, fetcher as unknown as typeof fetch); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_dependencies$/); expect(body).toMatchObject({ direction: 'both', target_dir: projectDir }); expect(body).not.toHaveProperty('file_path'); }); it('watch_changes forwards enable=false', async () => { const fetcher = makeStub(); await executeWatchChanges( { enable: false }, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/watch_changes$/); expect(body).toMatchObject({ enable: false, target_dir: projectDir }); }); it('get_semantic_neighborhoods defaults max_results to 10', async () => { const fetcher = makeStub(); await executeGetSemanticNeighborhoods( {}, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_semantic_neighborhoods$/); expect(body).toMatchObject({ max_results: 10, target_dir: projectDir }); }); it('get_framework_analysis sends only target_dir when no args are provided', async () => { const fetcher = makeStub(); await executeGetFrameworkAnalysis( {}, projectDir, fetcher as unknown as typeof fetch, ); const { url, body } = parsePOST(fetcher); expect(url).toMatch(/\/v1\/get_framework_analysis$/); expect(body).toMatchObject({ target_dir: projectDir }); expect(body).not.toHaveProperty('framework'); expect(body).not.toHaveProperty('include_stats'); }); });