/** * Comprehensive E2E tests for add-task command * Tests all aspects of task creation including AI and manual modes */ import { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } from 'fs'; import { join } from 'path'; import { tmpdir } from 'os'; import path from 'path'; describe('add-task command', () => { let testDir; let helpers; beforeEach(async () => { // Create test directory testDir = mkdtempSync(join(tmpdir(), 'task-master-add-task-')); // Initialize test helpers const context = global.createTestContext('add-task'); helpers = context.helpers; // Copy .env file if it exists const mainEnvPath = join(process.cwd(), '.env'); const testEnvPath = join(testDir, '.env'); if (existsSync(mainEnvPath)) { const envContent = readFileSync(mainEnvPath, 'utf8'); writeFileSync(testEnvPath, envContent); } // Initialize task-master project const initResult = await helpers.taskMaster('init', ['-y'], { cwd: testDir }); expect(initResult).toHaveExitCode(0); // Ensure tasks.json exists (bug workaround) const tasksJsonPath = join(testDir, '.taskmaster/tasks/tasks.json'); if (!existsSync(tasksJsonPath)) { mkdirSync(join(testDir, '.taskmaster/tasks'), { recursive: true }); writeFileSync(tasksJsonPath, JSON.stringify({ master: { tasks: [] } })); } }); afterEach(() => { // Clean up test directory if (testDir && existsSync(testDir)) { rmSync(testDir, { recursive: true, force: true }); } }); describe('AI-powered task creation', () => { it('should create task with AI prompt', async () => { const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Create a user authentication system with JWT tokens'], { cwd: testDir, timeout: 30000 } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); // AI generated task should contain a title and description expect(showResult.stdout).toContain('Title:'); expect(showResult.stdout).toContain('Description:'); expect(showResult.stdout).toContain('Implementation Details:'); }, 45000); // 45 second timeout for this test it('should handle very long prompts', async () => { const longPrompt = 'Create a comprehensive system that ' + 'handles many features '.repeat(50); const result = await helpers.taskMaster( 'add-task', ['--prompt', longPrompt], { cwd: testDir, timeout: 30000 } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); }, 45000); it('should handle special characters in prompt', async () => { const specialPrompt = 'Implement feature: User data and settings with special chars'; const result = await helpers.taskMaster( 'add-task', ['--prompt', specialPrompt], { cwd: testDir } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); }); it('should verify AI generates reasonable output', async () => { const result = await helpers.taskMaster( 'add-task', [ '--prompt', 'Build a responsive navigation menu with dropdown support' ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); // Verify AI generated task has proper structure expect(showResult.stdout).toContain('Title:'); expect(showResult.stdout).toContain('Status:'); expect(showResult.stdout).toContain('Priority:'); expect(showResult.stdout).toContain('Description:'); }); }); describe('Manual task creation', () => { it('should create task with title and description', async () => { const result = await helpers.taskMaster( 'add-task', [ '--title', 'Setup database connection', '--description', 'Configure PostgreSQL connection with connection pooling' ], { cwd: testDir } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); // Check that at least part of our title and description are shown expect(showResult.stdout).toContain('Setup'); expect(showResult.stdout).toContain('Configure'); }); it('should create task with manual details', async () => { const result = await helpers.taskMaster( 'add-task', [ '--title', 'Implement caching layer', '--description', 'Add Redis caching to improve performance', '--details', 'Use Redis for session storage and API response caching' ], { cwd: testDir } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); }); }); describe('Task creation with options', () => { it('should create task with priority', async () => { const result = await helpers.taskMaster( 'add-task', [ '--prompt', 'Fix critical security vulnerability', '--priority', 'high' ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout.toLowerCase()).toContain('high'); }); it('should create task with dependencies', async () => { // Create dependency task first const depResult = await helpers.taskMaster( 'add-task', [ '--title', 'Setup environment', '--description', 'Initial environment setup' ], { cwd: testDir } ); const depTaskId = helpers.extractTaskId(depResult.stdout); // Create task with dependency const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Deploy application', '--dependencies', depTaskId], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain(depTaskId); }); it('should handle multiple dependencies', async () => { // Create multiple dependency tasks const dep1 = await helpers.taskMaster( 'add-task', ['--prompt', 'Setup environment'], { cwd: testDir } ); const depId1 = helpers.extractTaskId(dep1.stdout); const dep2 = await helpers.taskMaster( 'add-task', ['--prompt', 'Configure database'], { cwd: testDir } ); const depId2 = helpers.extractTaskId(dep2.stdout); // Create task with multiple dependencies const result = await helpers.taskMaster( 'add-task', [ '--prompt', 'Deploy application', '--dependencies', `${depId1},${depId2}` ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toContain(depId1); expect(showResult.stdout).toContain(depId2); }); it('should create task with all options combined', async () => { // Setup const depResult = await helpers.taskMaster( 'add-task', [ '--title', 'Prerequisite task', '--description', 'Task that must be completed first' ], { cwd: testDir } ); const depTaskId = helpers.extractTaskId(depResult.stdout); await helpers.taskMaster( 'add-tag', ['feature-complete', '--description', 'Complete feature test'], { cwd: testDir } ); // Create task with all options const result = await helpers.taskMaster( 'add-task', [ '--prompt', 'Comprehensive task with all features', '--priority', 'medium', '--dependencies', depTaskId ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); // Verify all options const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout.toLowerCase()).toContain('medium'); expect(showResult.stdout).toContain(depTaskId); }); }); describe('Error handling', () => { it('should fail without prompt or title+description', async () => { const result = await helpers.taskMaster('add-task', [], { cwd: testDir, allowFailure: true }); expect(result.exitCode).not.toBe(0); expect(result.stderr).toContain( 'Either --prompt or both --title and --description must be provided' ); }); it('should fail with only title (missing description)', async () => { const result = await helpers.taskMaster( 'add-task', ['--title', 'Incomplete task'], { cwd: testDir, allowFailure: true } ); expect(result.exitCode).not.toBe(0); }); it('should handle invalid priority by defaulting to medium', async () => { const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Test task', '--priority', 'invalid'], { cwd: testDir } ); // Should succeed but use default priority and show warning expect(result).toHaveExitCode(0); expect(result.stdout).toContain('Invalid priority "invalid"'); expect(result.stdout).toContain('Using default priority "medium"'); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toMatch(/Priority:\s+│\s+medium/); }); it('should warn and continue with non-existent dependency', async () => { // Based on the implementation, invalid dependencies are filtered out with a warning const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Test task', '--dependencies', '99999'], { cwd: testDir } ); // Should succeed but with warning expect(result).toHaveExitCode(0); expect(result.stdout).toContain('do not exist'); }); }); describe('Concurrent operations', () => { it('should handle multiple tasks created in parallel', async () => { const promises = []; for (let i = 0; i < 3; i++) { promises.push( helpers.taskMaster( 'add-task', ['--prompt', `Parallel task ${i + 1}`], { cwd: testDir } ) ); } const results = await Promise.all(promises); results.forEach((result) => { expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); }); }); }); describe('Research mode', () => { it('should create task using research mode', async () => { const result = await helpers.taskMaster( 'add-task', [ '--prompt', 'Research best practices for implementing OAuth2 authentication', '--research' ], { cwd: testDir, timeout: 45000 } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); // Verify task was created const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); // Verify task was created with research mode (should have more detailed output) expect(showResult.stdout).toContain('Title:'); expect(showResult.stdout).toContain('Implementation Details:'); }, 60000); }); describe('File path handling', () => { it('should use custom tasks file path', async () => { // Create custom tasks file const customPath = join(testDir, 'custom-tasks.json'); writeFileSync(customPath, JSON.stringify({ master: { tasks: [] } })); const result = await helpers.taskMaster( 'add-task', ['--file', customPath, '--prompt', 'Task in custom file'], { cwd: testDir } ); expect(result).toHaveExitCode(0); // Verify task was added to custom file const customContent = JSON.parse(readFileSync(customPath, 'utf8')); expect(customContent.master.tasks.length).toBe(1); }); }); describe('Priority validation', () => { it('should accept all valid priority values', async () => { const priorities = ['high', 'medium', 'low']; for (const priority of priorities) { const result = await helpers.taskMaster( 'add-task', [ '--prompt', `Task with ${priority} priority`, '--priority', priority ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout.toLowerCase()).toContain(priority); } }); it('should accept priority values case-insensitively', async () => { const priorities = ['HIGH', 'Medium', 'LoW']; const expected = ['high', 'medium', 'low']; for (let i = 0; i < priorities.length; i++) { const result = await helpers.taskMaster( 'add-task', [ '--prompt', `Task with ${priorities[i]} priority`, '--priority', priorities[i] ], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout).toMatch(new RegExp(`Priority:\\s+│\\s+${expected[i]}`)); } }); it('should default to medium priority when not specified', async () => { const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Task without explicit priority'], { cwd: testDir } ); expect(result).toHaveExitCode(0); const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); expect(showResult.stdout.toLowerCase()).toContain('medium'); }); }); describe('AI dependency suggestions', () => { it('should let AI suggest dependencies based on context', async () => { // Create some existing tasks that AI might reference // Create an existing task that AI might reference await helpers.taskMaster( 'add-task', ['--prompt', 'Setup authentication system'], { cwd: testDir } ); // Create a task that should logically depend on auth const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Implement user profile page with authentication checks'], { cwd: testDir, timeout: 45000 } ); expect(result).toHaveExitCode(0); // Check if AI suggested dependencies if (result.stdout.includes('AI suggested')) { expect(result.stdout).toContain('Dependencies'); } }, 60000); }); describe('Tag support', () => { it('should add task to specific tag', async () => { // Create a new tag await helpers.taskMaster( 'add-tag', ['feature-branch', '--description', 'Feature branch tag'], { cwd: testDir } ); // Add task to specific tag const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Task for feature branch', '--tag', 'feature-branch'], { cwd: testDir } ); expect(result).toHaveExitCode(0); expect(result.stdout).toContainTaskId(); // Verify task is in the correct tag const taskId = helpers.extractTaskId(result.stdout); const showResult = await helpers.taskMaster( 'show', [taskId, '--tag', 'feature-branch'], { cwd: testDir } ); expect(showResult).toHaveExitCode(0); }); it('should add to master tag by default', async () => { const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Task for master tag'], { cwd: testDir } ); expect(result).toHaveExitCode(0); // Verify task is in master tag const tasksContent = JSON.parse( readFileSync(join(testDir, '.taskmaster/tasks/tasks.json'), 'utf8') ); expect(tasksContent.master.tasks.length).toBeGreaterThan(0); }); }); describe('AI fallback behavior', () => { it('should handle invalid model gracefully', async () => { // Set an invalid model await helpers.taskMaster('models', ['--set-main', 'invalid-model-xyz'], { cwd: testDir }); const result = await helpers.taskMaster( 'add-task', ['--prompt', 'Test fallback behavior'], { cwd: testDir, allowFailure: true } ); // Should either use fallback or fail gracefully if (result.exitCode === 0) { expect(result.stdout).toContainTaskId(); } else { expect(result.stderr).toBeTruthy(); } // Reset to valid model for other tests await helpers.taskMaster('models', ['--set-main', 'gpt-3.5-turbo'], { cwd: testDir }); }); }); });