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
JavaScript
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');
});
});
});