UNPKG

claude-flow

Version:

Enterprise-grade AI agent orchestration with ruv-swarm integration (Alpha Release)

482 lines (390 loc) 15.7 kB
/** * Tests for task command */ import { jest } from '@jest/globals'; import { taskCommand } from '../task.js'; import fs from 'fs-extra'; import path from 'path'; import chalk from 'chalk'; import ora from 'ora'; jest.mock('fs-extra'); jest.mock('ora'); jest.mock('chalk', () => ({ default: { blue: jest.fn(str => str), green: jest.fn(str => str), yellow: jest.fn(str => str), red: jest.fn(str => str), cyan: jest.fn(str => str), magenta: jest.fn(str => str), dim: jest.fn(str => str), bold: jest.fn(str => str), } })); describe('Task Command', () => { let consoleLogSpy; let consoleErrorSpy; let mockSpinner; beforeEach(() => { consoleLogSpy = jest.spyOn(console, 'log').mockImplementation(); consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(); mockSpinner = { start: jest.fn().mockReturnThis(), succeed: jest.fn().mockReturnThis(), fail: jest.fn().mockReturnThis(), info: jest.fn().mockReturnThis(), warn: jest.fn().mockReturnThis(), text: '', }; ora.mockReturnValue(mockSpinner); jest.clearAllMocks(); }); afterEach(() => { consoleLogSpy.mockRestore(); consoleErrorSpy.mockRestore(); }); describe('list subcommand', () => { test('should list all tasks', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Implement authentication', description: 'Add JWT authentication to API', status: 'in-progress', priority: 'high', assignedTo: 'agent-1', created: new Date().toISOString(), tags: ['api', 'security'] }, { id: 'task-2', title: 'Write tests', description: 'Add unit tests for core functions', status: 'pending', priority: 'medium', assignedTo: null, created: new Date().toISOString(), tags: ['testing'] } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['list'], {}); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Tasks (2)'); expect(output).toContain('Implement authentication'); expect(output).toContain('in-progress'); expect(output).toContain('Write tests'); expect(output).toContain('pending'); }); test('should filter tasks by status', async () => { const mockTasks = { tasks: [ { id: 'task-1', status: 'completed', title: 'Done task' }, { id: 'task-2', status: 'in-progress', title: 'Working task' }, { id: 'task-3', status: 'pending', title: 'Todo task' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['list'], { status: 'in-progress' }); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Working task'); expect(output).not.toContain('Done task'); expect(output).not.toContain('Todo task'); }); test('should filter tasks by assignee', async () => { const mockTasks = { tasks: [ { id: 'task-1', assignedTo: 'agent-1', title: 'Agent 1 task' }, { id: 'task-2', assignedTo: 'agent-2', title: 'Agent 2 task' }, { id: 'task-3', assignedTo: null, title: 'Unassigned task' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['list'], { assignee: 'agent-1' }); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Agent 1 task'); expect(output).not.toContain('Agent 2 task'); expect(output).not.toContain('Unassigned task'); }); test('should show empty list message', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); await taskCommand(['list'], {}); expect(consoleLogSpy).toHaveBeenCalledWith( expect.stringContaining('No tasks found') ); }); }); describe('create subcommand', () => { test('should create a new task', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['create', 'New task title', 'Task description'], {}); expect(mockSpinner.succeed).toHaveBeenCalledWith( expect.stringContaining('Task created') ); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks).toHaveLength(1); expect(writeCall[1].tasks[0].title).toBe('New task title'); expect(writeCall[1].tasks[0].description).toBe('Task description'); expect(writeCall[1].tasks[0].status).toBe('pending'); }); test('should create task with priority', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['create', 'High priority task'], { priority: 'high' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].priority).toBe('high'); }); test('should create task with tags', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['create', 'Tagged task'], { tags: 'api,security,urgent' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].tags).toEqual(['api', 'security', 'urgent']); }); test('should create task with assignment', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['create', 'Assigned task'], { assign: 'agent-1' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].assignedTo).toBe('agent-1'); }); test('should create tasks file if not exists', async () => { fs.pathExists.mockResolvedValue(false); fs.ensureDir.mockResolvedValue(undefined); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['create', 'First task'], {}); expect(fs.ensureDir).toHaveBeenCalled(); expect(fs.writeJson).toHaveBeenCalled(); }); }); describe('update subcommand', () => { test('should update task status', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Test task', status: 'pending' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['update', 'task-1'], { status: 'in-progress' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].status).toBe('in-progress'); expect(mockSpinner.succeed).toHaveBeenCalledWith( expect.stringContaining('Task updated') ); }); test('should update task assignment', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Test task', assignedTo: null } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['update', 'task-1'], { assign: 'agent-2' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].assignedTo).toBe('agent-2'); }); test('should update task priority', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Test task', priority: 'medium' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['update', 'task-1'], { priority: 'high' }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks[0].priority).toBe('high'); }); test('should handle non-existent task', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); await taskCommand(['update', 'nonexistent'], { status: 'completed' }); expect(mockSpinner.fail).toHaveBeenCalledWith( expect.stringContaining('Task nonexistent not found') ); }); }); describe('show subcommand', () => { test('should show task details', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Detailed task', description: 'Long description here', status: 'in-progress', priority: 'high', assignedTo: 'agent-1', created: new Date().toISOString(), updated: new Date().toISOString(), tags: ['api', 'security'], dependencies: ['task-0'], metadata: { estimatedHours: 8, actualHours: 4 } } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['show', 'task-1'], {}); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Task Details: task-1'); expect(output).toContain('Detailed task'); expect(output).toContain('Long description here'); expect(output).toContain('in-progress'); expect(output).toContain('agent-1'); expect(output).toContain('[api, security]'); }); test('should handle non-existent task', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); await taskCommand(['show', 'nonexistent'], {}); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Task nonexistent not found') ); }); }); describe('delete subcommand', () => { test('should delete a task', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Task 1' }, { id: 'task-2', title: 'Task 2' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); fs.writeJson.mockResolvedValue(undefined); await taskCommand(['delete', 'task-1'], { force: true }); const writeCall = fs.writeJson.mock.calls[0]; expect(writeCall[1].tasks).toHaveLength(1); expect(writeCall[1].tasks[0].id).toBe('task-2'); expect(mockSpinner.succeed).toHaveBeenCalledWith( expect.stringContaining('Task deleted') ); }); test('should require force flag', async () => { const mockTasks = { tasks: [{ id: 'task-1', title: 'Task 1' }] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['delete', 'task-1'], {}); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Use --force to confirm') ); }); }); describe('stats subcommand', () => { test('should show task statistics', async () => { const mockTasks = { tasks: [ { status: 'completed', priority: 'high', assignedTo: 'agent-1' }, { status: 'in-progress', priority: 'medium', assignedTo: 'agent-1' }, { status: 'pending', priority: 'high', assignedTo: 'agent-2' }, { status: 'pending', priority: 'low', assignedTo: null }, { status: 'completed', priority: 'medium', assignedTo: 'agent-2' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['stats'], {}); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Task Statistics'); expect(output).toContain('Total: 5'); expect(output).toContain('Completed: 2 (40%)'); expect(output).toContain('In Progress: 1 (20%)'); expect(output).toContain('Pending: 2 (40%)'); expect(output).toContain('High Priority: 2'); expect(output).toContain('Assigned: 4'); expect(output).toContain('Unassigned: 1'); }); }); describe('search subcommand', () => { test('should search tasks by title', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Implement API authentication', description: 'JWT auth' }, { id: 'task-2', title: 'Write unit tests', description: 'Test coverage' }, { id: 'task-3', title: 'API documentation', description: 'Swagger docs' } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['search', 'API'], {}); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Implement API authentication'); expect(output).toContain('API documentation'); expect(output).not.toContain('Write unit tests'); }); test('should search tasks by tags', async () => { const mockTasks = { tasks: [ { id: 'task-1', title: 'Task 1', tags: ['api', 'security'] }, { id: 'task-2', title: 'Task 2', tags: ['testing', 'quality'] }, { id: 'task-3', title: 'Task 3', tags: ['api', 'performance'] } ] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['search'], { tags: 'api' }); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Task 1'); expect(output).toContain('Task 3'); expect(output).not.toContain('Task 2'); }); }); describe('help subcommand', () => { test('should show help when no arguments', async () => { await taskCommand([], {}); const output = consoleLogSpy.mock.calls.flat().join('\n'); expect(output).toContain('Task Management'); expect(output).toContain('USAGE:'); expect(output).toContain('task <subcommand>'); }); }); describe('error handling', () => { test('should handle file system errors', async () => { fs.pathExists.mockRejectedValue(new Error('Permission denied')); await taskCommand(['list'], {}); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Error:') ); }); test('should handle invalid priority', async () => { fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue({ tasks: [] }); await taskCommand(['create', 'Test task'], { priority: 'invalid' }); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Invalid priority') ); }); test('should handle invalid status', async () => { const mockTasks = { tasks: [{ id: 'task-1', status: 'pending' }] }; fs.pathExists.mockResolvedValue(true); fs.readJson.mockResolvedValue(mockTasks); await taskCommand(['update', 'task-1'], { status: 'invalid-status' }); expect(consoleErrorSpy).toHaveBeenCalledWith( expect.stringContaining('Invalid status') ); }); }); });