Skip to main content
Glama
sascodiego

MCP Vibe Coding Knowledge Graph

by sascodiego
mcp-workflow.test.js22.7 kB
/** * MCP Workflow Integration Tests * CONTEXT: End-to-end testing of complete MCP tool workflows * REASON: Ensure all components work together correctly in realistic scenarios * CHANGE: Comprehensive integration testing for MCP server and tools * PREVENTION: Integration failures, workflow breakdowns, system inconsistencies */ import { jest } from '@jest/globals'; import { MCPServer } from '../../src/server.js'; import { MockKuzuClient, createMockEnvironment, testUtils } from '../mocks/index.js'; import { mockLogger } from '../mocks/index.js'; import fs from 'fs/promises'; import path from 'path'; // Mock dependencies jest.mock('../../src/utils/logger.js', () => ({ logger: mockLogger })); jest.mock('fs/promises'); describe('MCP Workflow Integration Tests', () => { let mcpServer; let mockEnv; let testCodebase; beforeEach(async () => { // Create mock environment mockEnv = createMockEnvironment(); // Create test codebase structure testCodebase = { '/src/services/UserService.js': ` /** * CONTEXT: User service for managing user data * REASON: Centralized user management with validation * CHANGE: Service layer implementation following clean architecture * PREVENTION: Scattered user logic and validation inconsistencies */ import { DatabaseManager } from '../database/DatabaseManager.js'; import { UserValidator } from '../validators/UserValidator.js'; export class UserService { constructor() { this.db = new DatabaseManager(); this.validator = new UserValidator(); } async createUser(userData) { if (!this.validator.validate(userData)) { throw new Error('Invalid user data'); } const user = await this.db.insert('users', userData); return user; } async getUser(id) { return await this.db.findById('users', id); } async updateUser(id, updates) { const existingUser = await this.getUser(id); if (!existingUser) { throw new Error('User not found'); } const validatedUpdates = this.validator.validateUpdates(updates); return await this.db.update('users', id, validatedUpdates); } async deleteUser(id) { const user = await this.getUser(id); if (!user) { throw new Error('User not found'); } return await this.db.delete('users', id); } } `, '/src/database/DatabaseManager.js': ` /** * CONTEXT: Database abstraction layer * REASON: Centralized database operations with connection pooling * CHANGE: Database manager with transaction support * PREVENTION: Database connection leaks and inconsistent queries */ export class DatabaseManager { constructor() { this.connection = null; this.transactionActive = false; } async connect() { // Database connection logic this.connection = { connected: true }; } async insert(table, data) { return { id: 'generated-id', ...data }; } async findById(table, id) { return { id, name: 'Test User' }; } async update(table, id, data) { return { id, ...data, updatedAt: new Date() }; } async delete(table, id) { return { deleted: true, id }; } } `, '/src/validators/UserValidator.js': ` /** * CONTEXT: User data validation logic * REASON: Consistent validation rules across the application * CHANGE: Centralized validation with comprehensive rules * PREVENTION: Invalid data persistence and security vulnerabilities */ export class UserValidator { validate(userData) { if (!userData.name || userData.name.trim().length === 0) { return false; } if (!userData.email || !this.isValidEmail(userData.email)) { return false; } return true; } validateUpdates(updates) { const validatedUpdates = {}; if (updates.name && updates.name.trim().length > 0) { validatedUpdates.name = updates.name.trim(); } if (updates.email && this.isValidEmail(updates.email)) { validatedUpdates.email = updates.email; } return validatedUpdates; } isValidEmail(email) { const emailRegex = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; return emailRegex.test(email); } } `, '/package.json': JSON.stringify({ name: 'test-project', version: '1.0.0', type: 'module', dependencies: { 'express': '^4.18.0' } }, null, 2) }; // Mock file system fs.readFile.mockImplementation((filePath) => { const content = testCodebase[filePath]; if (content) { return Promise.resolve(content); } throw new Error(`File not found: ${filePath}`); }); fs.readdir.mockResolvedValue(Object.keys(testCodebase)); fs.stat.mockResolvedValue({ isFile: () => true, size: 1000 }); // Initialize MCP Server with mock environment mcpServer = new MCPServer({ kuzu: mockEnv.kuzuClient, logger: mockEnv.logger, config: { kuzu: { databasePath: '/test/db' }, logging: { level: 'error' } } }); // Setup mock data in database await setupMockKnowledgeGraph(mockEnv.kuzuClient); }); afterEach(async () => { if (mcpServer) { await mcpServer.stop(); } jest.clearAllMocks(); }); async function setupMockKnowledgeGraph(kuzuClient) { // Setup patterns kuzuClient.setMockData('Pattern', [ { name: 'Service Layer', description: 'Service layer pattern for business logic', category: 'architectural', implementation: 'class Service { constructor() {} }', rules: ['single_responsibility', 'dependency_injection'] }, { name: 'Repository', description: 'Repository pattern for data access', category: 'data_access', implementation: 'class Repository { findById() {} }', rules: ['data_abstraction'] }, { name: 'Validator', description: 'Validation pattern for input checking', category: 'validation', implementation: 'class Validator { validate() {} }', rules: ['input_validation'] } ]); // Setup rules kuzuClient.setMockData('Rule', [ { description: 'Functions should not exceed 50 lines', type: 'function_length', severity: 'warning', category: 'maintainability' }, { description: 'Classes should have single responsibility', type: 'single_responsibility', severity: 'error', category: 'design' }, { description: 'Validate all user inputs', type: 'input_validation', severity: 'critical', category: 'security' } ]); // Setup standards kuzuClient.setMockData('Standard', [ { name: 'camelCase', value: 'functions', type: 'naming', category: 'coding_standards' }, { name: 'PascalCase', value: 'classes', type: 'naming', category: 'coding_standards' } ]); } describe('Complete Analysis Workflow', () => { test('should analyze entire codebase and update knowledge graph', async () => { const result = await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project', includeGitHistory: false, maxDepth: 10 }); expect(result.content).toBeDefined(); const analysis = JSON.parse(result.content[0].text); expect(analysis.summary.totalFiles).toBeGreaterThan(0); expect(analysis.summary.entitiesExtracted).toBeGreaterThan(0); expect(analysis.summary.patternsDetected).toBeDefined(); expect(analysis.languages).toContain('javascript'); // Verify entities were stored in knowledge graph const storedEntities = mockEnv.kuzuClient.getMockData('CodeEntity'); expect(storedEntities.length).toBeGreaterThan(0); }); test('should detect and store patterns from codebase', async () => { // First analyze the codebase await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project', includeGitHistory: false }); // Then query for detected patterns const result = await mcpServer.handlers.context.queryContextForTask({ taskDescription: 'Create a new service following existing patterns', entityTypes: ['class', 'service'], depth: 2 }); const context = JSON.parse(result.content[0].text); expect(context.context.patterns).toBeDefined(); expect(context.context.patterns.length).toBeGreaterThan(0); }); test('should maintain relationships between entities', async () => { await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); // Check for dependency relationships const relationships = mockEnv.kuzuClient.getMockData('relationships'); const dependencyRels = relationships.get('DEPENDS_ON') || []; expect(dependencyRels.length).toBeGreaterThan(0); // UserService should depend on DatabaseManager and UserValidator const userServiceDeps = dependencyRels.filter(rel => rel.from.includes('UserService') ); expect(userServiceDeps.length).toBeGreaterThan(0); }); }); describe('Code Generation Workflow', () => { test('should generate code with context from knowledge graph', async () => { // Setup knowledge graph with existing patterns await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); // Generate new code using context const result = await mcpServer.handlers.codeGeneration.generateWithContext({ requirement: 'Create a ProductService following existing service patterns', patternsToApply: ['Service Layer', 'Repository'], constraints: { language: 'javascript', includeValidation: true } }); const guidance = JSON.parse(result.content[0].text); expect(guidance.requirement).toContain('ProductService'); expect(guidance.guidance.patterns).toBeDefined(); expect(guidance.guidance.patterns.length).toBeGreaterThan(0); expect(guidance.guidance.implementation).toBeDefined(); expect(guidance.context.patterns).toBeDefined(); }); test('should suggest refactoring based on detected issues', async () => { // Create a problematic code entity mockEnv.kuzuClient.setMockData('CodeEntity', [ { name: 'LegacyService', type: 'class', filePath: '/src/legacy/LegacyService.js', complexity: 15 } ]); mockEnv.kuzuClient.setMockData('TechnicalDebt', [ { type: 'complexity', severity: 'high', description: 'High cyclomatic complexity detected', entity: 'LegacyService' } ]); const result = await mcpServer.handlers.codeGeneration.suggestRefactoring({ codeEntity: 'LegacyService', improvementGoals: ['reduce complexity', 'improve maintainability'], preserveBehavior: true }); const suggestions = JSON.parse(result.content[0].text); expect(suggestions.codeEntity).toBe('LegacyService'); expect(suggestions.suggestions).toBeDefined(); expect(suggestions.improvementGoals).toContain('reduce complexity'); }); }); describe('Validation Workflow', () => { test('should validate code against established patterns and rules', async () => { // Setup knowledge graph await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); const codeToValidate = ` export class ProductService { constructor() { this.db = new DatabaseManager(); } async createProduct(productData) { // Missing validation! return await this.db.insert('products', productData); } } `; const result = await mcpServer.handlers.validation.validateAgainstKG({ codeSnippet: codeToValidate, validationTypes: ['patterns', 'rules', 'standards'], strictMode: true }); const validation = JSON.parse(result.content[0].text); expect(validation.validationResults).toBeDefined(); expect(validation.overallScore).toBeDefined(); expect(validation.recommendations).toBeDefined(); // Should detect missing validation const hasValidationIssue = validation.validationResults.rules?.violations?.some( v => v.message.includes('validation') || v.rule.includes('validation') ); expect(hasValidationIssue).toBe(true); }); test('should detect technical debt across codebase', async () => { // Setup codebase analysis await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); const result = await mcpServer.handlers.validation.detectTechnicalDebt({ scope: 'project', debtTypes: ['complexity', 'duplication', 'coupling'] }); const debtAnalysis = JSON.parse(result.content[0].text); expect(debtAnalysis.scope).toBe('project'); expect(debtAnalysis.analysis).toBeDefined(); expect(debtAnalysis.recommendations).toBeDefined(); }); }); describe('Context Extraction Workflow', () => { test('should extract and store structured comments', async () => { const result = await mcpServer.handlers.context.extractFromCode({ filePath: '/src/services/UserService.js' }); const extraction = JSON.parse(result.content[0].text); expect(extraction.extracted.entities).toBeGreaterThan(0); expect(extraction.extracted.structuredComments).toBeGreaterThan(0); // Verify structured comments were stored in knowledge graph const storedEntities = mockEnv.kuzuClient.getMockData('CodeEntity'); const entityWithContext = storedEntities.find(e => e.context && e.context.includes('User service') ); expect(entityWithContext).toBeDefined(); }); test('should provide relevant context for development tasks', async () => { // Setup knowledge graph await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); const result = await mcpServer.handlers.context.queryContextForTask({ taskDescription: 'Implement user authentication service', entityTypes: ['service', 'validator'], depth: 3 }); const context = JSON.parse(result.content[0].text); expect(context.taskDescription).toContain('authentication'); expect(context.context.patterns).toBeDefined(); expect(context.context.relatedCode).toBeDefined(); expect(context.relevanceScore).toBeGreaterThan(0); }); }); describe('Knowledge Graph Updates', () => { test('should update knowledge graph with new patterns and relationships', async () => { const newAnalysis = { entities: [ { id: 'new-service-1', type: 'class', name: 'NotificationService', filePath: '/src/services/NotificationService.js' } ], patterns: [ { name: 'Observer', confidence: 0.9, entities: ['NotificationService'] } ], relationships: [ { from: 'NotificationService', to: 'UserService', type: 'NOTIFIES', confidence: 0.85 } ] }; const result = await mcpServer.handlers.knowledgeGraph.updateFromCode({ codeAnalysis: newAnalysis, decisions: [ { decision: 'Implemented Observer pattern for notifications', rationale: 'Loose coupling between services', impact: 'Improved maintainability' } ] }); const update = JSON.parse(result.content[0].text); expect(update.summary.entitiesCreated).toBeGreaterThan(0); expect(update.summary.relationshipsCreated).toBeGreaterThan(0); expect(update.summary.patternsDetected).toBeGreaterThan(0); }); test('should maintain knowledge graph consistency during updates', async () => { // Initial analysis await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); const initialEntities = mockEnv.kuzuClient.getMockData('CodeEntity'); const initialCount = initialEntities.length; // Update with new code await mcpServer.handlers.knowledgeGraph.updateFromCode({ codeAnalysis: { entities: [ { id: 'new-entity', type: 'function', name: 'newFunction' } ] } }); const updatedEntities = mockEnv.kuzuClient.getMockData('CodeEntity'); expect(updatedEntities.length).toBe(initialCount + 1); }); }); describe('Error Handling and Recovery', () => { test('should handle database connection failures gracefully', async () => { // Simulate database failure mockEnv.kuzuClient.simulateError('connection'); await expect(mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' })).rejects.toThrow('Connection lost'); expect(mockLogger.error).toHaveBeenCalled(); }); test('should handle malformed code gracefully', async () => { const malformedCode = { '/src/broken.js': 'class MissingBrace { method() { console.log("broken"' }; fs.readFile.mockImplementation((filePath) => { if (malformedCode[filePath]) { return Promise.resolve(malformedCode[filePath]); } return Promise.reject(new Error('File not found')); }); const result = await mcpServer.handlers.validation.validateAgainstKG({ codeSnippet: malformedCode['/src/broken.js'], validationTypes: ['patterns'] }); // Should handle parsing errors gracefully expect(result.content).toBeDefined(); const validation = JSON.parse(result.content[0].text); expect(validation.validationResults).toBeDefined(); }); test('should recover from query failures', async () => { // Setup successful initial state await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); // Simulate query failure mockEnv.kuzuClient.query = jest.fn().mockRejectedValue(new Error('Query failed')); await expect(mcpServer.handlers.context.queryContextForTask({ taskDescription: 'Test task' })).rejects.toThrow('Query failed'); // Should log error expect(mockLogger.error).toHaveBeenCalled(); }); }); describe('Performance and Scalability', () => { test('should handle large codebase analysis efficiently', async () => { // Create large codebase const largeCodebase = {}; for (let i = 0; i < 100; i++) { largeCodebase[`/src/services/Service${i}.js`] = ` export class Service${i} { method${i}() { return "service${i}"; } } `; } fs.readFile.mockImplementation((filePath) => { if (largeCodebase[filePath]) { return Promise.resolve(largeCodebase[filePath]); } throw new Error('File not found'); }); fs.readdir.mockResolvedValue(Object.keys(largeCodebase)); global.performanceMonitor.start('large-codebase-analysis'); const result = await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/large-project', maxDepth: 10 }); const duration = global.performanceMonitor.end('large-codebase-analysis'); expect(result.content).toBeDefined(); expect(duration).toBeWithinPerformanceThreshold(10000); // 10 seconds max }); test('should handle concurrent tool requests', async () => { // Setup knowledge graph await mcpServer.handlers.initialization.analyzeCodebase({ codebasePath: '/test/project' }); // Execute multiple tools concurrently const concurrentRequests = [ mcpServer.handlers.context.queryContextForTask({ taskDescription: 'Task 1' }), mcpServer.handlers.validation.validateAgainstKG({ codeSnippet: 'function test() {}', validationTypes: ['patterns'] }), mcpServer.handlers.codeGeneration.generateWithContext({ requirement: 'Generate service', patternsToApply: ['Service Layer'] }) ]; const results = await Promise.all(concurrentRequests); expect(results).toHaveLength(3); results.forEach(result => { expect(result.content).toBeDefined(); }); }); }); });

Latest Blog Posts

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/sascodiego/KGsMCP'

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