Skip to main content
Glama

mcp-adr-analysis-server

by tosin2013
smart-code-linking.test.ts18.9 kB
/** * Integration tests for Smart Code Linking feature * Tests the complete workflow from ADR analysis to related code discovery */ import * as fs from 'fs/promises'; import * as path from 'path'; import * as os from 'os'; describe('Smart Code Linking - Integration Tests', () => { let tempDir: string; let findFiles: any; let findRelatedCode: any; let analyzeProjectStructure: any; beforeAll(async () => { // Import all the functions we need to test const module = await import('../../src/utils/file-system.js'); findFiles = module.findFiles; findRelatedCode = module.findRelatedCode; analyzeProjectStructure = module.analyzeProjectStructure; // Create a temporary directory for integration tests tempDir = path.join(os.tmpdir(), 'smart-code-linking-integration-test'); await fs.mkdir(tempDir, { recursive: true }); }); afterAll(async () => { // Clean up temporary directory try { await fs.rm(tempDir, { recursive: true, force: true }); } catch (error) { console.warn('Failed to clean up temp directory:', error); } }); beforeEach(async () => { // Clean up any existing files in temp directory try { const files = await fs.readdir(tempDir); await Promise.all( files.map(file => fs.rm(path.join(tempDir, file), { recursive: true, force: true })) ); } catch { // Ignore cleanup errors } }); describe('End-to-end Smart Code Linking workflow', () => { it('should discover project structure and find related code', async () => { // Setup: Create a mock project structure const projectStructure = { 'package.json': JSON.stringify({ name: 'test-project', dependencies: { express: '^4.18.0', typescript: '^4.9.0', }, }), 'src/services/UserService.ts': ` export class UserService { async authenticateUser(username: string, password: string) { // Authentication logic return true; } }`, 'src/controllers/AuthController.ts': ` import { UserService } from '../services/UserService'; export class AuthController { private userService = new UserService(); async login(req: any, res: any) { // Login logic } }`, 'src/models/User.ts': ` export interface User { id: string; username: string; email: string; }`, 'docs/adrs/001-authentication.md': `# Authentication System ## Status Accepted ## Context We need to implement user authentication for the application. ## Decision We will use Express.js with JWT tokens for authentication. The UserService class will handle authentication logic. ## Consequences - Secure user authentication - JWT token-based sessions - Integration with Express middleware `, }; // Create the mock project files for (const [filePath, content] of Object.entries(projectStructure)) { const fullPath = path.join(tempDir, filePath); const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(fullPath, content); } // Step 1: Analyze project structure const projectAnalysis = await analyzeProjectStructure(tempDir); expect(projectAnalysis).toMatchObject({ rootPath: tempDir, files: expect.any(Array), technologies: expect.arrayContaining(['Node.js']), patterns: expect.any(Array), summary: expect.stringContaining('Node.js'), totalFiles: expect.any(Number), totalDirectories: expect.any(Number), }); expect(projectAnalysis.files.length).toBeGreaterThan(0); expect(projectAnalysis.technologies).toContain('Express'); // TypeScript may or may not be detected depending on file analysis logic const hasModernTech = projectAnalysis.technologies.some(tech => ['TypeScript', 'Express', 'Node.js'].includes(tech) ); expect(hasModernTech).toBe(true); // Step 2: Find TypeScript files using fast-glob const tsFiles = await findFiles(tempDir, ['**/*.ts'], { includeContent: true, limit: 10, }); expect(tsFiles.files).toHaveLength(3); // UserService, AuthController, User model expect(tsFiles.files.every(f => f.extension === '.ts')).toBe(true); expect(tsFiles.files.every(f => f.content && f.content.length > 0)).toBe(true); // Step 3: Use Smart Code Linking to find related code for the ADR const adrContent = await fs.readFile( path.join(tempDir, 'docs/adrs/001-authentication.md'), 'utf-8' ); const relatedCodeResult = await findRelatedCode( 'docs/adrs/001-authentication.md', adrContent, tempDir, { useAI: false, // Disable AI to avoid external dependencies useRipgrep: false, // Use fast-glob for consistency maxFiles: 10, includeContent: true, } ); expect(relatedCodeResult).toMatchObject({ adrPath: 'docs/adrs/001-authentication.md', relatedFiles: expect.any(Array), keywords: expect.any(Array), searchPatterns: expect.any(Array), confidence: expect.any(Number), }); // Should find authentication-related files expect(relatedCodeResult.keywords).toEqual(expect.arrayContaining(['express'])); // Should extract some relevant keywords from the ADR const hasAuthKeywords = relatedCodeResult.keywords.some( keyword => keyword.toLowerCase().includes('express') || keyword.toLowerCase().includes('authentication') || keyword.toLowerCase().includes('jwt') ); expect(hasAuthKeywords).toBe(true); // May or may not find files depending on keyword matching expect(relatedCodeResult.relatedFiles).toEqual(expect.any(Array)); // If files are found, verify the UserService file if present if (relatedCodeResult.relatedFiles.length > 0) { const userServiceFile = relatedCodeResult.relatedFiles.find(f => f.name.includes('UserService') ); if (userServiceFile?.content) { expect(userServiceFile.content).toContain('authenticateUser'); } // Validate all returned files have correct structure relatedCodeResult.relatedFiles.forEach(file => { expect(file).toHaveProperty('path'); expect(file).toHaveProperty('name'); expect(file).toHaveProperty('extension'); expect(file).toHaveProperty('size'); }); } }); it('should handle real project analysis on current codebase', async () => { // Test on the actual project to verify real-world functionality const currentProjectPath = process.cwd(); // Step 1: Analyze the current project structure const analysis = await analyzeProjectStructure(currentProjectPath); expect(analysis.technologies).toContain('Node.js'); expect(analysis.totalFiles).toBeGreaterThan(50); // This project has many files // Check for common technologies (may vary based on current state) const expectedTechs = ['TypeScript', 'Jest', 'Node.js']; const foundTechs = expectedTechs.filter(tech => analysis.technologies.includes(tech)); expect(foundTechs.length).toBeGreaterThan(0); // Step 2: Find ADR files in the project const adrFiles = await findFiles(currentProjectPath, ['docs/adrs/*.md'], { includeContent: true, limit: 5, }); // Step 3: If ADRs exist, test Smart Code Linking if (adrFiles.files.length > 0) { const firstAdr = adrFiles.files[0]; expect(firstAdr.content).toBeDefined(); const relatedCode = await findRelatedCode( firstAdr.path, firstAdr.content!, currentProjectPath, { useAI: false, useRipgrep: false, maxFiles: 5, includeContent: false, } ); expect(relatedCode.relatedFiles).toEqual(expect.any(Array)); expect(relatedCode.keywords).toEqual(expect.any(Array)); expect(relatedCode.confidence).toBeGreaterThanOrEqual(0); expect(relatedCode.confidence).toBeLessThanOrEqual(1); } // Step 4: Test finding TypeScript files const tsFiles = await findFiles(currentProjectPath, ['src/**/*.ts'], { limit: 20, }); expect(tsFiles.files.length).toBeGreaterThan(0); expect(tsFiles.files.every(f => f.extension === '.ts')).toBe(true); }); }); describe('Cross-tool integration', () => { it('should integrate with multiple tools for comprehensive analysis', async () => { // Create a project with multiple components const complexProject = { 'package.json': JSON.stringify({ name: 'complex-project', dependencies: { react: '^18.0.0', express: '^4.18.0', '@types/node': '^18.0.0', }, }), 'src/components/LoginForm.tsx': ` import React from 'react'; export const LoginForm: React.FC = () => { return <form>Login Form</form>; };`, 'src/api/authRoutes.ts': ` import express from 'express'; export const authRouter = express.Router(); authRouter.post('/login', (req, res) => { // Login endpoint });`, 'src/services/DatabaseService.ts': ` export class DatabaseService { async connectToDatabase() { // Database connection logic } }`, 'docs/adrs/002-frontend-framework.md': `# Frontend Framework Choice ## Decision We choose React for the frontend due to its component-based architecture and strong TypeScript support. ## Implementation - LoginForm component for authentication - Express API routes for backend - DatabaseService for data persistence `, }; // Create the complex project for (const [filePath, content] of Object.entries(complexProject)) { const fullPath = path.join(tempDir, filePath); const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(fullPath, content); } // Test 1: Project structure analysis const structure = await analyzeProjectStructure(tempDir); expect(structure.technologies).toEqual( expect.arrayContaining(['React', 'Express', 'Node.js']) ); // Test 2: Component-specific file discovery const reactFiles = await findFiles(tempDir, ['**/*.tsx', '**/*.jsx'], { includeContent: true, }); expect(reactFiles.files).toHaveLength(1); expect(reactFiles.files[0].name).toBe('LoginForm.tsx'); // Test 3: Smart Code Linking for ADR const adrContent = await fs.readFile( path.join(tempDir, 'docs/adrs/002-frontend-framework.md'), 'utf-8' ); const linkedCode = await findRelatedCode( 'docs/adrs/002-frontend-framework.md', adrContent, tempDir, { useAI: false, useRipgrep: false, maxFiles: 5, includeContent: true, } ); // Should find some related files expect(linkedCode.relatedFiles).toEqual(expect.any(Array)); // Check if at least some expected files are found const foundFiles = linkedCode.relatedFiles.map(f => f.name); if (foundFiles.length > 0) { // At least one of the expected files should be found const expectedFiles = ['LoginForm.tsx', 'authRoutes.ts', 'DatabaseService.ts']; const hasExpectedFile = expectedFiles.some(expected => foundFiles.includes(expected)); expect(hasExpectedFile).toBe(true); } // Test 4: Verify content is included and accurate (if files are found) if (linkedCode.relatedFiles.length > 0) { const loginForm = linkedCode.relatedFiles.find(f => f.name === 'LoginForm.tsx'); if (loginForm?.content) { expect(loginForm.content).toContain('LoginForm'); expect(loginForm.content).toContain('React.FC'); } } }); }); describe('Performance and scalability', () => { it('should handle moderate-sized projects efficiently', async () => { // Create a moderate-sized project structure const fileCount = 20; const files: Record<string, string> = { 'package.json': JSON.stringify({ name: 'perf-test' }), }; // Generate multiple files for (let i = 0; i < fileCount; i++) { files[`src/module${i}/Service${i}.ts`] = ` export class Service${i} { async process() { return 'result${i}'; } }`; files[`src/module${i}/Controller${i}.ts`] = ` import { Service${i} } from './Service${i}'; export class Controller${i} { private service = new Service${i}(); }`; } // Create all files for (const [filePath, content] of Object.entries(files)) { const fullPath = path.join(tempDir, filePath); const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(fullPath, content); } const startTime = Date.now(); // Test file discovery performance const allFiles = await findFiles(tempDir, ['**/*.ts'], { limit: 50 }); expect(allFiles.files.length).toBe(fileCount * 2); // Services + Controllers // Test Smart Code Linking performance const adrContent = 'System architecture using Service5 and Controller10 classes.'; const related = await findRelatedCode('docs/adrs/perf-test.md', adrContent, tempDir, { useAI: false, useRipgrep: false, maxFiles: 10, }); const duration = Date.now() - startTime; // Should complete within reasonable time (5 seconds for moderate project) expect(duration).toBeLessThan(5000); // Should find some files (may not find exact matches due to simple keyword extraction) expect(related.relatedFiles).toEqual(expect.any(Array)); expect(related.keywords.length).toBeGreaterThan(0); // If files are found, verify structure if (related.relatedFiles.length > 0) { related.relatedFiles.forEach(file => { expect(file).toHaveProperty('path'); expect(file).toHaveProperty('name'); expect(file).toHaveProperty('extension', '.ts'); }); } }); it('should handle concurrent Smart Code Linking requests', async () => { // Create a simple project for concurrent testing const simpleProject = { 'src/ServiceA.ts': 'export class ServiceA {}', 'src/ServiceB.ts': 'export class ServiceB {}', 'src/ServiceC.ts': 'export class ServiceC {}', }; for (const [filePath, content] of Object.entries(simpleProject)) { const fullPath = path.join(tempDir, filePath); const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(fullPath, content); } // Create multiple concurrent requests const requests = [ findRelatedCode('adr1.md', 'ServiceA implementation', tempDir, { useAI: false, useRipgrep: false, }), findRelatedCode('adr2.md', 'ServiceB architecture', tempDir, { useAI: false, useRipgrep: false, }), findRelatedCode('adr3.md', 'ServiceC design', tempDir, { useAI: false, useRipgrep: false }), ]; const startTime = Date.now(); const results = await Promise.all(requests); const duration = Date.now() - startTime; // All requests should complete successfully expect(results).toHaveLength(3); results.forEach(result => { expect(result.relatedFiles).toEqual(expect.any(Array)); expect(result.confidence).toBeGreaterThanOrEqual(0); }); // Should complete concurrently (faster than sequential) expect(duration).toBeLessThan(3000); }); }); describe('Error resilience and edge cases', () => { it('should handle mixed file types and complex directory structures', async () => { const mixedProject = { 'README.md': '# Mixed Project', 'src/deep/nested/structure/DeepService.ts': 'export class DeepService {}', 'config/app.json': '{"name": "app"}', 'scripts/build.sh': '#!/bin/bash\necho "Building..."', '.gitignore': 'node_modules/\n*.log', 'docs/api.md': '# API Documentation', 'tests/DeepService.test.ts': 'import { DeepService } from "../src/deep/nested/structure/DeepService";', }; for (const [filePath, content] of Object.entries(mixedProject)) { const fullPath = path.join(tempDir, filePath); const dir = path.dirname(fullPath); await fs.mkdir(dir, { recursive: true }); await fs.writeFile(fullPath, content); } // Test file discovery with different patterns const tsFiles = await findFiles(tempDir, ['**/*.ts'], { limit: 10 }); const mdFiles = await findFiles(tempDir, ['**/*.md'], { limit: 10 }); const allFiles = await findFiles(tempDir, ['**/*'], { limit: 20 }); expect(tsFiles.files).toHaveLength(2); // DeepService.ts and test file expect(mdFiles.files).toHaveLength(2); // README.md and api.md expect(allFiles.files.length).toBeGreaterThan(4); // Test Smart Code Linking with deep structure const adrContent = 'Deep nested service architecture using DeepService class.'; const result = await findRelatedCode('docs/adrs/deep-structure.md', adrContent, tempDir, { useAI: false, useRipgrep: false, maxFiles: 5, }); // Should find the deeply nested service const deepService = result.relatedFiles.find(f => f.path.includes('deep/nested/structure/DeepService.ts') ); expect(deepService).toBeDefined(); }); it('should gracefully handle empty directories and missing files', async () => { // Create empty directory structure await fs.mkdir(path.join(tempDir, 'empty/dir/structure'), { recursive: true }); // Test analysis of empty project const emptyAnalysis = await analyzeProjectStructure(tempDir); expect(emptyAnalysis.files).toHaveLength(0); expect(emptyAnalysis.technologies).toHaveLength(0); // Test file discovery in empty project const emptyFiles = await findFiles(tempDir, ['**/*.ts'], { limit: 10 }); expect(emptyFiles.files).toHaveLength(0); // Test Smart Code Linking with no files const emptyResult = await findRelatedCode( 'docs/adrs/empty-project.md', 'Empty project analysis', tempDir, { useAI: false, useRipgrep: false, maxFiles: 5, } ); expect(emptyResult.relatedFiles).toHaveLength(0); expect(emptyResult.confidence).toBe(0.2); // Low confidence for empty results }); }); });

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/tosin2013/mcp-adr-analysis-server'

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