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.

277 lines (229 loc) 8.14 kB
import { ChainSystem } from '../chain-system.js'; import { jest } from '@jest/globals'; // Mock KnowledgeGraph const mockKnowledgeGraph = { addNode: jest.fn(), updateContext: jest.fn(), }; describe('ChainSystem', () => { let chainSystem; beforeEach(() => { jest.clearAllMocks(); chainSystem = new ChainSystem(mockKnowledgeGraph); }); describe('Initialization', () => { it('should initialize chain system and register built-in chain types', async () => { await chainSystem.initialize(); expect(mockKnowledgeGraph.addNode).toHaveBeenCalledWith({ id: 'chain-system', type: 'system', data: { status: 'active', registeredChains: 0, activeChains: 0, completedChains: 0 } }); // Verify built-in chain types are registered expect(chainSystem.chainTypes.has('sequential')).toBe(true); expect(chainSystem.chainTypes.has('parallel')).toBe(true); expect(chainSystem.chainTypes.has('conditional')).toBe(true); }); }); describe('Chain Type Registration', () => { it('should register custom chain type', async () => { const customType = { validate: jest.fn().mockReturnValue({ valid: true }), getNextStep: jest.fn() }; chainSystem.registerChainType('custom', customType); expect(chainSystem.chainTypes.get('custom')).toBe(customType); }); it('should reject invalid chain type handlers', () => { expect(() => { chainSystem.registerChainType('invalid', { validate: jest.fn() }); }).toThrow('Chain type handlers must include validate and getNextStep functions'); }); }); describe('Chain Registration', () => { beforeEach(async () => { await chainSystem.initialize(); }); it('should register valid sequential chain', async () => { const config = { id: 'test-chain', type: 'sequential', steps: ['step1', 'step2', 'step3'] }; await chainSystem.registerChain(config); expect(chainSystem.chains.get('test-chain')).toBeDefined(); expect(mockKnowledgeGraph.addNode).toHaveBeenCalledWith({ id: 'chain:test-chain', type: 'workflow-chain', data: config }); }); it('should register valid parallel chain', async () => { const config = { id: 'parallel-chain', type: 'parallel', steps: ['step1', 'step2', 'step3'], options: { maxConcurrent: 2 } }; await chainSystem.registerChain(config); expect(chainSystem.chains.get('parallel-chain')).toBeDefined(); }); it('should register valid conditional chain', async () => { const config = { id: 'conditional-chain', type: 'conditional', steps: ['start', 'branch1', 'branch2'], options: { conditions: { success: 'branch1', failure: 'branch2' } } }; await chainSystem.registerChain(config); expect(chainSystem.chains.get('conditional-chain')).toBeDefined(); }); it('should reject invalid chain configurations', async () => { await expect(chainSystem.registerChain({ id: 'invalid', type: 'sequential' // Missing steps array })).rejects.toThrow('Sequential chain requires non-empty steps array'); }); }); describe('Chain Execution', () => { beforeEach(async () => { await chainSystem.initialize(); }); describe('Sequential Chain', () => { it('should execute steps in order', async () => { const steps = ['step1', 'step2', 'step3']; const stepResults = {}; // Register step handlers steps.forEach(step => { chainSystem.registerStepHandler(step, async () => { stepResults[step] = Date.now(); return { status: 'success' }; }); }); // Register and execute chain await chainSystem.registerChain({ id: 'seq-test', type: 'sequential', steps }); await chainSystem.executeChain('seq-test'); // Verify execution order const times = steps.map(step => stepResults[step]); expect(times[0]).toBeLessThan(times[1]); expect(times[1]).toBeLessThan(times[2]); }); }); describe('Parallel Chain', () => { it('should respect maxConcurrent limit', async () => { const steps = ['p1', 'p2', 'p3', 'p4']; let concurrentCount = 0; let maxObservedConcurrent = 0; // Register step handlers that track concurrency steps.forEach(step => { chainSystem.registerStepHandler(step, async () => { concurrentCount++; maxObservedConcurrent = Math.max(maxObservedConcurrent, concurrentCount); await new Promise(resolve => setTimeout(resolve, 10)); concurrentCount--; return { status: 'success' }; }); }); // Register and execute chain await chainSystem.registerChain({ id: 'parallel-test', type: 'parallel', steps, options: { maxConcurrent: 2 } }); await chainSystem.executeChain('parallel-test'); expect(maxObservedConcurrent).toBeLessThanOrEqual(2); }); }); describe('Conditional Chain', () => { it('should follow success path', async () => { const executedSteps = []; // Register step handlers chainSystem.registerStepHandler('start', async () => { executedSteps.push('start'); return { condition: 'success' }; }); chainSystem.registerStepHandler('success-path', async () => { executedSteps.push('success-path'); return { status: 'done' }; }); chainSystem.registerStepHandler('failure-path', async () => { executedSteps.push('failure-path'); return { status: 'done' }; }); // Register and execute chain await chainSystem.registerChain({ id: 'conditional-test', type: 'conditional', steps: ['start', 'success-path', 'failure-path'], options: { conditions: { success: 'success-path', failure: 'failure-path' } } }); await chainSystem.executeChain('conditional-test'); expect(executedSteps).toEqual(['start', 'success-path']); }); }); }); describe('Chain Status', () => { it('should track chain progress', async () => { await chainSystem.initialize(); // Register chain await chainSystem.registerChain({ id: 'progress-test', type: 'sequential', steps: ['s1', 's2', 's3'] }); // Register step handlers ['s1', 's2', 's3'].forEach(step => { chainSystem.registerStepHandler(step, async () => ({ status: 'success' })); }); // Start execution const execution = chainSystem.executeChain('progress-test'); // Check initial status let status = await chainSystem.getChainStatus('progress-test'); expect(status.status).toBe('active'); expect(status.progress).toBe(0); // Wait for completion await execution; // Check final status status = await chainSystem.getChainStatus('progress-test'); expect(status.status).toBe('completed'); expect(status.progress).toBe(100); }); }); describe('Error Handling', () => { it('should handle step failures', async () => { await chainSystem.initialize(); chainSystem.registerStepHandler('failing-step', async () => { throw new Error('Step failed'); }); await chainSystem.registerChain({ id: 'error-test', type: 'sequential', steps: ['failing-step'] }); await expect(chainSystem.executeChain('error-test')).rejects.toThrow('Step failed'); const status = await chainSystem.getChainStatus('error-test'); expect(status.status).toBe('failed'); }); }); });