Skip to main content
Glama
research-orchestrator-delegation.test.ts9.69 kB
/** * Tests for Research Orchestrator LLM Delegation * * These tests verify the new createResearchPlan method that returns * research plans for LLM delegation instead of executing research internally. */ import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import { ResearchOrchestrator } from '../../src/utils/research-orchestrator.js'; import { resetResearchTaskManager } from '../../src/utils/research-task-integration.js'; import { resetTaskManager } from '../../src/utils/task-manager.js'; describe('ResearchOrchestrator LLM Delegation', () => { let orchestrator: ResearchOrchestrator; beforeEach(async () => { // Suppress deprecation warning in tests vi.spyOn(console, 'warn').mockImplementation(() => {}); await resetTaskManager(); await resetResearchTaskManager(); orchestrator = new ResearchOrchestrator('/test/project', 'docs/adrs'); }); afterEach(() => { vi.restoreAllMocks(); }); describe('createResearchPlan', () => { it('should create a research plan with task ID', async () => { const { taskId, plan, tracker } = await orchestrator.createResearchPlan( 'How does authentication work?' ); expect(taskId).toBeDefined(); expect(typeof taskId).toBe('string'); expect(taskId.length).toBeGreaterThan(0); expect(plan).toBeDefined(); expect(tracker).toBeDefined(); }); it('should return a plan with the research question', async () => { const question = 'What database is used in this project?'; const { plan } = await orchestrator.createResearchPlan(question); expect(plan.question).toBe(question); }); it('should include all standard phases in the plan', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); // Should have at least 3 phases: project_files_search, knowledge_graph_query, environment_analysis expect(plan.phases.length).toBeGreaterThanOrEqual(3); const phaseNames = plan.phases.map(p => p.phase); expect(phaseNames).toContain('project_files_search'); expect(phaseNames).toContain('knowledge_graph_query'); expect(phaseNames).toContain('environment_analysis'); }); it('should include web search phase by default', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); const phaseNames = plan.phases.map(p => p.phase); expect(phaseNames).toContain('web_search'); }); it('should exclude web search when includeWebSearch is false', async () => { const { plan } = await orchestrator.createResearchPlan('Test question', { includeWebSearch: false, }); const phaseNames = plan.phases.map(p => p.phase); expect(phaseNames).not.toContain('web_search'); }); it('should include tool information for each phase', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); for (const phase of plan.phases) { expect(phase.tool).toBeDefined(); expect(typeof phase.tool).toBe('string'); expect(phase.params).toBeDefined(); expect(typeof phase.params).toBe('object'); expect(phase.purpose).toBeDefined(); expect(typeof phase.purpose).toBe('string'); expect(phase.expectedOutput).toBeDefined(); } }); it('should include synthesis instructions', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); expect(plan.synthesisInstructions).toBeDefined(); expect(typeof plan.synthesisInstructions).toBe('string'); expect(plan.synthesisInstructions.length).toBeGreaterThan(100); }); it('should include expected result format', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); expect(plan.expectedResultFormat).toBeDefined(); expect(typeof plan.expectedResultFormat).toBe('string'); expect(plan.expectedResultFormat).toContain('answer'); expect(plan.expectedResultFormat).toContain('confidence'); }); it('should use project path from orchestrator', async () => { const { plan } = await orchestrator.createResearchPlan('Test question'); // The project path should be included in phase params const projectFilesPhase = plan.phases.find(p => p.phase === 'project_files_search'); expect(projectFilesPhase?.params.projectPath).toBe('/test/project'); }); }); describe('Tracker Interface', () => { it('should provide all required tracker methods', async () => { const { tracker } = await orchestrator.createResearchPlan('Test question'); expect(typeof tracker.startPhase).toBe('function'); expect(typeof tracker.updatePhaseProgress).toBe('function'); expect(typeof tracker.completePhase).toBe('function'); expect(typeof tracker.failPhase).toBe('function'); expect(typeof tracker.storeProjectFilesResult).toBe('function'); expect(typeof tracker.storeKnowledgeGraphResult).toBe('function'); expect(typeof tracker.storeEnvironmentResult).toBe('function'); expect(typeof tracker.storeWebSearchResult).toBe('function'); expect(typeof tracker.storeSynthesizedAnswer).toBe('function'); expect(typeof tracker.isCancelled).toBe('function'); expect(typeof tracker.cancel).toBe('function'); expect(typeof tracker.complete).toBe('function'); expect(typeof tracker.fail).toBe('function'); expect(typeof tracker.getContext).toBe('function'); }); it('should allow starting and completing phases', async () => { const { tracker } = await orchestrator.createResearchPlan('Test question'); await tracker.startPhase('project_files_search', 'Starting search'); await tracker.completePhase('project_files_search', 'Search complete'); // Should not throw expect(true).toBe(true); }); it('should allow storing results', async () => { const { tracker } = await orchestrator.createResearchPlan('Test question'); await tracker.storeProjectFilesResult({ filesFound: 10, relevantFiles: ['file1.ts', 'file2.ts'], confidence: 0.85, }); await tracker.storeKnowledgeGraphResult({ nodesFound: 5, relationshipsFound: 3, confidence: 0.75, }); const context = tracker.getContext(); expect(context.projectFilesResult).toBeDefined(); expect(context.projectFilesResult?.filesFound).toBe(10); expect(context.knowledgeGraphResult).toBeDefined(); expect(context.knowledgeGraphResult?.nodesFound).toBe(5); }); it('should allow synthesizing and completing', async () => { const { tracker } = await orchestrator.createResearchPlan('Test question'); await tracker.storeSynthesizedAnswer('The authentication uses JWT tokens.', 0.9); await tracker.complete({ success: true, data: { answer: 'The authentication uses JWT tokens.', confidence: 0.9, sources: [], phasesCompleted: ['project_files_search', 'knowledge_graph_query'], }, }); // Should not throw expect(true).toBe(true); }); it('should support cancellation', async () => { const { taskId, tracker } = await orchestrator.createResearchPlan('Test question'); await tracker.cancel('User requested cancellation'); // After cancellation, the context is deleted from the manager // So isCancelled() returns false (no context found) // This is correct behavior - the task is cancelled and cleaned up const { getResearchTaskManager } = await import('../../src/utils/research-task-integration.js'); const rtm = getResearchTaskManager(); const context = rtm.getContext(taskId); expect(context).toBeUndefined(); // Context is removed after cancellation }); }); describe('Getter Methods', () => { it('should return project path', () => { expect(orchestrator.getProjectPath()).toBe('/test/project'); }); it('should return ADR directory', () => { expect(orchestrator.getAdrDirectory()).toBe('docs/adrs'); }); }); describe('Non-blocking Behavior', () => { it('should return immediately without executing research', async () => { const startTime = Date.now(); await orchestrator.createResearchPlan( 'Complex research question about authentication, databases, and deployment' ); const duration = Date.now() - startTime; // Should complete in under 100ms since it's not executing research // The old method would take 2-8 seconds expect(duration).toBeLessThan(100); }); it('should allow multiple createResearchPlan calls', async () => { // Verify we can call createResearchPlan multiple times without issues // This tests that the non-blocking delegation pattern works correctly // First createResearchPlan call const result1 = await orchestrator.createResearchPlan('Test question 1'); expect(result1.taskId).toBeDefined(); // Second createResearchPlan call const result2 = await orchestrator.createResearchPlan('Test question 2'); expect(result2.taskId).toBeDefined(); // Third createResearchPlan call const result3 = await orchestrator.createResearchPlan('Test question 3'); expect(result3.taskId).toBeDefined(); // Each call should produce unique task IDs expect(result1.taskId).not.toBe(result2.taskId); expect(result2.taskId).not.toBe(result3.taskId); expect(result1.taskId).not.toBe(result3.taskId); }); }); });

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

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