Skip to main content
Glama
auto-research-integration-live.test.ts15.7 kB
/** * Auto-Research Integration Tests * * Tests the end-to-end integration of auto-research triggering * with the task decomposition process. */ import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest'; import { DecompositionService } from '../../services/decomposition-service.js'; import { AutoResearchDetector } from '../../services/auto-research-detector.js'; import { AtomicTask } from '../../types/task.js'; import { ProjectContext } from '../../types/project-context.js'; import { OpenRouterConfig } from '../../../../types/workflow.js'; describe('Auto-Research Integration', () => { let decompositionService: DecompositionService; let autoResearchDetector: AutoResearchDetector; let mockConfig: OpenRouterConfig; beforeEach(() => { mockConfig = { apiKey: 'test-key', baseURL: 'https://openrouter.ai/api/v1', model: 'google/gemini-2.5-flash-preview-05-20', maxTokens: 4000, temperature: 0.7, timeout: 30000 }; decompositionService = new DecompositionService(mockConfig); autoResearchDetector = AutoResearchDetector.getInstance(); // Clear cache before each test autoResearchDetector.clearCache(); // Enable auto-research for tests (it's disabled by default) autoResearchDetector.updateConfig({ enabled: true }); // Mock LLM calls to avoid actual API calls in tests vi.mock('../../../../utils/llmHelper.js', () => ({ performFormatAwareLlmCall: vi.fn().mockResolvedValue({ isAtomic: true, reasoning: 'Task is atomic for testing', confidence: 0.9 }) })); }); afterEach(() => { autoResearchDetector.clearCache(); }); describe('Greenfield Project Detection', () => { it('should trigger auto-research for greenfield projects', async () => { const greenfieldTask: AtomicTask = { id: 'greenfield-task-1', title: 'Setup new React application', description: 'Create a new React application with TypeScript and modern tooling', type: 'development', priority: 'high', projectId: 'new-project', epicId: 'setup-epic', estimatedHours: 6, acceptanceCriteria: ['Application should compile without errors'], tags: ['react', 'typescript', 'setup'], filePaths: [], dependencies: [], createdAt: new Date(), updatedAt: new Date() }; const greenfieldContext: ProjectContext = { projectId: 'new-project', languages: ['typescript'], frameworks: ['react'], tools: ['vite', 'eslint'], existingTasks: [], codebaseSize: 'small', teamSize: 2, complexity: 'medium' }; // Mock context enrichment to return greenfield conditions (no files) const mockContextResult = { contextFiles: [], summary: { totalFiles: 0, // This triggers greenfield detection totalSize: 0, averageRelevance: 0, topFileTypes: [], gatheringTime: 100 }, metrics: { searchTime: 50, readTime: 0, scoringTime: 0, totalTime: 100, cacheHitRate: 0 } }; const contextSpy = vi.spyOn(decompositionService['contextService'], 'gatherContext') .mockResolvedValue(mockContextResult); // Mock the research integration to avoid actual API calls const mockResearchResult = { researchResults: [ { content: 'React best practices for TypeScript projects', metadata: { query: 'React TypeScript setup best practices' }, insights: { keyFindings: ['Use strict TypeScript configuration', 'Implement proper component patterns'], actionItems: ['Setup ESLint rules', 'Configure TypeScript paths'], recommendations: ['Use functional components', 'Implement proper error boundaries'] } } ], integrationMetrics: { researchTime: 1500, totalQueries: 1, successRate: 1.0 } }; // Spy on the research integration const researchSpy = vi.spyOn(decompositionService['researchIntegrationService'], 'enhanceDecompositionWithResearch') .mockResolvedValue(mockResearchResult); // Start decomposition const decompositionRequest = { task: greenfieldTask, context: greenfieldContext, sessionId: 'test-session-greenfield' }; const session = await decompositionService.startDecomposition(decompositionRequest); // Wait for completion await new Promise(resolve => setTimeout(resolve, 100)); // Verify research was triggered expect(researchSpy).toHaveBeenCalled(); // Verify session was created (check if session exists) expect(session).toBeDefined(); if (session) { expect(session.sessionId).toBe('test-session-greenfield'); expect(session.status).toBe('in_progress'); } contextSpy.mockRestore(); researchSpy.mockRestore(); }, 10000); }); describe('Task Complexity Detection', () => { it('should trigger auto-research for complex architectural tasks', async () => { const complexTask: AtomicTask = { id: 'complex-task-1', title: 'Implement microservices architecture', description: 'Design and implement a scalable microservices architecture with service discovery, load balancing, and fault tolerance', type: 'development', priority: 'high', projectId: 'existing-project', epicId: 'architecture-epic', estimatedHours: 20, acceptanceCriteria: ['Services should be independently deployable'], tags: ['architecture', 'microservices', 'scalability'], filePaths: ['src/services/', 'src/gateway/'], dependencies: [], createdAt: new Date(), updatedAt: new Date() }; const existingContext: ProjectContext = { projectId: 'existing-project', languages: ['typescript', 'javascript'], frameworks: ['express', 'nestjs'], tools: ['docker', 'kubernetes'], existingTasks: [], codebaseSize: 'large', teamSize: 5, complexity: 'high' }; // Mock context enrichment to return conditions that trigger complexity-based research const mockContextResult = { contextFiles: [ { filePath: 'src/service1.ts', relevance: { overallScore: 0.6 }, extension: '.ts', charCount: 1000 }, { filePath: 'src/service2.ts', relevance: { overallScore: 0.5 }, extension: '.ts', charCount: 800 } ], summary: { totalFiles: 2, // Some files but not enough for complex task totalSize: 1800, averageRelevance: 0.55, // Below threshold for complex task topFileTypes: ['.ts'], gatheringTime: 150 }, metrics: { searchTime: 75, readTime: 50, scoringTime: 25, totalTime: 150, cacheHitRate: 0.2 } }; const contextSpy = vi.spyOn(decompositionService['contextService'], 'gatherContext') .mockResolvedValue(mockContextResult); // Mock research integration const mockResearchResult = { researchResults: [ { content: 'Microservices architecture patterns and best practices', metadata: { query: 'microservices architecture design patterns' }, insights: { keyFindings: ['Use API Gateway pattern', 'Implement circuit breaker pattern'], actionItems: ['Setup service registry', 'Implement health checks'], recommendations: ['Use event-driven communication', 'Implement distributed tracing'] } } ], integrationMetrics: { researchTime: 2500, totalQueries: 2, successRate: 1.0 } }; const researchSpy = vi.spyOn(decompositionService['researchIntegrationService'], 'enhanceDecompositionWithResearch') .mockResolvedValue(mockResearchResult); const decompositionRequest = { task: complexTask, context: existingContext, sessionId: 'test-session-complex' }; const session = await decompositionService.startDecomposition(decompositionRequest); // Wait for completion await new Promise(resolve => setTimeout(resolve, 100)); // Verify research was triggered for complex task expect(researchSpy).toHaveBeenCalled(); expect(session).toBeDefined(); if (session) { expect(session.sessionId).toBe('test-session-complex'); } contextSpy.mockRestore(); researchSpy.mockRestore(); }, 10000); }); describe('Knowledge Gap Detection', () => { it('should trigger auto-research when context enrichment finds insufficient context', async () => { const taskWithLimitedContext: AtomicTask = { id: 'limited-context-task', title: 'Implement blockchain integration', description: 'Integrate with Ethereum blockchain for smart contract interactions', type: 'development', priority: 'medium', projectId: 'blockchain-project', epicId: 'blockchain-epic', estimatedHours: 8, acceptanceCriteria: ['Should connect to Ethereum mainnet'], tags: ['blockchain', 'ethereum', 'web3'], filePaths: [], dependencies: [], createdAt: new Date(), updatedAt: new Date() }; const limitedContext: ProjectContext = { projectId: 'blockchain-project', languages: ['javascript'], frameworks: ['express'], tools: ['npm'], existingTasks: [], codebaseSize: 'small', teamSize: 2, complexity: 'high' }; // Mock context enrichment to return limited results const mockContextResult = { contextFiles: [], summary: { totalFiles: 0, totalSize: 0, averageRelevance: 0, topFileTypes: [], gatheringTime: 100 }, metrics: { searchTime: 50, readTime: 0, scoringTime: 0, totalTime: 100, cacheHitRate: 0 } }; const contextSpy = vi.spyOn(decompositionService['contextService'], 'gatherContext') .mockResolvedValue(mockContextResult); const mockResearchResult = { researchResults: [ { content: 'Ethereum blockchain integration best practices', metadata: { query: 'Ethereum smart contract integration' }, insights: { keyFindings: ['Use Web3.js or Ethers.js', 'Implement proper error handling'], actionItems: ['Setup Web3 provider', 'Create contract interfaces'], recommendations: ['Use environment-specific networks', 'Implement gas optimization'] } } ], integrationMetrics: { researchTime: 2000, totalQueries: 1, successRate: 1.0 } }; const researchSpy = vi.spyOn(decompositionService['researchIntegrationService'], 'enhanceDecompositionWithResearch') .mockResolvedValue(mockResearchResult); const decompositionRequest = { task: taskWithLimitedContext, context: limitedContext, sessionId: 'test-session-knowledge-gap' }; const session = await decompositionService.startDecomposition(decompositionRequest); // Wait for completion await new Promise(resolve => setTimeout(resolve, 100)); // Verify research was triggered due to knowledge gap expect(researchSpy).toHaveBeenCalled(); expect(session).toBeDefined(); if (session) { expect(session.sessionId).toBe('test-session-knowledge-gap'); } contextSpy.mockRestore(); researchSpy.mockRestore(); }, 10000); }); describe('Auto-Research Configuration', () => { it('should respect auto-research configuration settings', async () => { // Disable auto-research autoResearchDetector.updateConfig({ enabled: false }); const task: AtomicTask = { id: 'config-test-task', title: 'Complex system integration', description: 'Integrate multiple complex systems with advanced architecture patterns', type: 'development', priority: 'high', projectId: 'config-test-project', epicId: 'config-epic', estimatedHours: 15, acceptanceCriteria: ['Systems should integrate seamlessly'], tags: ['integration', 'architecture', 'complex'], filePaths: [], dependencies: [], createdAt: new Date(), updatedAt: new Date() }; const context: ProjectContext = { projectId: 'config-test-project', languages: ['typescript'], frameworks: ['nestjs'], tools: ['docker'], existingTasks: [], codebaseSize: 'medium', teamSize: 3, complexity: 'high' }; const researchSpy = vi.spyOn(decompositionService['researchIntegrationService'], 'enhanceDecompositionWithResearch'); const decompositionRequest = { task, context, sessionId: 'test-session-config' }; const session = await decompositionService.startDecomposition(decompositionRequest); // Wait for completion await new Promise(resolve => setTimeout(resolve, 100)); // Verify research was NOT triggered due to disabled config expect(researchSpy).not.toHaveBeenCalled(); expect(session).toBeDefined(); if (session) { expect(session.sessionId).toBe('test-session-config'); } // Re-enable for other tests autoResearchDetector.updateConfig({ enabled: true }); researchSpy.mockRestore(); }, 10000); }); describe('Performance Metrics', () => { it('should track auto-research performance metrics', async () => { const initialMetrics = autoResearchDetector.getPerformanceMetrics(); const initialEvaluations = initialMetrics.totalEvaluations; const task: AtomicTask = { id: 'metrics-task', title: 'Simple task', description: 'A simple task for metrics testing', type: 'development', priority: 'low', projectId: 'metrics-project', epicId: 'metrics-epic', estimatedHours: 1, acceptanceCriteria: ['Task should complete'], tags: ['simple'], filePaths: [], dependencies: [], createdAt: new Date(), updatedAt: new Date() }; const context: ProjectContext = { projectId: 'metrics-project', languages: ['javascript'], frameworks: ['express'], tools: ['npm'], existingTasks: [], codebaseSize: 'small', teamSize: 1, complexity: 'low' }; const decompositionRequest = { task, context, sessionId: 'test-session-metrics' }; await decompositionService.startDecomposition(decompositionRequest); // Wait for completion await new Promise(resolve => setTimeout(resolve, 100)); const finalMetrics = autoResearchDetector.getPerformanceMetrics(); // Verify metrics were updated expect(finalMetrics.totalEvaluations).toBeGreaterThan(initialEvaluations); expect(finalMetrics.averageEvaluationTime).toBeGreaterThan(0); expect(finalMetrics.cacheHitRate).toBeGreaterThanOrEqual(0); }, 10000); }); });

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/freshtechbro/vibe-coder-mcp'

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