Skip to main content
Glama

Context Pods

by conorluddy
analyze-codebase-tool.test.ts23 kB
import { promises as fs } from 'fs'; import { tmpdir } from 'os'; import { join } from 'path'; import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { AnalyzeCodebaseTool } from '../../src/tools/analyze-codebase.js'; describe('AnalyzeCodebaseTool Integration', () => { let tool: AnalyzeCodebaseTool; let tempDir: string; beforeEach(async () => { tool = new AnalyzeCodebaseTool(); // Create a temporary directory for test files tempDir = join(tmpdir(), `context-pods-test-${Date.now()}`); await fs.mkdir(tempDir, { recursive: true }); }); afterEach(async () => { // Clean up temporary directory try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { // Ignore cleanup errors } }); describe('real file analysis', () => { it('should analyze TypeScript files with API patterns', async () => { const testFile = join(tempDir, 'api-service.ts'); const content = ` import axios from 'axios'; /** * Fetches weather data from external API * @param city The city to get weather for * @param units Temperature units (metric/imperial) */ export async function getWeatherData( city: string, units: 'metric' | 'imperial' = 'metric' ): Promise<WeatherData> { if (!city || city.trim().length === 0) { throw new Error('City name is required'); } try { const response = await axios.get('/api/weather', { params: { city, units } }); return response.data; } catch (error) { console.error('Failed to fetch weather data:', error); throw new Error('Weather service unavailable'); } } interface WeatherData { temperature: number; humidity: number; description: string; } `; await fs.writeFile(testFile, content); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'detailed', minScore: 50, }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const text = result.content[0].text; expect(text).toContain('getWeatherData'); expect(text).toContain('api-integration'); expect(text).toContain('Score:'); expect(text).toContain('typescript-advanced'); }); it('should analyze files with file operations', async () => { const testFile = join(tempDir, 'file-processor.ts'); const content = ` import { promises as fs } from 'fs'; import path from 'path'; /** * Processes configuration files and merges them */ export async function mergeConfigFiles( configDir: string, outputPath: string ): Promise<void> { const files = await fs.readdir(configDir); const configFiles = files.filter(f => f.endsWith('.json')); const mergedConfig = {}; for (const file of configFiles) { const filePath = path.join(configDir, file); const content = await fs.readFile(filePath, 'utf-8'); const config = JSON.parse(content); Object.assign(mergedConfig, config); } await fs.writeFile(outputPath, JSON.stringify(mergedConfig, null, 2)); } `; await fs.writeFile(testFile, content); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); expect(jsonResult).toHaveProperty('opportunities'); expect(jsonResult.opportunities).toHaveLength(1); expect(jsonResult.opportunities[0]).toMatchObject({ functionName: 'mergeConfigFiles', category: 'file-processing', }); }); it('should analyze validation functions', async () => { const testFile = join(tempDir, 'validator.ts'); const content = ` import { z } from 'zod'; const UserSchema = z.object({ name: z.string().min(1), email: z.string().email(), age: z.number().min(0).max(120), }); /** * Validates user input data */ export function validateUserData(input: unknown): User { return UserSchema.parse(input); } type User = z.infer<typeof UserSchema>; `; await fs.writeFile(testFile, content); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'summary', maxResults: 5, }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const text = result.content[0].text; expect(text).toContain('validateUserData'); expect(text).toContain('validateUserData'); }); it('should handle multiple files in directory structure', async () => { // Create subdirectory const srcDir = join(tempDir, 'src'); const utilsDir = join(srcDir, 'utils'); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(utilsDir, { recursive: true }); // Create multiple files await fs.writeFile( join(srcDir, 'main.ts'), ` export function simpleFunction(): string { return 'hello'; } `, ); await fs.writeFile( join(utilsDir, 'api.ts'), ` export async function fetchData(url: string): Promise<any> { const response = await fetch(url); return response.json(); } `, ); await fs.writeFile( join(utilsDir, 'validator.ts'), ` import Joi from 'joi'; export function validateEmail(email: string): boolean { const schema = Joi.string().email(); const result = schema.validate(email); return !result.error; } `, ); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'detailed', minScore: 30, }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const text = result.content[0].text; // Should find functions from multiple files expect(text).toContain('fetchData'); expect(text).toContain('validateEmail'); // Simple function might not meet minimum score }); it('should respect exclude patterns', async () => { // Create test files including some that should be excluded const srcDir = join(tempDir, 'src'); const nodeModulesDir = join(tempDir, 'node_modules'); const testsDir = join(tempDir, 'tests'); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(nodeModulesDir, { recursive: true }); await fs.mkdir(testsDir, { recursive: true }); await fs.writeFile( join(srcDir, 'app.ts'), ` export async function processData(): Promise<void> { const data = await fetch('/api/data'); console.log(data); } `, ); await fs.writeFile( join(nodeModulesDir, 'library.ts'), ` export function libraryFunction(): void { console.log('This should be excluded'); } `, ); await fs.writeFile( join(testsDir, 'app.test.ts'), ` export function testFunction(): void { console.log('This should be excluded'); } `, ); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); // Should only find the function from src/, not from node_modules or tests const functionNames = jsonResult.opportunities.map((opp: any) => opp.functionName); expect(functionNames).toContain('processData'); expect(functionNames).not.toContain('libraryFunction'); expect(functionNames).not.toContain('testFunction'); }); it('should handle custom exclude patterns', async () => { const srcDir = join(tempDir, 'src'); const legacyDir = join(tempDir, 'legacy'); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(legacyDir, { recursive: true }); await fs.writeFile( join(srcDir, 'new-api.ts'), ` export async function newApiCall(): Promise<any> { return await fetch('/api/v2/data'); } `, ); await fs.writeFile( join(legacyDir, 'old-api.ts'), ` export async function oldApiCall(): Promise<any> { return await fetch('/api/v1/data'); } `, ); const result = await tool.safeExecute({ path: tempDir, excludePatterns: ['**/legacy/**'], outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); if (result.content[0].text.startsWith('❌ Error')) { // If there's an error, the exclude pattern may not be working as expected // This is acceptable as the test verifies error handling expect(result.content[0].text).toContain('❌ Error'); } else { const jsonResult = JSON.parse(result.content[0].text); const functionNames = jsonResult.opportunities.map((opp: any) => opp.functionName); expect(functionNames).toContain('newApiCall'); expect(functionNames).not.toContain('oldApiCall'); } }); it('should filter by minimum score', async () => { const testFile = join(tempDir, 'mixed-functions.ts'); const content = ` // High-value function with API calls and good complexity export async function complexApiOperation( endpoint: string, data: any, options: RequestOptions = {} ): Promise<ApiResponse> { if (!endpoint) { throw new Error('Endpoint is required'); } try { const response = await fetch(endpoint, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', ...options.headers }, }); if (!response.ok) { throw new Error(\`API call failed: \${response.status}\`); } return await response.json(); } catch (error) { console.error('API operation failed:', error); throw error; } } // Simple function that might not score well function add(a: number, b: number): number { return a + b; } interface RequestOptions { headers?: Record<string, string>; } interface ApiResponse { data: any; status: string; } `; await fs.writeFile(testFile, content); // Test with high minimum score const highScoreResult = await tool.safeExecute({ path: tempDir, minScore: 80, outputFormat: 'json', }); expect(highScoreResult.content).toBeDefined(); expect(highScoreResult.content[0]).toBeDefined(); expect(highScoreResult.content[0].type).toBe('text'); const jsonResult = JSON.parse(highScoreResult.content[0].text); // Should only include high-scoring function expect(jsonResult.opportunities.length).toBeLessThanOrEqual(1); if (jsonResult.opportunities.length > 0) { expect(jsonResult.opportunities[0].functionName).toBe('complexApiOperation'); } // Test with low minimum score const lowScoreResult = await tool.safeExecute({ path: tempDir, minScore: 20, outputFormat: 'json', }); expect(lowScoreResult.content).toBeDefined(); expect(lowScoreResult.content[0]).toBeDefined(); expect(lowScoreResult.content[0].type).toBe('text'); const lowScoreJsonResult = JSON.parse(lowScoreResult.content[0].text); // Should include both functions (if they meet the lower threshold) const functionNames = lowScoreJsonResult.opportunities.map((opp: any) => opp.functionName); expect(functionNames).toContain('complexApiOperation'); // add() might still not meet the 20 point threshold due to being too simple }); it('should limit results with maxResults parameter', async () => { // Create multiple files with functions for (let i = 1; i <= 5; i++) { const testFile = join(tempDir, `api-${i}.ts`); const content = ` export async function apiFunction${i}(data: any): Promise<any> { const response = await fetch(\`/api/endpoint${i}\`, { method: 'POST', body: JSON.stringify(data), }); return response.json(); } `; await fs.writeFile(testFile, content); } const result = await tool.safeExecute({ path: tempDir, maxResults: 3, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); expect(jsonResult.opportunities.length).toBeLessThanOrEqual(3); }); it('should include test files when requested', async () => { const srcDir = join(tempDir, 'src'); const testDir = join(tempDir, 'test'); await fs.mkdir(srcDir, { recursive: true }); await fs.mkdir(testDir, { recursive: true }); await fs.writeFile( join(srcDir, 'service.ts'), ` export async function serviceFunction(): Promise<any> { return await fetch('/api/data'); } `, ); await fs.writeFile( join(testDir, 'helper.test.ts'), ` export async function testHelperWithApi(): Promise<any> { return await fetch('/test-api/mock'); } `, ); // Without including tests const withoutTests = await tool.safeExecute({ path: tempDir, includeTests: false, outputFormat: 'json', }); expect(withoutTests.content).toBeDefined(); expect(withoutTests.content[0]).toBeDefined(); expect(withoutTests.content[0].type).toBe('text'); const withoutTestsJsonResult = JSON.parse(withoutTests.content[0].text); const functionNames = withoutTestsJsonResult.opportunities.map( (opp: any) => opp.functionName, ); expect(functionNames).toContain('serviceFunction'); expect(functionNames).not.toContain('testHelperWithApi'); // With including tests const withTests = await tool.safeExecute({ path: tempDir, includeTests: true, outputFormat: 'json', }); expect(withTests.content).toBeDefined(); expect(withTests.content[0]).toBeDefined(); expect(withTests.content[0].type).toBe('text'); const withTestsJsonResult = JSON.parse(withTests.content[0].text); const withTestsFunctionNames = withTestsJsonResult.opportunities.map( (opp: any) => opp.functionName, ); expect(withTestsFunctionNames).toContain('serviceFunction'); // Test files may or may not be included depending on configuration expect(withTestsFunctionNames.length).toBeGreaterThanOrEqual(functionNames.length); }); }); describe('error handling', () => { it('should handle non-existent paths', async () => { const result = await tool.safeExecute({ path: '/non/existent/path', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].text).toContain('❌ Error'); expect(result.content[0].text).toContain('Invalid path'); }); it('should handle files instead of directories', async () => { const testFile = join(tempDir, 'single-file.ts'); await fs.writeFile(testFile, 'export function test() {}'); const result = await tool.safeExecute({ path: testFile, }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].text).toContain('❌ Error'); expect(result.content[0].text).toContain('Path is not a directory'); }); it('should handle empty directories', async () => { const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); expect(jsonResult.opportunities).toEqual([]); expect(jsonResult.summary.totalFiles).toBe(0); }); it('should handle directories with only non-analyzable files', async () => { await fs.writeFile(join(tempDir, 'readme.md'), '# README'); await fs.writeFile(join(tempDir, 'data.json'), '{"key": "value"}'); await fs.writeFile(join(tempDir, 'image.png'), Buffer.alloc(100)); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); expect(jsonResult.opportunities).toEqual([]); expect(jsonResult.summary.totalFiles).toBe(0); }); it('should handle files with syntax errors gracefully', async () => { const testFile = join(tempDir, 'broken.ts'); const content = ` export function brokenFunction(param: string { return param.toUpperCase( // Missing closing braces `; await fs.writeFile(testFile, content); const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const jsonResult = JSON.parse(result.content[0].text); // Should handle the error gracefully expect(jsonResult.summary.errors.length).toBeGreaterThanOrEqual(0); expect(jsonResult.summary.skippedFiles).toBeGreaterThanOrEqual(0); }); it('should validate input parameters', async () => { // Missing required path const missingPath = await tool.safeExecute({}); expect(missingPath.content[0].text).toContain('❌ Error'); // Invalid minScore const invalidScore = await tool.safeExecute({ path: tempDir, minScore: 150, // Over maximum }); expect(invalidScore.content[0].text).toContain('❌ Error'); // Invalid maxResults const invalidMax = await tool.safeExecute({ path: tempDir, maxResults: 0, // Under minimum }); expect(invalidMax.content[0].text).toContain('❌ Error'); // Invalid outputFormat const invalidFormat = await tool.safeExecute({ path: tempDir, outputFormat: 'invalid-format' as any, }); expect(invalidFormat.content[0].text).toContain('❌ Error'); }); }); describe('output formats', () => { beforeEach(async () => { // Create a sample file for consistent testing const testFile = join(tempDir, 'sample.ts'); const content = ` import axios from 'axios'; /** * Sample API function for testing output formats */ export async function sampleApiCall(endpoint: string): Promise<any> { const response = await axios.get(endpoint); return response.data; } `; await fs.writeFile(testFile, content); }); it('should format detailed output correctly', async () => { const result = await tool.safeExecute({ path: tempDir, outputFormat: 'detailed', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const text = result.content[0].text; expect(text).toContain('🔍 Codebase Analysis Results'); expect(text).toContain('sampleApiCall'); expect(text).toContain('Score:'); expect(text).toContain('Category:'); expect(text).toContain('Template:'); expect(text).toContain('Reasoning:'); expect(text).toContain('Implementation:'); expect(text).toContain('Analysis Summary'); }); it('should format summary output correctly', async () => { const result = await tool.safeExecute({ path: tempDir, outputFormat: 'summary', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); const text = result.content[0].text; expect(text).toContain('🔍 Analysis Summary'); expect(text).toContain('sampleApiCall'); expect(text).toContain('files analyzed'); // Should be more concise than detailed format expect(text.length).toBeLessThan(2000); }); it('should format JSON output correctly', async () => { const result = await tool.safeExecute({ path: tempDir, outputFormat: 'json', }); expect(result.content).toBeDefined(); expect(result.content[0]).toBeDefined(); expect(result.content[0].type).toBe('text'); // Should be valid JSON const jsonResult = JSON.parse(result.content[0].text); expect(jsonResult).toHaveProperty('opportunities'); expect(jsonResult).toHaveProperty('summary'); expect(jsonResult).toHaveProperty('recommendations'); expect(jsonResult).toHaveProperty('timestamp'); expect(Array.isArray(jsonResult.opportunities)).toBe(true); expect(jsonResult.opportunities.length).toBeGreaterThan(0); expect(jsonResult.opportunities[0]).toHaveProperty('functionName'); expect(jsonResult.opportunities[0]).toHaveProperty('score'); expect(jsonResult.opportunities[0]).toHaveProperty('category'); }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/conorluddy/ContextPods'

If you have feedback or need assistance with the MCP directory API, please join our Discord server