UNPKG

task-master-neo-sdlc

Version:

Enhanced task management system with Neo SDLC agents and MCP tools for comprehensive, AI-driven software development lifecycle management.

198 lines (167 loc) 9.63 kB
import { ProcessManagerAgent } from '../process-manager'; import { KnowledgeGraph } from '../../knowledge-graph'; // Adjust path import { AgentWorkflowSystem } from '../../agent-workflow'; // Adjust path // Mocks const mockKnowledgeGraph = { addNode: jest.fn(), findNodes: jest.fn(), updateContext: jest.fn() }; const mockWorkflowSystem = { executeAction: jest.fn() }; describe('ProcessManagerAgent', () => { let agent; beforeEach(() => { jest.clearAllMocks(); agent = new ProcessManagerAgent(mockKnowledgeGraph, mockWorkflowSystem); jest.spyOn(console, 'log').mockImplementation(() => {}); jest.spyOn(console, 'error').mockImplementation(() => {}); // Use fake timers to control async execution in tests jest.useFakeTimers(); }); afterEach(() => { console.log.mockRestore(); console.error.mockRestore(); jest.useRealTimers(); }); it('should start a process and add to KG', async () => { const workflowId = 'test-workflow'; const initialContext = { inputData: 'test' }; mockKnowledgeGraph.addNode.mockResolvedValue(undefined); // Spy on executeStep to check if it's called const executeStepSpy = jest.spyOn(agent, 'executeStep').mockResolvedValue(undefined); const processId = await agent.startProcess(workflowId, initialContext); expect(processId).toMatch(/^process_/); expect(agent.runningProcesses.has(processId)).toBe(true); const processState = agent.runningProcesses.get(processId); expect(processState.definitionId).toBe(workflowId); expect(processState.status).toBe('running'); expect(processState.currentStepId).toBe('step1'); // Based on placeholder definition expect(processState.context).toEqual(initialContext); expect(mockKnowledgeGraph.addNode).toHaveBeenCalledWith(expect.objectContaining({ id: `processInstance:${processId}`, type: 'process_instance', data: expect.objectContaining({ definitionId: workflowId, status: 'running' }) })); // Check that executeStep was scheduled expect(executeStepSpy).toHaveBeenCalledWith(processId); executeStepSpy.mockRestore(); }); it('should throw error if workflow definition is invalid', async () => { // Mock workflowLoader or definition loading logic if implemented // For now, assumes placeholder definition fails if workflowId is specific await expect(agent.startProcess('invalid-def')).rejects.toThrow( 'Workflow definition invalid-def is invalid or empty.' ); }); it('should execute steps sequentially until completion', async () => { const workflowId = 'sequential-workflow'; mockKnowledgeGraph.addNode.mockResolvedValue(undefined); mockKnowledgeGraph.updateContext.mockResolvedValue(undefined); // Mock the action execution mockWorkflowSystem.executeAction .mockResolvedValueOnce({ output: 'result-step1' }) .mockResolvedValueOnce({ output: 'result-step2' }); const processId = await agent.startProcess(workflowId); // Allow async operations triggered by startProcess to run await jest.runAllImmediates(); // Runs setImmediate await jest.runAllTimers(); // If any timers were used // Check step 1 execution (assuming executeStep was called by startProcess) expect(mockWorkflowSystem.executeAction).toHaveBeenCalledWith('agentA.doSomething', undefined, {}); // Check first call let processState = await agent.getProcessStatus(processId); expect(processState.context.step1).toEqual({ output: 'result-step1' }); expect(processState.currentStepId).toBe('step2'); expect(processState.history.some(h => h.stepId === 'step1' && h.status === 'completed')).toBe(true); expect(mockKnowledgeGraph.updateContext).toHaveBeenCalledWith(expect.objectContaining({ id: `processInstance:${processId}`, data: expect.objectContaining({ currentStepId: 'step2' }) })); // Allow next step triggered by previous step completion await jest.runAllImmediates(); await jest.runAllTimers(); // Check step 2 execution expect(mockWorkflowSystem.executeAction).toHaveBeenCalledWith('agentB.doAnotherThing', { input: '${step1.output}' }, expect.any(Object)); // Check second call processState = await agent.getProcessStatus(processId); // Process should be removed from running map upon completion expect(agent.runningProcesses.has(processId)).toBe(false); // Check final state in KG (mocking findNodes for getProcessStatus fallback) mockKnowledgeGraph.findNodes.mockResolvedValue([{ id: `processInstance:${processId}`, data: { status: 'completed' } }]); const finalState = await agent.getProcessStatus(processId); expect(finalState.status).toBe('completed'); expect(mockKnowledgeGraph.updateContext).toHaveBeenCalledWith(expect.objectContaining({ id: `processInstance:${processId}`, data: expect.objectContaining({ status: 'completed' }) })); }); it('should handle execution errors and fail the process', async () => { const workflowId = 'error-workflow'; const error = new Error('Action failed!'); mockKnowledgeGraph.addNode.mockResolvedValue(undefined); mockKnowledgeGraph.updateContext.mockResolvedValue(undefined); mockWorkflowSystem.executeAction.mockRejectedValue(error); // First action fails const processId = await agent.startProcess(workflowId); await jest.runAllImmediates(); await jest.runAllTimers(); expect(mockWorkflowSystem.executeAction).toHaveBeenCalledTimes(1); expect(agent.runningProcesses.has(processId)).toBe(false); expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Error executing step step1'), error); // Check final state in KG mockKnowledgeGraph.findNodes.mockResolvedValue([{ id: `processInstance:${processId}`, data: { status: 'failed' } }]); const finalState = await agent.getProcessStatus(processId); expect(finalState.status).toBe('failed'); expect(mockKnowledgeGraph.updateContext).toHaveBeenCalledWith(expect.objectContaining({ id: `processInstance:${processId}`, data: expect.objectContaining({ status: 'failed', history: expect.arrayContaining([expect.objectContaining({ status: 'failed' })]) }) })); }); it('should handle missing step definitions and fail the process', async () => { const workflowId = 'missing-step-def'; mockKnowledgeGraph.addNode.mockResolvedValue(undefined); mockKnowledgeGraph.updateContext.mockResolvedValue(undefined); // Modify the agent's internal logic or mock definition loading to simulate a missing step const originalExecuteStep = agent.executeStep; agent.executeStep = async (instanceId) => { const processState = agent.runningProcesses.get(instanceId); if (processState && processState.currentStepId === 'step1') { // Simulate step1 completion but step2 definition is missing processState.context.step1 = { output: 'data' }; processState.currentStepId = 'step2'; // Next step doesn't exist in placeholder def await agent._updateProcessStateInKG(instanceId, processState); setImmediate(() => originalExecuteStep.call(agent, instanceId)); } else { await originalExecuteStep.call(agent, instanceId); } }; const processId = await agent.startProcess(workflowId); await jest.runAllImmediates(); await jest.runAllTimers(); // First simulated step completes await jest.runAllImmediates(); // Second call to executeStep occurs expect(console.error).toHaveBeenCalledWith(expect.stringContaining('Current step step2 not found')); expect(agent.runningProcesses.has(processId)).toBe(false); mockKnowledgeGraph.findNodes.mockResolvedValue([{ id: `processInstance:${processId}`, data: { status: 'failed' } }]); const finalState = await agent.getProcessStatus(processId); expect(finalState.status).toBe('failed'); expect(mockKnowledgeGraph.updateContext).toHaveBeenCalledWith(expect.objectContaining({ data: expect.objectContaining({ status: 'failed', history: expect.arrayContaining([expect.objectContaining({ details: 'Step definition not found' })]) }) })); }); it('should get process status for running and historical processes', async () => { const runningProcessId = 'process_running'; const historicalProcessId = 'process_historical'; const runningState = { id: runningProcessId, status: 'running', currentStepId: 'stepX' }; const historicalState = { id: historicalProcessId, status: 'completed', currentStepId: null }; agent.runningProcesses.set(runningProcessId, runningState); mockKnowledgeGraph.findNodes .mockResolvedValueOnce([]) // Running process not found in KG history yet .mockResolvedValueOnce([{ id: `processInstance:${historicalProcessId}`, data: historicalState }]); // Historical found in KG const statusRunning = await agent.getProcessStatus(runningProcessId); const statusHistorical = await agent.getProcessStatus(historicalProcessId); const statusNotFound = await agent.getProcessStatus('not_a_process'); expect(statusRunning).toEqual(runningState); expect(statusHistorical).toEqual(historicalState); expect(statusNotFound).toBeNull(); expect(mockKnowledgeGraph.findNodes).toHaveBeenCalledWith({ id: `processInstance:${historicalProcessId}` }); expect(mockKnowledgeGraph.findNodes).toHaveBeenCalledWith({ id: `processInstance:not_a_process` }); }); });