From 74232d0e0d52d1ca2495f77274c9f65e57abff0a Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:38:28 +0300 Subject: [PATCH] feat: more e2e --- tests/e2e/tests/commands/parse-prd.test.js | 1121 ++++++----------- tests/e2e/tests/commands/research.test.js | 826 ++++++------ .../e2e/tests/commands/update-subtask.test.js | 860 +++++++------ tests/e2e/tests/commands/update-task.test.js | 884 +++++++------ tests/e2e/tests/commands/update-tasks.test.js | 818 ++++++------ 5 files changed, 2193 insertions(+), 2316 deletions(-) diff --git a/tests/e2e/tests/commands/parse-prd.test.js b/tests/e2e/tests/commands/parse-prd.test.js index 0f9a8fc1..278cf82a 100644 --- a/tests/e2e/tests/commands/parse-prd.test.js +++ b/tests/e2e/tests/commands/parse-prd.test.js @@ -1,787 +1,472 @@ /** * Comprehensive E2E tests for parse-prd command - * Tests all aspects of PRD parsing including different formats and error handling + * Tests all aspects of PRD parsing including task generation, research mode, and various formats */ -export default async function testParsePrd(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [], - tests: [] - }; +const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); +const { tmpdir } = require('os'); - async function runTest(name, testFn) { - try { - logger.info(`\nRunning: ${name}`); - await testFn(); - results.tests.push({ name, status: 'passed' }); - logger.success(`✓ ${name}`); - } catch (error) { - results.tests.push({ name, status: 'failed', error: error.message }); - results.errors.push({ test: name, error: error.message }); - logger.error(`✗ ${name}: ${error.message}`); +describe('parse-prd command', () => { + let testDir; + let helpers; + + beforeEach(async () => { + // Create test directory + testDir = mkdtempSync(join(tmpdir(), 'task-master-parse-prd-')); + + // Initialize test helpers + const context = global.createTestContext('parse-prd'); + helpers = context.helpers; + + // Copy .env file if it exists + const mainEnvPath = join(__dirname, '../../../../.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); + }); - try { - logger.info('Starting comprehensive parse-prd tests...'); + afterEach(() => { + // Clean up test directory + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } + }); - // Test 1: Basic PRD parsing from file - await runTest('Basic PRD parsing', async () => { + describe('Basic PRD parsing', () => { + it('should parse PRD from file', async () => { // Create a simple PRD file - const prdContent = `# Product Requirements Document + const prdContent = `# Project Requirements + + Build a user authentication system with the following features: + - User registration with email verification + - Login with JWT tokens + - Password reset functionality + - User profile management`; + + const prdPath = join(testDir, 'test-prd.txt'); + writeFileSync(prdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Tasks generated successfully'); + + // Verify tasks.json was created + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + expect(existsSync(tasksPath)).toBe(true); + + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + expect(tasks.master.tasks.length).toBeGreaterThan(0); + }, 60000); + + it('should use default PRD file when none specified', async () => { + // Create default prd.txt + const prdContent = 'Build a simple todo application'; + const defaultPrdPath = join(testDir, '.taskmaster/prd.txt'); + mkdirSync(join(testDir, '.taskmaster'), { recursive: true }); + writeFileSync(defaultPrdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + [], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Using default PRD file'); + }, 60000); + + it('should parse PRD using --input option', async () => { + const prdContent = 'Create a REST API for blog management'; + const prdPath = join(testDir, 'api-prd.txt'); + writeFileSync(prdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + ['--input', prdPath], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Tasks generated successfully'); + }, 60000); + }); + + describe('Task generation options', () => { + it('should generate custom number of tasks', async () => { + const prdContent = 'Build a comprehensive e-commerce platform with all features'; + const prdPath = join(testDir, 'ecommerce-prd.txt'); + writeFileSync(prdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath, '--num-tasks', '5'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + // AI might generate slightly more or less, but should be close to 5 + expect(tasks.master.tasks.length).toBeGreaterThanOrEqual(3); + expect(tasks.master.tasks.length).toBeLessThanOrEqual(7); + }, 60000); + + it('should handle custom output path', async () => { + const prdContent = 'Build a chat application'; + const prdPath = join(testDir, 'chat-prd.txt'); + writeFileSync(prdPath, prdContent); + + const customOutput = join(testDir, 'custom-tasks.json'); + + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath, '--output', customOutput], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(existsSync(customOutput)).toBe(true); + + const tasks = JSON.parse(readFileSync(customOutput, 'utf8')); + expect(tasks.master.tasks.length).toBeGreaterThan(0); + }, 60000); + }); + + describe('Force and append modes', () => { + it('should overwrite with --force flag', async () => { + // Create initial tasks + const initialPrd = 'Build feature A'; + const prdPath1 = join(testDir, 'initial.txt'); + writeFileSync(prdPath1, initialPrd); + + await helpers.taskMaster('parse-prd', [prdPath1], { cwd: testDir }); + + // Create new PRD + const newPrd = 'Build feature B'; + const prdPath2 = join(testDir, 'new.txt'); + writeFileSync(prdPath2, newPrd); + + // Parse with force flag + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath2, '--force'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).not.toContain('overwrite existing tasks?'); + }, 90000); + + it('should append tasks with --append flag', async () => { + // Create initial tasks + const initialPrd = 'Build authentication system'; + const prdPath1 = join(testDir, 'auth-prd.txt'); + writeFileSync(prdPath1, initialPrd); + + await helpers.taskMaster('parse-prd', [prdPath1], { cwd: testDir }); + + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const initialTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const initialCount = initialTasks.master.tasks.length; + + // Create additional PRD + const additionalPrd = 'Build user profile features'; + const prdPath2 = join(testDir, 'profile-prd.txt'); + writeFileSync(prdPath2, additionalPrd); + + // Parse with append flag + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath2, '--append'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Appending to existing tasks'); + + const finalTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + expect(finalTasks.master.tasks.length).toBeGreaterThan(initialCount); + + // Verify IDs are sequential + const maxId = Math.max(...finalTasks.master.tasks.map(t => t.id)); + expect(maxId).toBe(finalTasks.master.tasks.length); + }, 90000); + }); + + describe('Research mode', () => { + it('should use research mode with --research flag', async () => { + const prdContent = 'Build a machine learning pipeline for recommendation system'; + const prdPath = join(testDir, 'ml-prd.txt'); + writeFileSync(prdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath, '--research'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Using Perplexity AI for research-backed task generation'); + + // Research mode should produce more detailed tasks + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + // Check that tasks have detailed implementation details + const hasDetailedTasks = tasks.master.tasks.some(t => + t.details && t.details.length > 200 + ); + expect(hasDetailedTasks).toBe(true); + }, 120000); + }); + + describe('Tag support', () => { + it('should parse PRD to specific tag', async () => { + // Create a new tag + await helpers.taskMaster('add-tag', ['feature-x'], { cwd: testDir }); + + const prdContent = 'Build feature X components'; + const prdPath = join(testDir, 'feature-x-prd.txt'); + writeFileSync(prdPath, prdContent); + + const result = await helpers.taskMaster( + 'parse-prd', + [prdPath, '--tag', 'feature-x'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + expect(tasks['feature-x']).toBeDefined(); + expect(tasks['feature-x'].tasks.length).toBeGreaterThan(0); + }, 60000); + }); + + describe('File format handling', () => { + it('should parse markdown format PRD', async () => { + const prdContent = `# Project: Task Management System ## Overview -Build a task management system for developers. +Build a task management system with the following features: -## Features -1. Create and manage tasks -2. Set task dependencies -3. Track task status -4. Generate reports +### Core Features +- **Task Creation**: Users can create tasks with title and description +- **Task Lists**: Organize tasks in different lists +- **Due Dates**: Set and track due dates -## Technical Requirements -- Node.js backend -- RESTful API -- JSON data storage -- CLI interface - -## User Stories -As a developer, I want to: -- Create tasks quickly from the command line -- View my task list with priorities -- Mark tasks as complete -- See task dependencies`; - - helpers.writeFile(`${testDir}/simple-prd.txt`, prdContent); +### Technical Requirements +- REST API backend +- React frontend +- PostgreSQL database`; + + const prdPath = join(testDir, 'markdown-prd.md'); + writeFileSync(prdPath, prdContent); const result = await helpers.taskMaster( 'parse-prd', - ['simple-prd.txt'], - { cwd: testDir, timeout: 120000 } + [prdPath], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Check for success message - if (!result.stdout.includes('task') || !result.stdout.includes('created')) { - throw new Error('PRD parsing did not report task creation'); - } + expect(result).toHaveExitCode(0); - // Verify tasks were created - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Create and manage tasks') && - !listResult.stdout.includes('task')) { - throw new Error('Tasks from PRD not found in task list'); - } - }); + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + // Should parse technical requirements into tasks + const hasApiTask = tasks.master.tasks.some(t => + t.title.toLowerCase().includes('api') || + t.description.toLowerCase().includes('api') + ); + expect(hasApiTask).toBe(true); + }, 60000); - // Test 2: PRD parsing with complex structure - await runTest('Complex PRD parsing', async () => { - const complexPrd = ` -# E-Commerce Platform PRD + it('should handle PRD with code blocks', async () => { + const prdContent = `# API Requirements -## Executive Summary -A comprehensive e-commerce platform with multi-vendor support. +Create REST endpoints: -## Core Features -### User Management -- User registration and authentication -- Role-based access control -- User profiles and preferences +\`\`\` +POST /api/users - Create user +GET /api/users/:id - Get user by ID +PUT /api/users/:id - Update user +DELETE /api/users/:id - Delete user +\`\`\` -### Product Catalog -- Product listing and search -- Categories and filters -- Product reviews and ratings - -### Shopping Cart -- Add/remove items -- Save for later -- Apply discount codes - -### Payment Processing -- Multiple payment methods -- Secure checkout -- Order confirmation - -## Technical Architecture -### Frontend -- React.js with TypeScript -- Responsive design -- Progressive Web App - -### Backend -- Node.js with Express -- PostgreSQL database -- Redis for caching - -### Infrastructure -- Docker containers -- Kubernetes orchestration -- CI/CD pipeline - -## Development Phases -Phase 1: Core infrastructure and user management -Phase 2: Product catalog and search -Phase 3: Shopping cart and checkout -Phase 4: Payment integration -Phase 5: Admin dashboard - -## Dependencies -- User management must be complete before any other features -- Product catalog required before shopping cart -- Shopping cart required before payment processing -`; - - helpers.writeFile(`${testDir}/complex-prd.md`, complexPrd); +Each endpoint should have proper error handling and validation.`; + + const prdPath = join(testDir, 'api-prd.txt'); + writeFileSync(prdPath, prdContent); const result = await helpers.taskMaster( 'parse-prd', - ['complex-prd.md'], - { cwd: testDir, timeout: 180000 } + [prdPath], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should create multiple tasks - const taskCountMatch = result.stdout.match(/(\d+) tasks? created/i); - if (!taskCountMatch || parseInt(taskCountMatch[1]) < 5) { - throw new Error('Complex PRD should create more tasks'); - } + expect(result).toHaveExitCode(0); - // Check for phase-based tasks - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Phase') && !listResult.stdout.includes('phase')) { - throw new Error('Phase-based tasks not created from PRD'); - } + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + // Should create tasks for API endpoints + const hasEndpointTasks = tasks.master.tasks.some(t => + t.title.includes('endpoint') || + t.description.includes('endpoint') || + t.details.includes('/api/') + ); + expect(hasEndpointTasks).toBe(true); + }, 60000); + }); + + describe('Error handling', () => { + it('should fail with non-existent file', async () => { + const result = await helpers.taskMaster( + 'parse-prd', + ['non-existent-file.txt'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('not found'); }); - // Test 3: PRD parsing with custom task template - await runTest('PRD parsing with task template', async () => { - const templatePrd = `# Project: API Development - -## Tasks -[TASK] Design RESTful API endpoints -- Define resource models -- Document API specifications -- Create OpenAPI schema - -[TASK] Implement authentication -- JWT token generation -- Refresh token mechanism -- Role-based permissions - -[TASK] Build core endpoints -- CRUD operations for resources -- Input validation -- Error handling - -[TASK] Add caching layer -- Redis integration -- Cache invalidation strategy -- Performance monitoring`; - - helpers.writeFile(`${testDir}/template-prd.txt`, templatePrd); + it('should fail with empty PRD file', async () => { + const emptyPrdPath = join(testDir, 'empty.txt'); + writeFileSync(emptyPrdPath, ''); const result = await helpers.taskMaster( 'parse-prd', - ['template-prd.txt', '--template', '[TASK]'], - { cwd: testDir, timeout: 120000 } + [emptyPrdPath], + { cwd: testDir, allowFailure: true } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should recognize custom template - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Design RESTful API') || - !listResult.stdout.includes('authentication')) { - throw new Error('Custom template tasks not parsed correctly'); - } + expect(result.exitCode).not.toBe(0); }); - // Test 4: Incremental PRD update - await runTest('Incremental PRD update', async () => { - // First PRD - const initialPrd = `# Initial Requirements - -## Phase 1 -- Setup project structure -- Configure development environment`; - - helpers.writeFile(`${testDir}/incremental-prd.txt`, initialPrd); - - // Parse initial PRD - await helpers.taskMaster( + it('should show help when no PRD specified and no default exists', async () => { + const result = await helpers.taskMaster( 'parse-prd', - ['incremental-prd.txt'], + [], { cwd: testDir } ); - // Get initial task count - const initialList = await helpers.taskMaster('list', [], { cwd: testDir }); - const initialTaskCount = (initialList.stdout.match(/\d+\s*\|/g) || []).length; - - // Update PRD with additional content - const updatedPrd = `# Initial Requirements - -## Phase 1 -- Setup project structure -- Configure development environment - -## Phase 2 (NEW) -- Implement user authentication -- Create database schema -- Build API endpoints`; - - helpers.writeFile(`${testDir}/incremental-prd.txt`, updatedPrd); - - // Parse updated PRD - const result = await helpers.taskMaster( - 'parse-prd', - ['incremental-prd.txt', '--update'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should add new tasks without duplicating existing ones - const updatedList = await helpers.taskMaster('list', [], { cwd: testDir }); - const updatedTaskCount = (updatedList.stdout.match(/\d+\s*\|/g) || []).length; - - if (updatedTaskCount <= initialTaskCount) { - throw new Error('Incremental update did not add new tasks'); - } - - if (!updatedList.stdout.includes('authentication') || - !updatedList.stdout.includes('Phase 2')) { - throw new Error('New phase tasks not added'); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Parse PRD Help'); + expect(result.stdout).toContain('No PRD file specified'); }); + }); - // Test 5: PRD parsing with dependencies - await runTest('PRD with explicit dependencies', async () => { - const dependencyPrd = `# Project with Dependencies - -## Tasks and Dependencies - -### 1. Database Setup -No dependencies - -### 2. User Model -Depends on: Database Setup - -### 3. Authentication Service -Depends on: User Model - -### 4. API Endpoints -Depends on: Authentication Service, User Model - -### 5. Frontend Integration -Depends on: API Endpoints - -## Additional Notes -Tasks should be completed in dependency order.`; - - helpers.writeFile(`${testDir}/dependency-prd.txt`, dependencyPrd); - - const result = await helpers.taskMaster( - 'parse-prd', - ['dependency-prd.txt'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); + describe('Performance and edge cases', () => { + it('should handle large PRD files', async () => { + // Create a large PRD with many requirements + let largePrd = '# Large Project Requirements\n\n'; + for (let i = 1; i <= 50; i++) { + largePrd += `## Feature ${i}\n`; + largePrd += `Build feature ${i} with the following requirements:\n`; + largePrd += `- Requirement A for feature ${i}\n`; + largePrd += `- Requirement B for feature ${i}\n`; + largePrd += `- Integration with feature ${i - 1}\n\n`; } - // Verify dependencies were set - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - const taskIds = {}; - - // Extract task IDs - const lines = listResult.stdout.split('\n'); - lines.forEach(line => { - if (line.includes('Database Setup')) { - const match = line.match(/(\d+)\s*\|/); - if (match) taskIds.database = match[1]; - } else if (line.includes('User Model')) { - const match = line.match(/(\d+)\s*\|/); - if (match) taskIds.userModel = match[1]; - } - }); - - // Check dependency relationships - if (taskIds.userModel && taskIds.database) { - const showResult = await helpers.taskMaster('show', [taskIds.userModel], { cwd: testDir }); - if (!showResult.stdout.includes(taskIds.database)) { - throw new Error('Dependencies not properly set from PRD'); - } - } - }); - - // Test 6: Error handling - non-existent file - await runTest('Error handling - non-existent file', async () => { - const result = await helpers.taskMaster( - 'parse-prd', - ['non-existent-prd.txt'], - { cwd: testDir, allowFailure: true } - ); - if (result.exitCode === 0) { - throw new Error('Should have failed with non-existent file'); - } - if (!result.stderr.includes('not found') && !result.stderr.includes('exist')) { - throw new Error('Error message does not indicate file not found'); - } - }); - - // Test 7: Error handling - malformed PRD - await runTest('Error handling - malformed PRD', async () => { - const malformedPrd = `This is not a valid PRD format - -Random text without structure -No headers or sections -Just plain text`; - - helpers.writeFile(`${testDir}/malformed-prd.txt`, malformedPrd); - - const result = await helpers.taskMaster( - 'parse-prd', - ['malformed-prd.txt'], - { cwd: testDir, allowFailure: true } - ); - - // Should either fail or create minimal tasks - if (result.exitCode === 0) { - // If it succeeds, should create at least one task - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - const taskCount = (listResult.stdout.match(/\d+\s*\|/g) || []).length; - if (taskCount === 0) { - throw new Error('No tasks created from malformed PRD'); - } - } - }); - - // Test 8: PRD parsing with different formats - await runTest('PRD parsing - JSON format', async () => { - const jsonPrd = { - "project": "Mobile App Development", - "features": [ - { - "name": "User Authentication", - "tasks": [ - "Design login UI", - "Implement OAuth integration", - "Add biometric authentication" - ] - }, - { - "name": "Data Synchronization", - "tasks": [ - "Implement offline mode", - "Create sync engine", - "Handle conflict resolution" - ] - } - ], - "technical": { - "platform": "React Native", - "backend": "Firebase" - } - }; - - helpers.writeFile(`${testDir}/json-prd.json`, JSON.stringify(jsonPrd, null, 2)); - - const result = await helpers.taskMaster( - 'parse-prd', - ['json-prd.json'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify tasks from JSON were created - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Authentication') || - !listResult.stdout.includes('Synchronization')) { - throw new Error('JSON PRD features not parsed correctly'); - } - }); - - // Test 9: PRD with markdown formatting - await runTest('PRD with rich markdown', async () => { - const markdownPrd = `# **Project**: Developer Tools Suite - -## 🎯 Goals -- Increase developer productivity -- Automate repetitive tasks -- Improve code quality - -## 📋 Features - -### Code Analysis Tool -- [ ] Static code analysis -- [ ] Security vulnerability scanning -- [ ] Performance profiling -- [ ] Code complexity metrics - -### Documentation Generator -1. **Auto-generate API docs** from code comments -2. **Create architecture diagrams** from codebase -3. **Generate changelog** from git history - -### Testing Framework -| Feature | Priority | Effort | -|---------|----------|--------| -| Unit test generation | High | Medium | -| Integration test templates | Medium | Low | -| Load testing suite | Low | High | - -## 🔗 Links -- [Design Docs](https://example.com/design) -- [API Specs](https://example.com/api) - -## ⚠️ Constraints -- Must support multiple programming languages -- Should integrate with existing CI/CD pipelines -- Performance impact < 5% on build times`; - - helpers.writeFile(`${testDir}/markdown-prd.md`, markdownPrd); - - const result = await helpers.taskMaster( - 'parse-prd', - ['markdown-prd.md'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Check parsing handled markdown elements - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Code Analysis') || - !listResult.stdout.includes('Documentation Generator')) { - throw new Error('Markdown formatting interfered with parsing'); - } - }); - - // Test 10: Large PRD performance test - await runTest('Performance - large PRD', async () => { - // Generate a large PRD - let largePrd = `# Large Enterprise System PRD\n\n`; - - for (let i = 1; i <= 20; i++) { - largePrd += `## Module ${i}: ${['User', 'Product', 'Order', 'Payment', 'Shipping'][i % 5]} Management\n\n`; - largePrd += `### Features\n`; - for (let j = 1; j <= 5; j++) { - largePrd += `- Feature ${i}.${j}: Implement ${['CRUD', 'Search', 'Filter', 'Export', 'Import'][j-1]} functionality\n`; - } - largePrd += `\n### Technical Requirements\n`; - largePrd += `- Database tables for module ${i}\n`; - largePrd += `- API endpoints for module ${i}\n`; - largePrd += `- Unit tests for module ${i}\n\n`; - } - - helpers.writeFile(`${testDir}/large-prd.txt`, largePrd); + const prdPath = join(testDir, 'large-prd.txt'); + writeFileSync(prdPath, largePrd); const startTime = Date.now(); const result = await helpers.taskMaster( 'parse-prd', - ['large-prd.txt'], - { cwd: testDir, timeout: 300000 } + [prdPath, '--num-tasks', '20'], + { cwd: testDir, timeout: 120000 } ); const duration = Date.now() - startTime; - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + expect(result).toHaveExitCode(0); + expect(duration).toBeLessThan(120000); // Should complete within 2 minutes - logger.info(`Large PRD parsed in ${duration}ms`); + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + expect(tasks.master.tasks.length).toBeGreaterThan(10); + }, 150000); + + it('should handle PRD with special characters', async () => { + const prdContent = `# Project: Système de Gestion 管理システム + +Build a system with: +- UTF-8 support: ñáéíóú αβγδε 中文字符 +- Special symbols: @#$%^&*()_+{}[]|\\:;"'<>,.?/ +- Emoji support: 🚀 📊 💻 ✅`; - // Should create many tasks - const taskCountMatch = result.stdout.match(/(\d+) tasks? created/i); - const taskCount = taskCountMatch ? parseInt(taskCountMatch[1]) : 0; - - if (taskCount < 20) { - throw new Error(`Large PRD should create more tasks (got ${taskCount})`); - } - logger.info(`Created ${taskCount} tasks from large PRD`); - }); - - // Test 11: PRD with images and diagrams references - await runTest('PRD with external references', async () => { - const referencePrd = `# System Architecture PRD - -## Overview -See architecture diagram: ![Architecture](./diagrams/system-arch.png) - -## Features -Based on the wireframes in /designs/wireframes/: - -1. Dashboard (see dashboard-wireframe.png) - - Real-time metrics display - - Customizable widgets - - Export functionality - -2. User Management (see user-flow.pdf) - - CRUD operations - - Role assignment - - Activity logging - -## API Design -Refer to swagger.yaml for detailed API specifications. - -## Database Schema -See database-schema.sql for table definitions.`; - - helpers.writeFile(`${testDir}/reference-prd.md`, referencePrd); + const prdPath = join(testDir, 'special-chars-prd.txt'); + writeFileSync(prdPath, prdContent, 'utf8'); const result = await helpers.taskMaster( 'parse-prd', - ['reference-prd.md'], - { cwd: testDir, timeout: 120000 } + [prdPath], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should parse content despite external references - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('Dashboard') || - !listResult.stdout.includes('User Management')) { - throw new Error('Failed to parse PRD with external references'); - } - }); - - // Test 12: PRD parsing with priority hints - await runTest('PRD with priority indicators', async () => { - const priorityPrd = `# Project Roadmap - -## Critical Features (P0) -- Security authentication system -- Data encryption at rest -- Audit logging - -## High Priority (P1) -- User dashboard -- Reporting module -- API rate limiting - -## Medium Priority (P2) -- Dark mode support -- Export to PDF -- Batch operations - -## Nice to Have (P3) -- Theme customization -- Advanced analytics -- Third-party integrations`; - - helpers.writeFile(`${testDir}/priority-prd.txt`, priorityPrd); + expect(result).toHaveExitCode(0); - const result = await helpers.taskMaster( - 'parse-prd', - ['priority-prd.txt'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasksContent = readFileSync(tasksPath, 'utf8'); + const tasks = JSON.parse(tasksContent); - // Check if priorities were recognized - // Get task details to verify priority assignment + // Verify special characters are preserved + expect(tasksContent).toContain('UTF-8'); + }, 60000); + }); + + describe('Integration with other commands', () => { + it('should work with list command after parsing', async () => { + const prdContent = 'Build a simple blog system'; + const prdPath = join(testDir, 'blog-prd.txt'); + writeFileSync(prdPath, prdContent); + + // Parse PRD + await helpers.taskMaster('parse-prd', [prdPath], { cwd: testDir }); + + // List tasks const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - // Find a critical task - const lines = listResult.stdout.split('\n'); - let criticalTaskId = null; - lines.forEach(line => { - if (line.includes('Security authentication') || line.includes('encryption')) { - const match = line.match(/(\d+)\s*\|/); - if (match) criticalTaskId = match[1]; - } - }); - - if (criticalTaskId) { - const showResult = await helpers.taskMaster('show', [criticalTaskId], { cwd: testDir }); - // Check if it has high priority - if (!showResult.stdout.includes('high') && !showResult.stdout.includes('High')) { - logger.warning('Critical tasks may not have been assigned high priority'); - } - } + expect(listResult).toHaveExitCode(0); + expect(listResult.stdout).toContain('ID'); + expect(listResult.stdout).toContain('Title'); + expect(listResult.stdout).toContain('pending'); }); - // Test 13: Multiple PRD files - await runTest('Parse multiple PRD files', async () => { - // Create multiple PRD files - const prd1 = `# Frontend Requirements -- Build responsive UI -- Implement state management -- Add unit tests`; - - const prd2 = `# Backend Requirements -- Design REST API -- Setup database -- Implement caching`; - - helpers.writeFile(`${testDir}/frontend-prd.txt`, prd1); - helpers.writeFile(`${testDir}/backend-prd.txt`, prd2); + it('should work with expand command on generated tasks', async () => { + const prdContent = 'Build user authentication'; + const prdPath = join(testDir, 'auth-prd.txt'); + writeFileSync(prdPath, prdContent); - const result = await helpers.taskMaster( - 'parse-prd', - ['frontend-prd.txt', 'backend-prd.txt'], - { cwd: testDir, timeout: 180000 } + // Parse PRD + await helpers.taskMaster('parse-prd', [prdPath], { cwd: testDir }); + + // Expand first task + const expandResult = await helpers.taskMaster( + 'expand', + ['--id', '1'], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should create tasks from both files - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('responsive UI') || - !listResult.stdout.includes('REST API')) { - throw new Error('Not all PRD files were parsed'); - } - }); - - // Test 14: PRD with code blocks - await runTest('PRD with code examples', async () => { - const codePrd = `# Technical Implementation PRD - -## Authentication Module - -Implement JWT-based authentication with the following structure: - -\`\`\`javascript -// Expected token payload -{ - userId: string, - email: string, - roles: string[], - exp: number -} -\`\`\` - -### Tasks: -1. Create token generation service -2. Implement token validation middleware -3. Add refresh token mechanism - -## Database Schema - -\`\`\`sql -CREATE TABLE users ( - id UUID PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - created_at TIMESTAMP DEFAULT NOW() -); -\`\`\` - -### Tasks: -1. Create database migrations -2. Add indexes for performance -3. Implement data validation`; - - helpers.writeFile(`${testDir}/code-prd.md`, codePrd); - - const result = await helpers.taskMaster( - 'parse-prd', - ['code-prd.md'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should parse tasks despite code blocks - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - if (!listResult.stdout.includes('token generation') || - !listResult.stdout.includes('database migrations')) { - throw new Error('Code blocks interfered with task parsing'); - } - }); - - // Test 15: PRD parsing with auto-grouping - await runTest('PRD auto-grouping into epics', async () => { - const epicPrd = `# E-Learning Platform - -## User Management Epic -- User registration and profiles -- Role-based access control -- Social login integration -- Password reset functionality - -## Course Management Epic -- Course creation tools -- Video upload and processing -- Quiz and assignment builder -- Progress tracking - -## Payment Processing Epic -- Subscription management -- Payment gateway integration -- Invoice generation -- Refund processing - -## Analytics Epic -- User engagement metrics -- Course completion rates -- Revenue analytics -- Custom reports`; - - helpers.writeFile(`${testDir}/epic-prd.txt`, epicPrd); - - const result = await helpers.taskMaster( - 'parse-prd', - ['epic-prd.txt', '--group-by-section'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should create grouped tasks - const listResult = await helpers.taskMaster('list', [], { cwd: testDir }); - - // Check for epic grouping (tasks might have similar IDs or tags) - if (!listResult.stdout.includes('User Management') || - !listResult.stdout.includes('Course Management')) { - throw new Error('Epic grouping not reflected in tasks'); - } - }); - - // Calculate summary - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'passed').length; - const failedTests = results.tests.filter(t => t.status === 'failed').length; - - logger.info('\n=== Parse-PRD Test Summary ==='); - logger.info(`Total tests: ${totalTests}`); - logger.info(`Passed: ${passedTests}`); - logger.info(`Failed: ${failedTests}`); - - if (failedTests > 0) { - results.status = 'failed'; - logger.error(`\n${failedTests} tests failed`); - } else { - logger.success('\n✅ All parse-prd tests passed!'); - } - - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'parse-prd test suite', - error: error.message, - stack: error.stack - }); - logger.error(`Parse-prd test suite failed: ${error.message}`); - } - - return results; -} \ No newline at end of file + expect(expandResult).toHaveExitCode(0); + expect(expandResult.stdout).toContain('Expanded task'); + }, 90000); + }); +}); \ No newline at end of file diff --git a/tests/e2e/tests/commands/research.test.js b/tests/e2e/tests/commands/research.test.js index 1a9392b5..b3a7001f 100644 --- a/tests/e2e/tests/commands/research.test.js +++ b/tests/e2e/tests/commands/research.test.js @@ -3,422 +3,488 @@ * Tests all aspects of AI-powered research functionality */ -export default async function testResearch(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [], - tests: [] - }; +const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); +const { tmpdir } = require('os'); - async function runTest(name, testFn) { - try { - logger.info(`\nRunning: ${name}`); - await testFn(); - results.tests.push({ name, status: 'passed' }); - logger.success(`✓ ${name}`); - } catch (error) { - results.tests.push({ name, status: 'failed', error: error.message }); - results.errors.push({ test: name, error: error.message }); - logger.error(`✗ ${name}: ${error.message}`); +describe('research command', () => { + let testDir; + let helpers; + + beforeEach(async () => { + // Create test directory + testDir = mkdtempSync(join(tmpdir(), 'task-master-research-')); + + // Initialize test helpers + const context = global.createTestContext('research'); + helpers = context.helpers; + + // Copy .env file if it exists + const mainEnvPath = join(__dirname, '../../../../.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); + }); - try { - logger.info('Starting comprehensive research tests...'); + afterEach(() => { + // Clean up test directory + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } + }); - // Test 1: Basic research on a topic - await runTest('Basic research query', async () => { + describe('Basic research functionality', () => { + it('should perform research on a topic', async () => { const result = await helpers.taskMaster( 'research', - ['What are the best practices for implementing JWT authentication in Node.js?'], - { cwd: testDir, timeout: 120000 } + ['What are the best practices for implementing OAuth 2.0 authentication?'], + { cwd: testDir, timeout: 90000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Check for relevant research output - const output = result.stdout.toLowerCase(); - if (!output.includes('jwt') || !output.includes('authentication')) { - throw new Error('Research output does not contain expected keywords'); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); - // Should provide actionable information - const hasActionableInfo = output.includes('implement') || - output.includes('use') || - output.includes('practice') || - output.includes('security'); - if (!hasActionableInfo) { - throw new Error('Research output lacks actionable information'); - } - }); + // Should contain relevant OAuth information + const hasOAuthInfo = result.stdout.toLowerCase().includes('oauth') || + result.stdout.toLowerCase().includes('authentication'); + expect(hasOAuthInfo).toBe(true); + }, 120000); - // Test 2: Research with specific context - await runTest('Research with project context', async () => { - // Create a task to provide context - const taskResult = await helpers.taskMaster( - 'add-task', - ['--title', 'Implement user authentication', '--description', 'Need to add secure login to our Express.js API'], - { cwd: testDir } - ); - const taskId = helpers.extractTaskId(taskResult.stdout); - + it('should research using --topic flag', async () => { const result = await helpers.taskMaster( 'research', - ['--task', taskId, 'Compare bcrypt vs argon2 for password hashing'], - { cwd: testDir, timeout: 120000 } + ['--topic', 'React performance optimization techniques'], + { cwd: testDir, timeout: 90000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should mention both technologies - const output = result.stdout.toLowerCase(); - if (!output.includes('bcrypt') || !output.includes('argon2')) { - throw new Error('Research did not compare both technologies'); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); - // Should relate to the task context - if (!output.includes('password') || !output.includes('hash')) { - throw new Error('Research not relevant to password hashing'); - } - }); + // Should contain React-related information + const hasReactInfo = result.stdout.toLowerCase().includes('react') || + result.stdout.toLowerCase().includes('performance'); + expect(hasReactInfo).toBe(true); + }, 120000); - // Test 3: Research with output format options - await runTest('Research with markdown output', async () => { + it('should handle technical research queries', async () => { const result = await helpers.taskMaster( 'research', - ['--format', 'markdown', 'How to implement rate limiting in REST APIs?'], - { cwd: testDir, timeout: 120000 } + ['Compare PostgreSQL vs MongoDB for a real-time analytics application'], + { cwd: testDir, timeout: 90000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Check for markdown formatting - const hasMarkdown = result.stdout.includes('#') || - result.stdout.includes('*') || - result.stdout.includes('-') || - result.stdout.includes('```'); - if (!hasMarkdown) { - throw new Error('Output does not appear to be in markdown format'); - } - }); + expect(result).toHaveExitCode(0); + + // Should contain database comparison + const hasDatabaseInfo = result.stdout.toLowerCase().includes('postgresql') || + result.stdout.toLowerCase().includes('mongodb'); + expect(hasDatabaseInfo).toBe(true); + }, 120000); + }); - // Test 4: Research with depth parameter - await runTest('Research with depth control', async () => { - const shallowResult = await helpers.taskMaster( - 'research', - ['--depth', 'shallow', 'React state management options'], - { cwd: testDir, timeout: 120000 } - ); - - const deepResult = await helpers.taskMaster( - 'research', - ['--depth', 'deep', 'React state management options'], - { cwd: testDir, timeout: 180000 } - ); - - if (shallowResult.exitCode !== 0 || deepResult.exitCode !== 0) { - throw new Error('Research with depth parameter failed'); - } - - // Deep research should provide more content - if (deepResult.stdout.length <= shallowResult.stdout.length) { - throw new Error('Deep research did not provide more detailed information'); - } - - // Both should mention state management solutions - const solutions = ['redux', 'context', 'mobx', 'zustand', 'recoil']; - const shallowMentions = solutions.filter(s => shallowResult.stdout.toLowerCase().includes(s)).length; - const deepMentions = solutions.filter(s => deepResult.stdout.toLowerCase().includes(s)).length; - - if (deepMentions <= shallowMentions) { - throw new Error('Deep research should cover more solutions'); - } - }); - - // Test 5: Research for multiple tasks - await runTest('Research across multiple tasks', async () => { - // Create related tasks - const task1 = await helpers.taskMaster( - 'add-task', - ['--title', 'Setup database connection'], - { cwd: testDir } - ); - const taskId1 = helpers.extractTaskId(task1.stdout); - - const task2 = await helpers.taskMaster( - 'add-task', - ['--title', 'Implement caching layer'], - { cwd: testDir } - ); - const taskId2 = helpers.extractTaskId(task2.stdout); - - const result = await helpers.taskMaster( - 'research', - ['--tasks', `${taskId1},${taskId2}`, 'Best practices for database connection pooling and Redis caching'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should cover both topics - const output = result.stdout.toLowerCase(); - if (!output.includes('database') || !output.includes('connection')) { - throw new Error('Research did not cover database connections'); - } - if (!output.includes('redis') || !output.includes('cach')) { - throw new Error('Research did not cover caching'); - } - }); - - // Test 6: Research with source preferences - await runTest('Research with source preferences', async () => { - const result = await helpers.taskMaster( - 'research', - ['--sources', 'official-docs,stackoverflow', 'How to use React hooks effectively?'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should focus on practical examples - const output = result.stdout.toLowerCase(); - if (!output.includes('hook') || !output.includes('react')) { - throw new Error('Research not relevant to React hooks'); - } - }); - - // Test 7: Research with language/framework context - await runTest('Research with technology context', async () => { - const result = await helpers.taskMaster( - 'research', - ['--context', 'python,django', 'How to optimize database queries?'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should be Python/Django specific - const output = result.stdout.toLowerCase(); - if (!output.includes('django') || !output.includes('orm') || !output.includes('queryset')) { - throw new Error('Research not specific to Django context'); - } - }); - - // Test 8: Research error handling - empty query - await runTest('Error handling - empty query', async () => { - const result = await helpers.taskMaster( - 'research', - [''], - { cwd: testDir, allowFailure: true } - ); - if (result.exitCode === 0) { - throw new Error('Should have failed with empty query'); - } - }); - - // Test 9: Research with time constraints - await runTest('Research with recency filter', async () => { - const result = await helpers.taskMaster( - 'research', - ['--since', '2023', 'Latest JavaScript features and ES2024 updates'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should mention recent features - const output = result.stdout.toLowerCase(); - const recentFeatures = ['es2023', 'es2024', '2023', '2024', 'latest', 'recent']; - const mentionsRecent = recentFeatures.some(feature => output.includes(feature)); - - if (!mentionsRecent) { - throw new Error('Research did not focus on recent information'); - } - }); - - // Test 10: Research with comparison request - await runTest('Research comparison analysis', async () => { - const result = await helpers.taskMaster( - 'research', - ['Compare REST vs GraphQL vs gRPC for microservices communication'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should mention all three technologies - const output = result.stdout.toLowerCase(); - if (!output.includes('rest') || !output.includes('graphql') || !output.includes('grpc')) { - throw new Error('Research did not compare all three technologies'); - } - - // Should include pros/cons or comparison points - const hasComparison = output.includes('advantage') || - output.includes('disadvantage') || - output.includes('pros') || - output.includes('cons') || - output.includes('better') || - output.includes('when to use'); - if (!hasComparison) { - throw new Error('Research lacks comparative analysis'); - } - }); - - // Test 11: Research with code examples request - await runTest('Research with code examples', async () => { - const result = await helpers.taskMaster( - 'research', - ['--include-examples', 'How to implement a singleton pattern in TypeScript?'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should include code blocks - if (!result.stdout.includes('```') && !result.stdout.includes('class') && !result.stdout.includes('function')) { - throw new Error('Research did not include code examples'); - } - - // Should be TypeScript specific - const output = result.stdout.toLowerCase(); - if (!output.includes('typescript') && !output.includes('private constructor')) { - throw new Error('Examples not specific to TypeScript'); - } - }); - - // Test 12: Research for architecture decisions - await runTest('Research for architecture decisions', async () => { - const result = await helpers.taskMaster( - 'research', - ['--type', 'architecture', 'Microservices vs monolithic architecture for a startup'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should provide architectural insights - const output = result.stdout.toLowerCase(); - const archKeywords = ['scalability', 'deployment', 'complexity', 'team size', 'maintenance', 'cost']; - const mentionedKeywords = archKeywords.filter(keyword => output.includes(keyword)).length; - - if (mentionedKeywords < 3) { - throw new Error('Research lacks architectural considerations'); - } - }); - - // Test 13: Research with tag context - await runTest('Research within tag context', async () => { - // Create tag and tagged tasks - await helpers.taskMaster('add-tag', ['security-research'], { cwd: testDir }); - - const result = await helpers.taskMaster( - 'research', - ['--tag', 'security-research', 'OWASP top 10 vulnerabilities and mitigation strategies'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should focus on security - const output = result.stdout.toLowerCase(); - const securityTerms = ['vulnerability', 'security', 'attack', 'protection', 'owasp', 'mitigation']; - const mentionedTerms = securityTerms.filter(term => output.includes(term)).length; - - if (mentionedTerms < 4) { - throw new Error('Research not focused on security topics'); - } - }); - - // Test 14: Research performance with complex query - await runTest('Performance - complex research query', async () => { + describe('Research depth control', () => { + it('should perform quick research with --quick flag', async () => { const startTime = Date.now(); const result = await helpers.taskMaster( 'research', - ['Comprehensive guide to building a scalable real-time chat application with WebSockets, including architecture, database design, message queuing, and deployment strategies'], - { cwd: testDir, timeout: 180000 } + ['--topic', 'REST API design', '--quick'], + { cwd: testDir, timeout: 60000 } ); const duration = Date.now() - startTime; - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); - logger.info(`Complex research completed in ${duration}ms`); - - // Should cover all requested topics - const output = result.stdout.toLowerCase(); - const topics = ['websocket', 'architecture', 'database', 'queue', 'deployment', 'scalab']; - const coveredTopics = topics.filter(topic => output.includes(topic)).length; - - if (coveredTopics < 4) { - throw new Error('Complex research did not cover all requested topics'); - } - }); + // Quick research should be faster + expect(duration).toBeLessThan(60000); + }, 90000); - // Test 15: Research with export option (preparing for research-save) - await runTest('Research with export preparation', async () => { + it('should perform detailed research with --detailed flag', async () => { const result = await helpers.taskMaster( 'research', - ['--prepare-export', 'Best practices for API versioning'], + ['--topic', 'Microservices architecture patterns', '--detailed'], { cwd: testDir, timeout: 120000 } ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); + + // Detailed research should have more content + expect(result.stdout.length).toBeGreaterThan(500); + + // Should contain comprehensive information + const hasPatterns = result.stdout.toLowerCase().includes('pattern') || + result.stdout.toLowerCase().includes('architecture'); + expect(hasPatterns).toBe(true); + }, 150000); + }); + + describe('Research with citations', () => { + it('should include sources with --sources flag', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'GraphQL best practices', '--sources'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); + + // Should include source references + const hasSources = result.stdout.includes('Source:') || + result.stdout.includes('Reference:') || + result.stdout.includes('http'); + expect(hasSources).toBe(true); + }, 120000); + }); + + describe('Research output options', () => { + it('should save research to file with --save flag', async () => { + const outputPath = join(testDir, 'research-output.md'); + + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Docker container security', '--save', outputPath], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research saved to'); + + // Verify file was created + expect(existsSync(outputPath)).toBe(true); + + // Verify file contains research content + const content = readFileSync(outputPath, 'utf8'); + expect(content).toContain('Docker'); + expect(content.length).toBeGreaterThan(100); + }, 120000); + + it('should output in JSON format', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'WebSocket implementation', '--output', 'json'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + + // Output should be valid JSON + const jsonOutput = JSON.parse(result.stdout); + expect(jsonOutput.topic).toBeDefined(); + expect(jsonOutput.research).toBeDefined(); + expect(jsonOutput.timestamp).toBeDefined(); + }, 120000); + + it('should output in markdown format by default', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'CI/CD pipeline best practices'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + + // Should contain markdown formatting + const hasMarkdown = result.stdout.includes('#') || + result.stdout.includes('*') || + result.stdout.includes('-'); + expect(hasMarkdown).toBe(true); + }, 120000); + }); + + describe('Research categories', () => { + it('should research coding patterns', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Singleton pattern in JavaScript', '--category', 'patterns'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout.toLowerCase()).toContain('singleton'); + expect(result.stdout.toLowerCase()).toContain('pattern'); + }, 120000); + + it('should research security topics', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'OWASP Top 10 vulnerabilities', '--category', 'security'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout.toLowerCase()).toContain('security'); + expect(result.stdout.toUpperCase()).toContain('OWASP'); + }, 120000); + + it('should research performance topics', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Database query optimization', '--category', 'performance'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout.toLowerCase()).toContain('optimization'); + expect(result.stdout.toLowerCase()).toContain('performance'); + }, 120000); + }); + + describe('Research integration with tasks', () => { + it('should research for specific task context', async () => { + // Create a task first + const addResult = await helpers.taskMaster( + 'add-task', + ['--prompt', 'Implement real-time chat feature'], + { cwd: testDir } + ); + const taskId = helpers.extractTaskId(addResult.stdout); + + // Research for the task + const result = await helpers.taskMaster( + 'research', + ['--task', taskId, '--topic', 'WebSocket vs Server-Sent Events'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research Results'); + expect(result.stdout.toLowerCase()).toContain('websocket'); + }, 120000); + + it('should append research to task notes', async () => { + // Create a task + const addResult = await helpers.taskMaster( + 'add-task', + ['--prompt', 'Setup monitoring system'], + { cwd: testDir } + ); + const taskId = helpers.extractTaskId(addResult.stdout); + + // Research and append to task + const result = await helpers.taskMaster( + 'research', + ['--task', taskId, '--topic', 'Prometheus vs ELK stack', '--append-to-task'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research appended to task'); + + // Verify task has research notes + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('prometheus'); + }, 120000); + }); + + describe('Research history', () => { + it('should save research history', async () => { + // Perform multiple researches + await helpers.taskMaster( + 'research', + ['--topic', 'GraphQL subscriptions'], + { cwd: testDir, timeout: 60000 } + ); + + await helpers.taskMaster( + 'research', + ['--topic', 'Redis pub/sub'], + { cwd: testDir, timeout: 60000 } + ); + + // Check research history + const historyPath = join(testDir, '.taskmaster/research-history.json'); + if (existsSync(historyPath)) { + const history = JSON.parse(readFileSync(historyPath, 'utf8')); + expect(history.length).toBeGreaterThanOrEqual(2); + } + }, 150000); + + it('should list recent research with --history flag', async () => { + // Perform a research first + await helpers.taskMaster( + 'research', + ['--topic', 'Kubernetes deployment strategies'], + { cwd: testDir, timeout: 60000 } + ); + + // List history + const result = await helpers.taskMaster( + 'research', + ['--history'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Research History'); + }, 90000); + }); + + describe('Error handling', () => { + it('should fail without topic', async () => { + const result = await helpers.taskMaster( + 'research', + [], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('topic'); + }); + + it('should handle invalid output format', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Test topic', '--output', 'invalid-format'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid output format'); + }); + + it('should handle network errors gracefully', async () => { + // This test might pass if network is available + // It's mainly to ensure the command handles errors gracefully + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Test with potential network issues'], + { cwd: testDir, timeout: 30000, allowFailure: true } + ); + + // Should either succeed or fail gracefully if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); + expect(result.stderr).toBeTruthy(); + } else { + expect(result.stdout).toContain('Research Results'); } + }, 45000); + }); + + describe('Research focus areas', () => { + it('should research implementation details', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'JWT implementation in Node.js', '--focus', 'implementation'], + { cwd: testDir, timeout: 90000 } + ); - // Should indicate export readiness - if (!result.stdout.includes('API') || !result.stdout.includes('version')) { - throw new Error('Research content not relevant to query'); - } + expect(result).toHaveExitCode(0); + expect(result.stdout.toLowerCase()).toContain('implementation'); + expect(result.stdout.toLowerCase()).toContain('code'); + }, 120000); + + it('should research best practices', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'REST API versioning', '--focus', 'best-practices'], + { cwd: testDir, timeout: 90000 } + ); - // Check if research is structured for saving - const hasStructure = result.stdout.includes('#') || - result.stdout.includes('##') || - result.stdout.includes('1.') || - result.stdout.includes('*'); - if (!hasStructure) { - throw new Error('Research not well-structured for export'); + expect(result).toHaveExitCode(0); + expect(result.stdout.toLowerCase()).toContain('best practice'); + }, 120000); + + it('should research comparisons', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Vue vs React vs Angular', '--focus', 'comparison'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + const output = result.stdout.toLowerCase(); + expect(output).toContain('vue'); + expect(output).toContain('react'); + expect(output).toContain('angular'); + }, 120000); + }); + + describe('Research with constraints', () => { + it('should limit research length with --max-length', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Machine learning basics', '--max-length', '500'], + { cwd: testDir, timeout: 60000 } + ); + + expect(result).toHaveExitCode(0); + // Research output should be concise + expect(result.stdout.length).toBeLessThan(2000); // Accounting for formatting + }, 90000); + + it('should research with specific year constraint', async () => { + const result = await helpers.taskMaster( + 'research', + ['--topic', 'Latest JavaScript features', '--year', '2024'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + // Should focus on recent content + const hasRecentInfo = result.stdout.includes('2024') || + result.stdout.toLowerCase().includes('latest') || + result.stdout.toLowerCase().includes('recent'); + expect(hasRecentInfo).toBe(true); + }, 120000); + }); + + describe('Research caching', () => { + it('should cache and reuse research results', async () => { + const topic = 'Redis caching strategies'; + + // First research + const startTime1 = Date.now(); + const result1 = await helpers.taskMaster( + 'research', + ['--topic', topic], + { cwd: testDir, timeout: 90000 } + ); + const duration1 = Date.now() - startTime1; + expect(result1).toHaveExitCode(0); + + // Second research (should be cached) + const startTime2 = Date.now(); + const result2 = await helpers.taskMaster( + 'research', + ['--topic', topic], + { cwd: testDir, timeout: 30000 } + ); + const duration2 = Date.now() - startTime2; + expect(result2).toHaveExitCode(0); + + // Cached result should be much faster + if (result2.stdout.includes('(cached)')) { + expect(duration2).toBeLessThan(duration1 / 2); } - }); + }, 150000); - // Calculate summary - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'passed').length; - const failedTests = results.tests.filter(t => t.status === 'failed').length; - - logger.info('\n=== Research Test Summary ==='); - logger.info(`Total tests: ${totalTests}`); - logger.info(`Passed: ${passedTests}`); - logger.info(`Failed: ${failedTests}`); - - if (failedTests > 0) { - results.status = 'failed'; - logger.error(`\n${failedTests} tests failed`); - } else { - logger.success('\n✅ All research tests passed!'); - } - - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'research test suite', - error: error.message, - stack: error.stack - }); - logger.error(`Research test suite failed: ${error.message}`); - } - - return results; -} \ No newline at end of file + it('should bypass cache with --no-cache flag', async () => { + const topic = 'Docker best practices'; + + // First research + await helpers.taskMaster( + 'research', + ['--topic', topic], + { cwd: testDir, timeout: 60000 } + ); + + // Second research without cache + const result = await helpers.taskMaster( + 'research', + ['--topic', topic, '--no-cache'], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).not.toContain('(cached)'); + }, 180000); + }); +}); \ No newline at end of file diff --git a/tests/e2e/tests/commands/update-subtask.test.js b/tests/e2e/tests/commands/update-subtask.test.js index f74a9ea1..c748118a 100644 --- a/tests/e2e/tests/commands/update-subtask.test.js +++ b/tests/e2e/tests/commands/update-subtask.test.js @@ -1,475 +1,565 @@ /** * Comprehensive E2E tests for update-subtask command - * Tests all aspects of subtask updates including AI and manual modes + * Tests all aspects of subtask updates including AI-powered updates */ -export default async function testUpdateSubtask(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [], - tests: [] - }; +const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); +const { tmpdir } = require('os'); - async function runTest(name, testFn) { - try { - logger.info(`\nRunning: ${name}`); - await testFn(); - results.tests.push({ name, status: 'passed' }); - logger.success(`✓ ${name}`); - } catch (error) { - results.tests.push({ name, status: 'failed', error: error.message }); - results.errors.push({ test: name, error: error.message }); - logger.error(`✗ ${name}: ${error.message}`); - } - } +describe('update-subtask command', () => { + let testDir; + let helpers; + let parentTaskId; + let subtaskId; - try { - logger.info('Starting comprehensive update-subtask tests...'); - - // Setup: Create parent task with subtasks - logger.info('Setting up parent task with subtasks...'); + beforeEach(async () => { + // Create test directory + testDir = mkdtempSync(join(tmpdir(), 'task-master-update-subtask-')); - // Create parent task + // Initialize test helpers + const context = global.createTestContext('update-subtask'); + helpers = context.helpers; + + // Copy .env file if it exists + const mainEnvPath = join(__dirname, '../../../../.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); + + // Create a parent task with subtask const parentResult = await helpers.taskMaster( 'add-task', - ['--prompt', 'Build a user authentication system'], + ['--title', 'Parent task', '--description', 'Task with subtasks'], { cwd: testDir } ); - const parentTaskId = helpers.extractTaskId(parentResult.stdout); + parentTaskId = helpers.extractTaskId(parentResult.stdout); - // Expand to get AI-generated subtasks - await helpers.taskMaster('expand', [parentTaskId], { cwd: testDir, timeout: 120000 }); - - // Add some manual subtasks - await helpers.taskMaster( + // Create a subtask + const subtaskResult = await helpers.taskMaster( 'add-subtask', - [parentTaskId, 'Setup database schema'], - { cwd: testDir } - ); - await helpers.taskMaster( - 'add-subtask', - [parentTaskId, 'Create login endpoint'], + [parentTaskId, 'Initial subtask'], { cwd: testDir } ); + // Extract subtask ID (should be like "1.1") + const match = subtaskResult.stdout.match(/subtask #?(\d+\.\d+)/i); + subtaskId = match ? match[1] : '1.1'; + }); - // Test 1: Basic AI-powered subtask update - await runTest('AI-powered subtask update', async () => { - const subtaskId = `${parentTaskId}.1`; + afterEach(() => { + // Clean up test directory + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } + }); + + describe('Basic subtask updates', () => { + it('should update subtask title', async () => { const result = await helpers.taskMaster( 'update-subtask', - ['--id', subtaskId, '--prompt', 'Make this subtask focus on JWT token implementation'], - { cwd: testDir, timeout: 120000 } + [subtaskId, 'Updated subtask title'], + { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify subtask was updated + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated subtask'); + + // Verify update const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('JWT') || !showResult.stdout.includes('token')) { - throw new Error('Subtask not updated with JWT focus'); - } + expect(showResult.stdout).toContain('Updated subtask title'); }); - // Test 2: Manual subtask update (without AI) - await runTest('Manual subtask update', async () => { - const subtaskId = `${parentTaskId}.2`; + it('should update subtask with additional notes', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--notes', 'Implementation details: Use async/await'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify notes were added + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('async/await'); + }); + + it('should update subtask status', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--status', 'completed'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify status update + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('completed'); + }); + }); + + describe('AI-powered subtask updates', () => { + it('should update subtask using AI prompt', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--prompt', 'Add implementation steps and best practices'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated subtask'); + + // Verify AI enhanced the subtask + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const parentTask = tasks.master.tasks.find(t => t.id === parseInt(parentTaskId)); + const subtask = parentTask.subtasks.find(s => s.id === subtaskId); + + // Should have more detailed content + expect(subtask.title.length).toBeGreaterThan(20); + }, 60000); + + it('should enhance subtask with technical details', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--prompt', 'Add technical requirements and edge cases to consider'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + // Check that subtask was enhanced + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + const hasEnhancement = showResult.stdout.toLowerCase().includes('requirement') || + showResult.stdout.toLowerCase().includes('edge case') || + showResult.stdout.toLowerCase().includes('consider'); + expect(hasEnhancement).toBe(true); + }, 60000); + + it('should update subtask with research mode', async () => { const result = await helpers.taskMaster( 'update-subtask', [ - '--id', subtaskId, - '--title', 'Implement OAuth 2.0 integration', - '--description', 'Add support for Google and GitHub OAuth providers' + subtaskId, + '--prompt', 'Add industry best practices for error handling', + '--research' + ], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + + // Research mode should add comprehensive content + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + const hasResearchContent = showResult.stdout.toLowerCase().includes('error') || + showResult.stdout.toLowerCase().includes('handling') || + showResult.stdout.toLowerCase().includes('practice'); + expect(hasResearchContent).toBe(true); + }, 120000); + }); + + describe('Multiple subtask updates', () => { + it('should update multiple subtasks sequentially', async () => { + // Create another subtask + const subtask2Result = await helpers.taskMaster( + 'add-subtask', + [parentTaskId, 'Second subtask'], + { cwd: testDir } + ); + const match = subtask2Result.stdout.match(/subtask #?(\d+\.\d+)/i); + const subtaskId2 = match ? match[1] : '1.2'; + + // Update first subtask + await helpers.taskMaster( + 'update-subtask', + [subtaskId, 'First subtask updated'], + { cwd: testDir } + ); + + // Update second subtask + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId2, 'Second subtask updated'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify both updates + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('First subtask updated'); + expect(showResult.stdout).toContain('Second subtask updated'); + }); + }); + + describe('Subtask metadata updates', () => { + it('should add priority to subtask', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--priority', 'high'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify priority was set + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('high'); + }); + + it('should add estimated time to subtask', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--estimated-time', '2h'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify estimated time was set + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('2h'); + }); + + it('should add assignee to subtask', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--assignee', 'john.doe@example.com'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify assignee was set + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('john.doe'); + }); + }); + + describe('Combined updates', () => { + it('should update title and notes together', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [ + subtaskId, + 'New comprehensive title', + '--notes', 'Additional implementation details' ], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify exact updates + expect(result).toHaveExitCode(0); + + // Verify both updates const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('OAuth 2.0')) { - throw new Error('Subtask title not updated'); - } - if (!showResult.stdout.includes('Google') || !showResult.stdout.includes('GitHub')) { - throw new Error('Subtask description not updated'); - } + expect(showResult.stdout).toContain('New comprehensive title'); + expect(showResult.stdout).toContain('Additional implementation details'); }); - // Test 3: Update subtask status - await runTest('Update subtask status', async () => { - const subtaskId = `${parentTaskId}.3`; + it('should combine manual update with AI prompt', async () => { const result = await helpers.taskMaster( 'update-subtask', - ['--id', subtaskId, '--status', 'in_progress'], - { cwd: testDir } + [ + subtaskId, + '--status', 'in_progress', + '--prompt', 'Add acceptance criteria' + ], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify status change - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - const subtask = parentTask.subtasks.find(s => s.id === subtaskId); + expect(result).toHaveExitCode(0); - if (subtask.status !== 'in_progress') { - throw new Error('Subtask status not updated'); - } - }); - - // Test 4: Update subtask priority - await runTest('Update subtask priority', async () => { - const subtaskId = `${parentTaskId}.4`; - const result = await helpers.taskMaster( - 'update-subtask', - ['--id', subtaskId, '--priority', 'high'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify priority change - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - const subtask = parentTask.subtasks.find(s => s.id === subtaskId); - - if (subtask.priority !== 'high') { - throw new Error('Subtask priority not updated'); - } - }); - - // Test 5: Batch subtask updates - await runTest('Batch subtask updates', async () => { - // Update multiple subtasks at once - const subtaskIds = [`${parentTaskId}.1`, `${parentTaskId}.2`]; - const result = await helpers.taskMaster( - 'update-subtask', - ['--ids', subtaskIds.join(','), '--status', 'completed'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify all were updated - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - - subtaskIds.forEach(id => { - const subtask = parentTask.subtasks.find(s => s.id === id); - if (subtask.status !== 'completed') { - throw new Error(`Subtask ${id} not updated in batch`); - } - }); - }); - - // Test 6: Update subtask with dependencies - await runTest('Update subtask dependencies', async () => { - const subtask1 = `${parentTaskId}.3`; - const subtask2 = `${parentTaskId}.4`; - - const result = await helpers.taskMaster( - 'update-subtask', - ['--id', subtask2, '--depends-on', subtask1], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify dependency was added - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - const subtask = parentTask.subtasks.find(s => s.id === subtask2); - - if (!subtask.dependencies || !subtask.dependencies.includes(subtask1)) { - throw new Error('Subtask dependency not added'); - } - }); - - // Test 7: AI enhancement of existing subtask - await runTest('AI enhancement of manual subtask', async () => { - // Get last manual subtask + // Verify both manual and AI updates const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - const subtaskMatches = showResult.stdout.match(/(\d+\.\d+)/g) || []; - const lastSubtaskId = subtaskMatches[subtaskMatches.length - 1]; - - const result = await helpers.taskMaster( - 'update-subtask', - ['--id', lastSubtaskId, '--enhance', '--prompt', 'Add security considerations'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should include security aspects - const updatedShow = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - const hasSecurityMention = updatedShow.stdout.toLowerCase().includes('security') || - updatedShow.stdout.toLowerCase().includes('secure') || - updatedShow.stdout.toLowerCase().includes('protection'); - - if (!hasSecurityMention) { - throw new Error('AI enhancement did not add security considerations'); - } - }); + expect(showResult.stdout.toLowerCase()).toContain('in_progress'); + const hasAcceptanceCriteria = showResult.stdout.toLowerCase().includes('acceptance') || + showResult.stdout.toLowerCase().includes('criteria'); + expect(hasAcceptanceCriteria).toBe(true); + }, 60000); + }); - // Test 8: Error handling - invalid subtask ID - await runTest('Error handling - invalid subtask ID', async () => { + describe('Append mode', () => { + it('should append to subtask notes', async () => { + // First set some notes + await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--notes', 'Initial notes.'], + { cwd: testDir } + ); + + // Then append const result = await helpers.taskMaster( 'update-subtask', - ['--id', '999.999', '--title', 'Invalid update'], + [subtaskId, '--append-notes', '\nAdditional considerations.'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify notes were appended + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Initial notes'); + expect(showResult.stdout).toContain('Additional considerations'); + }); + }); + + describe('Nested subtasks', () => { + it('should update nested subtask', async () => { + // Create a nested subtask + const nestedResult = await helpers.taskMaster( + 'add-subtask', + [subtaskId, 'Nested subtask'], + { cwd: testDir } + ); + const match = nestedResult.stdout.match(/subtask #?(\d+\.\d+\.\d+)/i); + const nestedId = match ? match[1] : '1.1.1'; + + // Update nested subtask + const result = await helpers.taskMaster( + 'update-subtask', + [nestedId, 'Updated nested subtask'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify update + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Updated nested subtask'); + }); + }); + + describe('Tag-specific subtask updates', () => { + it('should update subtask in specific tag', async () => { + // Create a tag and add task to it + await helpers.taskMaster('add-tag', ['feature-y'], { cwd: testDir }); + + // Create task in tag + const tagTaskResult = await helpers.taskMaster( + 'add-task', + ['--prompt', 'Task in feature-y', '--tag', 'feature-y'], + { cwd: testDir } + ); + const tagTaskId = helpers.extractTaskId(tagTaskResult.stdout); + + // Add subtask to tagged task + const tagSubtaskResult = await helpers.taskMaster( + 'add-subtask', + [tagTaskId, 'Subtask in feature tag'], + { cwd: testDir, options: ['--tag', 'feature-y'] } + ); + const match = tagSubtaskResult.stdout.match(/subtask #?(\d+\.\d+)/i); + const tagSubtaskId = match ? match[1] : '1.1'; + + // Update subtask in specific tag + const result = await helpers.taskMaster( + 'update-subtask', + [tagSubtaskId, 'Updated in feature tag', '--tag', 'feature-y'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify update in correct tag + const showResult = await helpers.taskMaster( + 'show', + [tagTaskId, '--tag', 'feature-y'], + { cwd: testDir } + ); + expect(showResult.stdout).toContain('Updated in feature tag'); + }); + }); + + describe('Output formats', () => { + it('should output in JSON format', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, 'JSON test update', '--output', 'json'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Output should be valid JSON + const jsonOutput = JSON.parse(result.stdout); + expect(jsonOutput.success).toBe(true); + expect(jsonOutput.subtask).toBeDefined(); + expect(jsonOutput.subtask.title).toBe('JSON test update'); + expect(jsonOutput.parentTaskId).toBe(parseInt(parentTaskId)); + }); + }); + + describe('Error handling', () => { + it('should fail with non-existent subtask ID', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + ['99.99', 'This should fail'], { cwd: testDir, allowFailure: true } ); - if (result.exitCode === 0) { - throw new Error('Should have failed with invalid subtask ID'); - } - if (!result.stderr.includes('not found') && !result.stderr.includes('invalid')) { - throw new Error('Error message not clear about invalid ID'); - } + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('not found'); }); - // Test 9: Update subtask metadata - await runTest('Update subtask metadata', async () => { - const subtaskId = `${parentTaskId}.1`; + it('should fail with invalid subtask ID format', async () => { const result = await helpers.taskMaster( 'update-subtask', - [ - '--id', subtaskId, - '--metadata', 'assigned_to=john@example.com', - '--metadata', 'estimated_hours=4' - ], + ['invalid-id', 'This should fail'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid subtask ID'); + }); + + it('should fail with invalid priority', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--priority', 'invalid-priority'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid priority'); + }); + + it('should fail with invalid status', async () => { + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--status', 'invalid-status'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid status'); + }); + }); + + describe('Performance and edge cases', () => { + it('should handle very long subtask titles', async () => { + const longTitle = 'This is a very detailed subtask title. '.repeat(10); + + const result = await helpers.taskMaster( + 'update-subtask', + [subtaskId, longTitle], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify metadata - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); + expect(result).toHaveExitCode(0); + + // Verify long title was saved + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const parentTask = tasks.master.tasks.find(t => t.id === parseInt(parentTaskId)); const subtask = parentTask.subtasks.find(s => s.id === subtaskId); - - if (!subtask.metadata || subtask.metadata.assigned_to !== 'john@example.com') { - throw new Error('Subtask metadata not updated'); - } + expect(subtask.title).toBe(longTitle); }); - // Test 10: Update with validation - await runTest('Update with validation rules', async () => { - // Try to update completed subtask (should warn or fail based on rules) - const subtaskId = `${parentTaskId}.1`; // This was marked completed earlier + it('should update subtask without affecting parent task', async () => { + const originalParentTitle = 'Parent task'; + + // Update subtask const result = await helpers.taskMaster( 'update-subtask', - ['--id', subtaskId, '--title', 'Trying to update completed task', '--force'], + [subtaskId, 'Completely different subtask'], { cwd: testDir } ); - // Should either succeed with --force or provide clear message - if (result.exitCode !== 0 && !result.stderr.includes('completed')) { - throw new Error('No clear message about updating completed subtask'); - } - }); - - // Test 11: Complex update with multiple fields - await runTest('Complex multi-field update', async () => { - // Create fresh subtask - await helpers.taskMaster( - 'add-subtask', - [parentTaskId, 'Fresh subtask for complex update'], - { cwd: testDir } - ); + expect(result).toHaveExitCode(0); + // Verify parent task remains unchanged const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - const subtaskMatches = showResult.stdout.match(/(\d+\.\d+)/g) || []; - const newSubtaskId = subtaskMatches[subtaskMatches.length - 1]; - - const result = await helpers.taskMaster( - 'update-subtask', - [ - '--id', newSubtaskId, - '--prompt', 'Enhance with testing requirements', - '--priority', 'medium', - '--status', 'in_progress', - '--metadata', 'test_coverage=required' - ], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify all updates applied - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - const subtask = parentTask.subtasks.find(s => s.id === newSubtaskId); - - if (subtask.priority !== 'medium' || subtask.status !== 'in_progress') { - throw new Error('Not all fields updated'); - } - if (!subtask.metadata || subtask.metadata.test_coverage !== 'required') { - throw new Error('Metadata not updated in complex update'); - } + expect(showResult.stdout).toContain(originalParentTitle); }); - // Test 12: Update subtask in context of parent task - await runTest('Context-aware subtask update', async () => { - // Create new parent task with specific context - const contextParent = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Build REST API with Node.js'], - { cwd: testDir } - ); - const contextParentId = helpers.extractTaskId(contextParent.stdout); + it('should handle subtask updates with special characters', async () => { + const specialTitle = 'Subtask with special chars: @#$% & "quotes" \'apostrophes\''; - // Add subtask - await helpers.taskMaster( - 'add-subtask', - [contextParentId, 'Create endpoints'], - { cwd: testDir } - ); - - const subtaskId = `${contextParentId}.1`; const result = await helpers.taskMaster( 'update-subtask', - ['--id', subtaskId, '--prompt', 'Focus on CRUD operations', '--use-parent-context'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should maintain REST API context - const showResult = await helpers.taskMaster('show', [contextParentId], { cwd: testDir }); - const hasApiContext = showResult.stdout.toLowerCase().includes('api') || - showResult.stdout.toLowerCase().includes('endpoint') || - showResult.stdout.toLowerCase().includes('rest'); - - if (!hasApiContext) { - throw new Error('Parent context not preserved in subtask update'); - } - }); - - // Test 13: Reorder subtasks during update - await runTest('Reorder subtasks', async () => { - const subtaskId = `${parentTaskId}.3`; - const result = await helpers.taskMaster( - 'update-subtask', - ['--id', subtaskId, '--position', '1'], + [subtaskId, specialTitle], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify reordering + expect(result).toHaveExitCode(0); + + // Verify special characters were preserved const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); - // The subtask that was at position 3 should now appear first - // This is implementation dependent, so we just check it succeeded + expect(showResult.stdout).toContain('@#$%'); }); + }); - // Test 14: Update with tag assignment - await runTest('Update subtask with tags', async () => { - // Create tag first - await helpers.taskMaster('add-tag', ['backend-subtasks'], { cwd: testDir }); - - const subtaskId = `${parentTaskId}.1`; + describe('Dry run mode', () => { + it('should preview updates without applying them', async () => { const result = await helpers.taskMaster( 'update-subtask', - ['--id', subtaskId, '--tag', 'backend-subtasks'], + [subtaskId, 'Dry run test', '--dry-run'], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify tag was assigned - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const parentTask = tasksJson.tasks.find(t => t.id === parentTaskId); - const subtask = parentTask.subtasks.find(s => s.id === subtaskId); + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('DRY RUN'); + expect(result.stdout).toContain('Would update'); - if (!subtask.tags || !subtask.tags.includes('backend-subtasks')) { - throw new Error('Tag not assigned to subtask'); - } + // Verify subtask was NOT actually updated + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout).not.toContain('Dry run test'); + expect(showResult.stdout).toContain('Initial subtask'); }); + }); - // Test 15: Performance - update many subtasks - await runTest('Performance - bulk subtask updates', async () => { - // Create parent with many subtasks - const perfParent = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Large project with many components'], + describe('Integration with other commands', () => { + it('should reflect updates in parent task expansion', async () => { + // Update subtask with AI + await helpers.taskMaster( + 'update-subtask', + [subtaskId, '--prompt', 'Add detailed implementation steps'], + { cwd: testDir, timeout: 45000 } + ); + + // Expand parent task + const expandResult = await helpers.taskMaster( + 'expand', + ['--id', parentTaskId], + { cwd: testDir, timeout: 45000 } + ); + + expect(expandResult).toHaveExitCode(0); + expect(expandResult.stdout).toContain('Expanded task'); + + // Should include updated subtask information + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + const hasImplementationSteps = showResult.stdout.toLowerCase().includes('implementation') || + showResult.stdout.toLowerCase().includes('step'); + expect(hasImplementationSteps).toBe(true); + }, 90000); + + it('should update subtask after parent task status change', async () => { + // Change parent task status + await helpers.taskMaster( + 'set-status', + [parentTaskId, 'in_progress'], { cwd: testDir } ); - const perfParentId = helpers.extractTaskId(perfParent.stdout); - // Add 20 subtasks - const promises = []; - for (let i = 1; i <= 20; i++) { - promises.push( - helpers.taskMaster( - 'add-subtask', - [perfParentId, `Component ${i}`], - { cwd: testDir } - ) - ); - } - await Promise.all(promises); - - // Update all to in_progress - const subtaskIds = []; - for (let i = 1; i <= 20; i++) { - subtaskIds.push(`${perfParentId}.${i}`); - } - - const startTime = Date.now(); + // Update subtask const result = await helpers.taskMaster( 'update-subtask', - ['--ids', subtaskIds.join(','), '--status', 'in_progress'], + [subtaskId, '--status', 'in_progress'], { cwd: testDir } ); - const duration = Date.now() - startTime; - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + expect(result).toHaveExitCode(0); - logger.info(`Updated 20 subtasks in ${duration}ms`); - if (duration > 5000) { - throw new Error(`Bulk update too slow: ${duration}ms`); - } + // Verify both statuses + const showResult = await helpers.taskMaster('show', [parentTaskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('in_progress'); }); - - // Calculate summary - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'passed').length; - const failedTests = results.tests.filter(t => t.status === 'failed').length; - - logger.info('\n=== Update-Subtask Test Summary ==='); - logger.info(`Total tests: ${totalTests}`); - logger.info(`Passed: ${passedTests}`); - logger.info(`Failed: ${failedTests}`); - - if (failedTests > 0) { - results.status = 'failed'; - logger.error(`\n${failedTests} tests failed`); - } else { - logger.success('\n✅ All update-subtask tests passed!'); - } - - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'update-subtask test suite', - error: error.message, - stack: error.stack - }); - logger.error(`Update-subtask test suite failed: ${error.message}`); - } - - return results; -} \ No newline at end of file + }); +}); \ No newline at end of file diff --git a/tests/e2e/tests/commands/update-task.test.js b/tests/e2e/tests/commands/update-task.test.js index a35abbec..7f9309f6 100644 --- a/tests/e2e/tests/commands/update-task.test.js +++ b/tests/e2e/tests/commands/update-task.test.js @@ -1,484 +1,554 @@ /** - * Comprehensive E2E tests for update-task command - * Tests all aspects of task updates including AI-powered and manual updates + * Comprehensive E2E tests for update-task command (single task update) + * Tests all aspects of single task updates including AI-powered updates */ -export default async function testUpdateTask(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [], - tests: [] - }; +const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); +const { tmpdir } = require('os'); - async function runTest(name, testFn) { - try { - logger.info(`\nRunning: ${name}`); - await testFn(); - results.tests.push({ name, status: 'passed' }); - logger.success(`✓ ${name}`); - } catch (error) { - results.tests.push({ name, status: 'failed', error: error.message }); - results.errors.push({ test: name, error: error.message }); - logger.error(`✗ ${name}: ${error.message}`); +describe('update-task command', () => { + let testDir; + let helpers; + let taskId; + + beforeEach(async () => { + // Create test directory + testDir = mkdtempSync(join(tmpdir(), 'task-master-update-task-')); + + // Initialize test helpers + const context = global.createTestContext('update-task'); + helpers = context.helpers; + + // Copy .env file if it exists + const mainEnvPath = join(__dirname, '../../../../.env'); + const testEnvPath = join(testDir, '.env'); + if (existsSync(mainEnvPath)) { + const envContent = readFileSync(mainEnvPath, 'utf8'); + writeFileSync(testEnvPath, envContent); } - } - - try { - logger.info('Starting comprehensive update-task tests...'); - - // Setup: Create various tasks for testing - logger.info('Setting up test tasks...'); - // Create simple task - const simpleResult = await helpers.taskMaster( + // Initialize task-master project + const initResult = await helpers.taskMaster('init', ['-y'], { cwd: testDir }); + expect(initResult).toHaveExitCode(0); + + // Create a test task for updates + const addResult = await helpers.taskMaster( 'add-task', - ['--title', 'Simple task', '--description', 'Initial description'], + ['--title', 'Initial task', '--description', 'Task to be updated'], { cwd: testDir } ); - const simpleTaskId = helpers.extractTaskId(simpleResult.stdout); - - // Create AI task - const aiResult = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Create a logging system for the application'], - { cwd: testDir } - ); - const aiTaskId = helpers.extractTaskId(aiResult.stdout); - - // Create task with metadata - const metaResult = await helpers.taskMaster( - 'add-task', - ['--title', 'Task with metadata', '--metadata', 'version=1.0'], - { cwd: testDir } - ); - const metaTaskId = helpers.extractTaskId(metaResult.stdout); + taskId = helpers.extractTaskId(addResult.stdout); + }); - // Test 1: Basic manual update - description only - await runTest('Basic description update', async () => { + afterEach(() => { + // Clean up test directory + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); + } + }); + + describe('Basic task updates', () => { + it('should update task description', async () => { const result = await helpers.taskMaster( 'update-task', - [simpleTaskId, '--description', 'Updated description with more details'], + [taskId, '--description', 'Updated task description with more details'], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated task'); // Verify update - const showResult = await helpers.taskMaster('show', [simpleTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('Updated description with more details')) { - throw new Error('Description not updated'); - } + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Updated task description'); }); - // Test 2: AI-powered task update - await runTest('AI-powered task update', async () => { + it('should update task title', async () => { const result = await helpers.taskMaster( 'update-task', - [aiTaskId, '--prompt', 'Add requirements for structured logging with log levels and rotation'], - { cwd: testDir, timeout: 120000 } + [taskId, '--title', 'Completely new title'], + { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify AI enhancements - const showResult = await helpers.taskMaster('show', [aiTaskId], { cwd: testDir }); - const output = showResult.stdout.toLowerCase(); + expect(result).toHaveExitCode(0); - // Should mention logging concepts - const hasLoggingConcepts = output.includes('log level') || - output.includes('rotation') || - output.includes('structured') || - output.includes('logging'); - if (!hasLoggingConcepts) { - throw new Error('AI did not enhance task with logging requirements'); - } + // Verify update + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Completely new title'); }); - // Test 3: Update multiple fields simultaneously - await runTest('Multi-field update', async () => { + it('should update task priority', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--priority', 'high'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify update + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('high'); + }); + + it('should update task details', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--details', 'Implementation notes: Use async/await pattern'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify update + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('async/await'); + }); + }); + + describe('AI-powered updates', () => { + it('should update task using AI prompt', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--prompt', 'Add security considerations and best practices'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated task'); + + // Verify AI added security content + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + const hasSecurityInfo = showResult.stdout.toLowerCase().includes('security') || + showResult.stdout.toLowerCase().includes('practice'); + expect(hasSecurityInfo).toBe(true); + }, 60000); + + it('should enhance task with AI suggestions', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--prompt', 'Break this down into subtasks and add implementation details'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + // Check that task was enhanced + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const updatedTask = tasks.master.tasks.find(t => t.id === parseInt(taskId)); + + // Should have more detailed content + expect(updatedTask.details.length).toBeGreaterThan(50); + }, 60000); + + it('should update task with research mode', async () => { const result = await helpers.taskMaster( 'update-task', [ - simpleTaskId, - '--title', 'Renamed task', - '--description', 'New comprehensive description', + taskId, + '--prompt', 'Add current industry best practices for authentication', + '--research' + ], + { cwd: testDir, timeout: 90000 } + ); + + expect(result).toHaveExitCode(0); + + // Research mode should add comprehensive content + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.length).toBeGreaterThan(500); + }, 120000); + }); + + describe('Multiple field updates', () => { + it('should update multiple fields at once', async () => { + const result = await helpers.taskMaster( + 'update-task', + [ + taskId, + '--title', 'New comprehensive title', + '--description', 'New detailed description', '--priority', 'high', - '--status', 'in_progress' + '--details', 'Additional implementation notes' ], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + + expect(result).toHaveExitCode(0); // Verify all updates - const showResult = await helpers.taskMaster('show', [simpleTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('Renamed task')) { - throw new Error('Title not updated'); - } - if (!showResult.stdout.includes('New comprehensive description')) { - throw new Error('Description not updated'); - } - if (!showResult.stdout.includes('high') && !showResult.stdout.includes('High')) { - throw new Error('Priority not updated'); - } - if (!showResult.stdout.includes('in_progress') && !showResult.stdout.includes('In Progress')) { - throw new Error('Status not updated'); - } + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('New comprehensive title'); + expect(showResult.stdout).toContain('New detailed description'); + expect(showResult.stdout.toLowerCase()).toContain('high'); + expect(showResult.stdout).toContain('Additional implementation notes'); }); - // Test 4: Update task metadata - await runTest('Update task metadata', async () => { + it('should combine manual updates with AI prompt', async () => { const result = await helpers.taskMaster( 'update-task', [ - metaTaskId, - '--metadata', 'version=2.0', - '--metadata', 'author=test-user', - '--metadata', 'reviewed=true' + taskId, + '--priority', 'high', + '--prompt', 'Add technical requirements and dependencies' ], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + // Verify both manual and AI updates + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('high'); + const hasTechnicalInfo = showResult.stdout.toLowerCase().includes('requirement') || + showResult.stdout.toLowerCase().includes('dependenc'); + expect(hasTechnicalInfo).toBe(true); + }, 60000); + }); + + describe('Task metadata updates', () => { + it('should add tags to task', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--add-tags', 'backend,api,urgent'], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify metadata - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const task = tasksJson.tasks.find(t => t.id === metaTaskId); + expect(result).toHaveExitCode(0); - if (!task.metadata || task.metadata.version !== '2.0' || - task.metadata.author !== 'test-user' || task.metadata.reviewed !== 'true') { - throw new Error('Metadata not properly updated'); - } + // Verify tags were added + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('backend'); + expect(showResult.stdout).toContain('api'); + expect(showResult.stdout).toContain('urgent'); }); - // Test 5: Error handling - non-existent task - await runTest('Error handling - non-existent task', async () => { + it('should remove tags from task', async () => { + // First add tags + await helpers.taskMaster( + 'update-task', + [taskId, '--add-tags', 'frontend,ui,design'], + { cwd: testDir } + ); + + // Then remove some + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--remove-tags', 'ui,design'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify tags were removed + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('frontend'); + expect(showResult.stdout).not.toContain('ui'); + expect(showResult.stdout).not.toContain('design'); + }); + + it('should update due date', async () => { + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 7); + const dateStr = futureDate.toISOString().split('T')[0]; + + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--due-date', dateStr], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify due date was set + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain(dateStr); + }); + + it('should update estimated time', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--estimated-time', '4h'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify estimated time was set + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('4h'); + }); + }); + + describe('Status updates', () => { + it('should update task status', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--status', 'in_progress'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify status change + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('in_progress'); + }); + + it('should mark task as completed', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--status', 'completed'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify completion + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('completed'); + }); + + it('should mark task as blocked with reason', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--status', 'blocked', '--blocked-reason', 'Waiting for API access'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify blocked status and reason + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout.toLowerCase()).toContain('blocked'); + expect(showResult.stdout).toContain('Waiting for API access'); + }); + }); + + describe('Append mode', () => { + it('should append to description', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--append-description', '\nAdditional requirements added.'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify description was appended + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Task to be updated'); + expect(showResult.stdout).toContain('Additional requirements added'); + }); + + it('should append to details', async () => { + // First set some details + await helpers.taskMaster( + 'update-task', + [taskId, '--details', 'Initial implementation notes.'], + { cwd: testDir } + ); + + // Then append + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--append-details', '\nPerformance considerations added.'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify details were appended + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain('Initial implementation notes'); + expect(showResult.stdout).toContain('Performance considerations added'); + }); + }); + + describe('Tag-specific updates', () => { + it('should update task in specific tag', async () => { + // Create a tag and move task to it + await helpers.taskMaster('add-tag', ['feature-x'], { cwd: testDir }); + await helpers.taskMaster( + 'add-task', + ['--prompt', 'Task in feature-x', '--tag', 'feature-x'], + { cwd: testDir } + ); + + // Get task ID from feature-x tag + const listResult = await helpers.taskMaster('list', ['--tag', 'feature-x'], { cwd: testDir }); + const featureTaskId = helpers.extractTaskId(listResult.stdout); + + // Update task in specific tag + const result = await helpers.taskMaster( + 'update-task', + [featureTaskId, '--description', 'Updated in feature tag', '--tag', 'feature-x'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify update in correct tag + const showResult = await helpers.taskMaster( + 'show', + [featureTaskId, '--tag', 'feature-x'], + { cwd: testDir } + ); + expect(showResult.stdout).toContain('Updated in feature tag'); + }); + }); + + describe('Output formats', () => { + it('should output in JSON format', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--description', 'JSON test update', '--output', 'json'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Output should be valid JSON + const jsonOutput = JSON.parse(result.stdout); + expect(jsonOutput.success).toBe(true); + expect(jsonOutput.task).toBeDefined(); + expect(jsonOutput.task.description).toBe('JSON test update'); + }); + }); + + describe('Error handling', () => { + it('should fail with non-existent task ID', async () => { const result = await helpers.taskMaster( 'update-task', ['99999', '--description', 'This should fail'], { cwd: testDir, allowFailure: true } ); - if (result.exitCode === 0) { - throw new Error('Should have failed with non-existent task'); - } - if (!result.stderr.includes('not found') && !result.stderr.includes('exist')) { - throw new Error('Error message not clear about missing task'); - } + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('not found'); }); - // Test 6: Update with validation of AI output - await runTest('AI update with validation', async () => { - // Create task with specific context - const validationResult = await helpers.taskMaster( - 'add-task', - ['--prompt', 'Setup CI/CD pipeline'], - { cwd: testDir } - ); - const validationTaskId = helpers.extractTaskId(validationResult.stdout); - - // Update with specific requirements + it('should fail with invalid priority', async () => { const result = await helpers.taskMaster( 'update-task', - [validationTaskId, '--prompt', 'Add automated testing and deployment stages', '--validate-ai'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Check AI added relevant content - const showResult = await helpers.taskMaster('show', [validationTaskId], { cwd: testDir }); - const output = showResult.stdout.toLowerCase(); - - if (!output.includes('test') || !output.includes('deploy')) { - throw new Error('AI validation failed - missing required concepts'); - } - }); - - // Test 7: Update task with tag changes - await runTest('Update task tags', async () => { - // Create tags - await helpers.taskMaster('add-tag', ['frontend'], { cwd: testDir }); - await helpers.taskMaster('add-tag', ['urgent'], { cwd: testDir }); - - const result = await helpers.taskMaster( - 'update-task', - [simpleTaskId, '--add-tag', 'frontend', '--add-tag', 'urgent'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify tags in appropriate contexts - const frontendList = await helpers.taskMaster('list', ['--tag', 'frontend'], { cwd: testDir }); - const urgentList = await helpers.taskMaster('list', ['--tag', 'urgent'], { cwd: testDir }); - - if (!frontendList.stdout.includes(simpleTaskId)) { - throw new Error('Task not found in frontend tag'); - } - if (!urgentList.stdout.includes(simpleTaskId)) { - throw new Error('Task not found in urgent tag'); - } - }); - - // Test 8: Remove tags from task - await runTest('Remove task tags', async () => { - const result = await helpers.taskMaster( - 'update-task', - [simpleTaskId, '--remove-tag', 'urgent'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify tag removed - const urgentList = await helpers.taskMaster('list', ['--tag', 'urgent'], { cwd: testDir }); - if (urgentList.stdout.includes(simpleTaskId)) { - throw new Error('Task still in removed tag'); - } - }); - - // Test 9: Update with dependencies - await runTest('Update task dependencies', async () => { - // Create dependency task - const depResult = await helpers.taskMaster( - 'add-task', - ['--title', 'Dependency task'], - { cwd: testDir } - ); - const depTaskId = helpers.extractTaskId(depResult.stdout); - - const result = await helpers.taskMaster( - 'update-task', - [aiTaskId, '--add-dependency', depTaskId], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify dependency added - const showResult = await helpers.taskMaster('show', [aiTaskId], { cwd: testDir }); - if (!showResult.stdout.includes(depTaskId)) { - throw new Error('Dependency not added to task'); - } - }); - - // Test 10: Complex AI enhancement - await runTest('Complex AI task enhancement', async () => { - // Create task needing enhancement - const enhanceResult = await helpers.taskMaster( - 'add-task', - ['--title', 'Basic API endpoint', '--description', 'Create user endpoint'], - { cwd: testDir } - ); - const enhanceTaskId = helpers.extractTaskId(enhanceResult.stdout); - - const result = await helpers.taskMaster( - 'update-task', - [ - enhanceTaskId, - '--prompt', 'Enhance with REST best practices, error handling, validation, and OpenAPI documentation', - '--keep-original' - ], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should preserve original and add enhancements - const showResult = await helpers.taskMaster('show', [enhanceTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('user endpoint')) { - throw new Error('Original content lost during enhancement'); - } - - // Check for enhancements - const output = showResult.stdout.toLowerCase(); - const enhancements = ['validation', 'error', 'rest', 'openapi', 'documentation']; - const foundEnhancements = enhancements.filter(e => output.includes(e)).length; - - if (foundEnhancements < 3) { - throw new Error('AI did not add sufficient enhancements'); - } - }); - - // Test 11: Bulk property update - await runTest('Update common properties across tasks', async () => { - // Update all tasks to have a common property - const taskIds = [simpleTaskId, aiTaskId, metaTaskId]; - - // This tests if update-task can handle multiple IDs (implementation dependent) - // If not supported, test single updates in sequence - for (const taskId of taskIds) { - const result = await helpers.taskMaster( - 'update-task', - [taskId, '--metadata', 'project=test-suite'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Failed to update task ${taskId}: ${result.stderr}`); - } - } - - // Verify all have the metadata - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - taskIds.forEach(taskId => { - const task = tasksJson.tasks.find(t => t.id === taskId); - if (!task.metadata || task.metadata.project !== 'test-suite') { - throw new Error(`Task ${taskId} missing project metadata`); - } - }); - }); - - // Test 12: Update completed task - await runTest('Update completed task handling', async () => { - // Complete a task first - await helpers.taskMaster('set-status', [simpleTaskId, 'completed'], { cwd: testDir }); - - // Try to update it - const result = await helpers.taskMaster( - 'update-task', - [simpleTaskId, '--description', 'Trying to update completed task'], + [taskId, '--priority', 'invalid-priority'], { cwd: testDir, allowFailure: true } ); - // Should either fail with clear message or succeed with warning - if (result.exitCode !== 0) { - if (!result.stderr.includes('completed')) { - throw new Error('No clear message about updating completed task'); - } - } else if (!result.stdout.includes('warning') && !result.stdout.includes('completed')) { - throw new Error('No warning about updating completed task'); - } + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid priority'); }); - // Test 13: Update with context preservation - await runTest('Context-aware AI update', async () => { - // Create task with rich context - const contextResult = await helpers.taskMaster( + it('should fail with invalid status', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--status', 'invalid-status'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid status'); + }); + + it('should fail without any update parameters', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('No updates specified'); + }); + }); + + describe('Performance and edge cases', () => { + it('should handle very long descriptions', async () => { + const longDescription = 'This is a very detailed description. '.repeat(50); + + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--description', longDescription], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + + // Verify long description was saved + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const updatedTask = tasks.master.tasks.find(t => t.id === parseInt(taskId)); + expect(updatedTask.description).toBe(longDescription); + }); + + it('should preserve task relationships during updates', async () => { + // Add a dependency + const depResult = await helpers.taskMaster( 'add-task', - ['--prompt', 'Implement user profile page with React'], + ['--title', 'Dependency task', '--description', 'Must be done first'], { cwd: testDir } ); - const contextTaskId = helpers.extractTaskId(contextResult.stdout); + const depId = helpers.extractTaskId(depResult.stdout); - // Expand to add subtasks - await helpers.taskMaster('expand', [contextTaskId], { cwd: testDir, timeout: 120000 }); - - // Update with context preservation - const result = await helpers.taskMaster( - 'update-task', - [contextTaskId, '--prompt', 'Add accessibility features', '--preserve-context'], - { cwd: testDir, timeout: 120000 } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should maintain React context and add accessibility - const showResult = await helpers.taskMaster('show', [contextTaskId], { cwd: testDir }); - const output = showResult.stdout.toLowerCase(); - - if (!output.includes('react')) { - throw new Error('Lost React context during update'); - } - if (!output.includes('accessibility') && !output.includes('a11y') && !output.includes('aria')) { - throw new Error('Accessibility features not added'); - } - }); - - // Test 14: Update with estimation - await runTest('Update task with time estimation', async () => { - const result = await helpers.taskMaster( - 'update-task', - [ - aiTaskId, - '--estimate', '8h', - '--metadata', 'story_points=5' - ], + await helpers.taskMaster( + 'add-dependency', + ['--id', taskId, '--depends-on', depId], { cwd: testDir } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify estimation added - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const task = tasksJson.tasks.find(t => t.id === aiTaskId); - - if (!task.estimate || !task.estimate.includes('8h')) { - throw new Error('Time estimate not added'); - } - if (!task.metadata || task.metadata.story_points !== '5') { - throw new Error('Story points not added'); - } - }); - - // Test 15: Performance - large description update - await runTest('Performance - large content update', async () => { - // Create large description - const largeDescription = 'This is a detailed task description. '.repeat(100) + - '\n\n## Requirements\n' + - '- Requirement item\n'.repeat(50); - - const startTime = Date.now(); + // Update the task const result = await helpers.taskMaster( 'update-task', - [metaTaskId, '--description', largeDescription], + [taskId, '--description', 'Updated with dependencies intact'], { cwd: testDir } ); - const duration = Date.now() - startTime; - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + expect(result).toHaveExitCode(0); - logger.info(`Large update completed in ${duration}ms`); - if (duration > 5000) { - throw new Error(`Update too slow: ${duration}ms`); - } - - // Verify content was saved - const showResult = await helpers.taskMaster('show', [metaTaskId], { cwd: testDir }); - if (!showResult.stdout.includes('detailed task description')) { - throw new Error('Large description not saved properly'); - } + // Verify dependency is preserved + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).toContain(depId); }); + }); - // Calculate summary - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'passed').length; - const failedTests = results.tests.filter(t => t.status === 'failed').length; - - logger.info('\n=== Update-Task Test Summary ==='); - logger.info(`Total tests: ${totalTests}`); - logger.info(`Passed: ${passedTests}`); - logger.info(`Failed: ${failedTests}`); - - if (failedTests > 0) { - results.status = 'failed'; - logger.error(`\n${failedTests} tests failed`); - } else { - logger.success('\n✅ All update-task tests passed!'); - } - - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'update-task test suite', - error: error.message, - stack: error.stack + describe('Dry run mode', () => { + it('should preview updates without applying them', async () => { + const result = await helpers.taskMaster( + 'update-task', + [taskId, '--description', 'Dry run test', '--dry-run'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('DRY RUN'); + expect(result.stdout).toContain('Would update'); + + // Verify task was NOT actually updated + const showResult = await helpers.taskMaster('show', [taskId], { cwd: testDir }); + expect(showResult.stdout).not.toContain('Dry run test'); }); - logger.error(`Update-task test suite failed: ${error.message}`); - } + }); - return results; -} \ No newline at end of file + describe('Integration with other commands', () => { + it('should work with expand after update', async () => { + // Update task with AI + await helpers.taskMaster( + 'update-task', + [taskId, '--prompt', 'Add implementation steps'], + { cwd: testDir, timeout: 45000 } + ); + + // Then expand it + const expandResult = await helpers.taskMaster( + 'expand', + ['--id', taskId], + { cwd: testDir, timeout: 45000 } + ); + + expect(expandResult).toHaveExitCode(0); + expect(expandResult.stdout).toContain('Expanded task'); + }, 90000); + }); +}); \ No newline at end of file diff --git a/tests/e2e/tests/commands/update-tasks.test.js b/tests/e2e/tests/commands/update-tasks.test.js index d24c534c..a992a142 100644 --- a/tests/e2e/tests/commands/update-tasks.test.js +++ b/tests/e2e/tests/commands/update-tasks.test.js @@ -1,483 +1,449 @@ /** - * Comprehensive E2E tests for update-tasks command - * Tests bulk task update functionality with various filters and AI capabilities + * Comprehensive E2E tests for update-tasks command (bulk update) + * Tests all aspects of bulk task updates including AI-powered updates */ -export default async function testUpdateTasks(logger, helpers, context) { - const { testDir } = context; - const results = { - status: 'passed', - errors: [], - tests: [] - }; +const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs'); +const { join } = require('path'); +const { tmpdir } = require('os'); - async function runTest(name, testFn) { - try { - logger.info(`\nRunning: ${name}`); - await testFn(); - results.tests.push({ name, status: 'passed' }); - logger.success(`✓ ${name}`); - } catch (error) { - results.tests.push({ name, status: 'failed', error: error.message }); - results.errors.push({ test: name, error: error.message }); - logger.error(`✗ ${name}: ${error.message}`); - } - } +describe('update-tasks command', () => { + let testDir; + let helpers; - try { - logger.info('Starting comprehensive update-tasks tests...'); - - // Setup: Create a variety of tasks for bulk operations - logger.info('Setting up test tasks for bulk operations...'); + beforeEach(async () => { + // Create test directory + testDir = mkdtempSync(join(tmpdir(), 'task-master-update-tasks-')); - // Create tasks with different statuses - const taskIds = []; + // Initialize test helpers + const context = global.createTestContext('update-tasks'); + helpers = context.helpers; - // Pending tasks - for (let i = 1; i <= 3; i++) { - const result = await helpers.taskMaster( - 'add-task', - ['--title', `Pending task ${i}`, '--priority', i === 1 ? 'high' : 'medium'], - { cwd: testDir } - ); - taskIds.push(helpers.extractTaskId(result.stdout)); + // Copy .env file if it exists + const mainEnvPath = join(__dirname, '../../../../.env'); + const testEnvPath = join(testDir, '.env'); + if (existsSync(mainEnvPath)) { + const envContent = readFileSync(mainEnvPath, 'utf8'); + writeFileSync(testEnvPath, envContent); } - // In-progress tasks - for (let i = 1; i <= 2; i++) { - const result = await helpers.taskMaster( - 'add-task', - ['--title', `In-progress task ${i}`], - { cwd: testDir } - ); - const taskId = helpers.extractTaskId(result.stdout); - taskIds.push(taskId); - await helpers.taskMaster('set-status', [taskId, 'in_progress'], { cwd: testDir }); - } + // Initialize task-master project + const initResult = await helpers.taskMaster('init', ['-y'], { cwd: testDir }); + expect(initResult).toHaveExitCode(0); - // Tasks with tags - await helpers.taskMaster('add-tag', ['backend'], { cwd: testDir }); - await helpers.taskMaster('add-tag', ['frontend'], { cwd: testDir }); - - for (let i = 1; i <= 2; i++) { - const result = await helpers.taskMaster( - 'add-task', - ['--title', `Backend task ${i}`, '--tag', 'backend'], - { cwd: testDir } - ); - taskIds.push(helpers.extractTaskId(result.stdout)); + // Create some test tasks for bulk updates + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasksData = { + master: { + tasks: [ + { + id: 1, + title: "Setup authentication", + description: "Implement user authentication", + priority: "medium", + status: "pending", + details: "Basic auth implementation" + }, + { + id: 2, + title: "Create database schema", + description: "Design database structure", + priority: "high", + status: "pending", + details: "PostgreSQL schema" + }, + { + id: 3, + title: "Build API endpoints", + description: "RESTful API development", + priority: "medium", + status: "in_progress", + details: "Express.js endpoints" + } + ] + } + }; + mkdirSync(join(testDir, '.taskmaster/tasks'), { recursive: true }); + writeFileSync(tasksPath, JSON.stringify(tasksData, null, 2)); + }); + + afterEach(() => { + // Clean up test directory + if (testDir && existsSync(testDir)) { + rmSync(testDir, { recursive: true, force: true }); } + }); - // Test 1: Bulk update by status - await runTest('Bulk update tasks by status', async () => { + describe('Bulk task updates with prompts', () => { + it('should update all tasks with general prompt', async () => { const result = await helpers.taskMaster( 'update-tasks', - ['--status', 'pending', '--set-priority', 'high'], - { cwd: testDir } + ['--prompt', 'Add security considerations to all tasks'], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Should report number of tasks updated - if (!result.stdout.includes('updated') || !result.stdout.match(/\d+/)) { - throw new Error('No update count reported'); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated'); + expect(result.stdout).toContain('task'); - // Verify all pending tasks now have high priority - const listResult = await helpers.taskMaster('list', ['--status', 'pending'], { cwd: testDir }); - const pendingTasks = listResult.stdout.match(/\d+\s*\|/g) || []; + // Verify tasks were updated + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); - // Check a sample task - if (pendingTasks.length > 0) { - const showResult = await helpers.taskMaster('show', [taskIds[0]], { cwd: testDir }); - if (!showResult.stdout.includes('high') && !showResult.stdout.includes('High')) { - throw new Error('Priority not updated for pending tasks'); - } - } - }); + // Check that tasks have been modified (details should mention security) + const hasSecurityUpdates = tasks.master.tasks.some(t => + t.details && t.details.toLowerCase().includes('security') + ); + expect(hasSecurityUpdates).toBe(true); + }, 60000); - // Test 2: Bulk update by tag - await runTest('Bulk update tasks by tag', async () => { + it('should update specific tasks by IDs', async () => { const result = await helpers.taskMaster( 'update-tasks', - ['--tag', 'backend', '--add-metadata', 'team=backend-team'], - { cwd: testDir } + ['--ids', '1,3', '--prompt', 'Add performance optimization notes'], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify backend tasks have metadata - const listResult = await helpers.taskMaster('list', ['--tag', 'backend'], { cwd: testDir }); - const backendTaskIds = (listResult.stdout.match(/\d+(?=\s*\|)/g) || []); - - if (backendTaskIds.length > 0) { - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const backendTask = tasksJson.tasks.find(t => backendTaskIds.includes(t.id)); - - if (!backendTask || !backendTask.metadata || backendTask.metadata.team !== 'backend-team') { - throw new Error('Metadata not added to backend tasks'); - } - } - }); + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated 2 task'); + }, 60000); - // Test 3: Bulk update with AI enhancement - await runTest('Bulk AI enhancement', async () => { + it('should update tasks by status filter', async () => { const result = await helpers.taskMaster( 'update-tasks', - ['--tag', 'backend', '--enhance', '--prompt', 'Add security considerations'], - { cwd: testDir, timeout: 180000 } + ['--status', 'pending', '--prompt', 'Add estimated time requirements'], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Check that tasks were enhanced - const listResult = await helpers.taskMaster('list', ['--tag', 'backend'], { cwd: testDir }); - const backendTaskIds = (listResult.stdout.match(/\d+(?=\s*\|)/g) || []); + expect(result).toHaveExitCode(0); + // Should update tasks 1 and 2 (pending status) + expect(result.stdout).toContain('Updated 2 task'); - if (backendTaskIds.length > 0) { - const showResult = await helpers.taskMaster('show', [backendTaskIds[0]], { cwd: testDir }); - const hasSecurityMention = showResult.stdout.toLowerCase().includes('security') || - showResult.stdout.toLowerCase().includes('secure') || - showResult.stdout.toLowerCase().includes('auth'); - - if (!hasSecurityMention) { - throw new Error('AI enhancement did not add security considerations'); - } - } - }); + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + // Verify only pending tasks were updated + const pendingTasks = tasks.master.tasks.filter(t => t.status === 'pending'); + const hasTimeEstimates = pendingTasks.some(t => + t.details && (t.details.includes('time') || t.details.includes('hour') || t.details.includes('day')) + ); + expect(hasTimeEstimates).toBe(true); + }, 60000); - // Test 4: Bulk status change - await runTest('Bulk status change', async () => { + it('should update tasks by priority filter', async () => { const result = await helpers.taskMaster( 'update-tasks', - ['--priority', 'high', '--set-status', 'in_progress'], - { cwd: testDir } + ['--priority', 'medium', '--prompt', 'Add testing requirements'], + { cwd: testDir, timeout: 45000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Verify high priority tasks are now in progress - const listResult = await helpers.taskMaster('list', ['--priority', 'high'], { cwd: testDir }); - const highPriorityIds = (listResult.stdout.match(/\d+(?=\s*\|)/g) || []); - - if (highPriorityIds.length > 0) { - const showResult = await helpers.taskMaster('show', [highPriorityIds[0]], { cwd: testDir }); - if (!showResult.stdout.includes('in_progress') && !showResult.stdout.includes('In Progress')) { - throw new Error('Status not updated for high priority tasks'); - } - } - }); + expect(result).toHaveExitCode(0); + // Should update tasks 1 and 3 (medium priority) + expect(result.stdout).toContain('Updated 2 task'); + }, 60000); + }); - // Test 5: Bulk update with multiple filters - await runTest('Bulk update with combined filters', async () => { - // Add frontend tag to some tasks - await helpers.taskMaster('update-task', [taskIds[0], '--add-tag', 'frontend'], { cwd: testDir }); - await helpers.taskMaster('update-task', [taskIds[1], '--add-tag', 'frontend'], { cwd: testDir }); - - const result = await helpers.taskMaster( - 'update-tasks', - ['--tag', 'frontend', '--status', 'in_progress', '--add-metadata', 'urgent=true'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should only update tasks matching both filters - const updateCount = result.stdout.match(/(\d+) tasks? updated/); - if (!updateCount) { - throw new Error('Update count not reported'); - } - }); - - // Test 6: Bulk update all tasks - await runTest('Update all tasks', async () => { - const result = await helpers.taskMaster( - 'update-tasks', - ['--all', '--add-metadata', 'batch_update=test'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify all tasks have the metadata - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const tasksWithoutMetadata = tasksJson.tasks.filter( - t => !t.metadata || t.metadata.batch_update !== 'test' - ); - - if (tasksWithoutMetadata.length > 0) { - throw new Error('Not all tasks were updated'); - } - }); - - // Test 7: Bulk update with confirmation - await runTest('Bulk update with safety check', async () => { - // This test checks if dangerous operations require confirmation - // The actual behavior depends on implementation - const result = await helpers.taskMaster( - 'update-tasks', - ['--all', '--set-status', 'completed', '--force'], - { cwd: testDir } - ); - - // Should either succeed with --force or show warning - if (result.exitCode !== 0 && !result.stderr.includes('confirm')) { - throw new Error('No safety check for dangerous bulk operation'); - } - }); - - // Test 8: Bulk update by ID list - await runTest('Bulk update specific task IDs', async () => { - const targetIds = taskIds.slice(0, 3); - const result = await helpers.taskMaster( - 'update-tasks', - ['--ids', targetIds.join(','), '--add-metadata', 'selected=true'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify only specified tasks were updated - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - targetIds.forEach(id => { - const task = tasksJson.tasks.find(t => t.id === id); - if (!task.metadata || task.metadata.selected !== 'true') { - throw new Error(`Task ${id} not updated`); - } - }); - - // Verify other tasks were not updated - const otherTasks = tasksJson.tasks.filter(t => !targetIds.includes(t.id)); - otherTasks.forEach(task => { - if (task.metadata && task.metadata.selected === 'true') { - throw new Error(`Task ${task.id} incorrectly updated`); - } - }); - }); - - // Test 9: Bulk update with complex query - await runTest('Complex query bulk update', async () => { - // Create tasks with specific patterns - for (let i = 1; i <= 3; i++) { - await helpers.taskMaster( - 'add-task', - ['--title', `API endpoint: /users/${i}`, '--tag', 'backend'], - { cwd: testDir } - ); - } - - const result = await helpers.taskMaster( - 'update-tasks', - ['--query', 'title:API endpoint', '--add-metadata', 'type=api'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Verify API tasks were updated - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const apiTasks = tasksJson.tasks.filter(t => t.title.includes('API endpoint')); - - apiTasks.forEach(task => { - if (!task.metadata || task.metadata.type !== 'api') { - throw new Error('API tasks not properly updated'); - } - }); - }); - - // Test 10: Error handling - no matching tasks - await runTest('Error handling - no matches', async () => { - const result = await helpers.taskMaster( - 'update-tasks', - ['--tag', 'non-existent-tag', '--set-priority', 'low'], - { cwd: testDir, allowFailure: true } - ); - - // Should indicate no tasks matched - if (!result.stdout.includes('0 tasks') && !result.stdout.includes('No tasks')) { - throw new Error('No clear message about zero matches'); - } - }); - - // Test 11: Bulk update with dry run - await runTest('Dry run mode', async () => { - const result = await helpers.taskMaster( - 'update-tasks', - ['--status', 'pending', '--set-priority', 'low', '--dry-run'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should show what would be updated - if (!result.stdout.includes('would') || !result.stdout.includes('dry')) { - throw new Error('Dry run output not clear'); - } - - // Verify no actual changes - const showResult = await helpers.taskMaster('show', [taskIds[0]], { cwd: testDir }); - if (showResult.stdout.includes('low') || showResult.stdout.includes('Low')) { - throw new Error('Dry run actually modified tasks'); - } - }); - - // Test 12: Bulk update with progress reporting - await runTest('Progress reporting for large updates', async () => { - // Create many tasks - const manyTaskIds = []; - for (let i = 1; i <= 20; i++) { - const result = await helpers.taskMaster( - 'add-task', - ['--title', `Bulk task ${i}`], - { cwd: testDir } - ); - manyTaskIds.push(helpers.extractTaskId(result.stdout)); - } - - const result = await helpers.taskMaster( - 'update-tasks', - ['--ids', manyTaskIds.join(','), '--set-priority', 'medium', '--verbose'], - { cwd: testDir } - ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - - // Should show progress or summary - const hasProgress = result.stdout.includes('updated') && - result.stdout.includes('20'); - if (!hasProgress) { - throw new Error('No progress information for bulk update'); - } - }); - - // Test 13: Bulk update with rollback on error - await runTest('Rollback on error', async () => { - // Try to update with invalid data that should fail partway through - const result = await helpers.taskMaster( - 'update-tasks', - ['--all', '--add-dependency', '99999'], - { cwd: testDir, allowFailure: true } - ); - - // Should fail and indicate rollback or atomic operation - if (result.exitCode === 0) { - throw new Error('Should have failed with invalid dependency'); - } - - // Verify no partial updates occurred - const tasksJson = helpers.readJson(`${testDir}/.taskmaster/tasks/tasks.json`); - const tasksWithBadDep = tasksJson.tasks.filter( - t => t.dependencies && t.dependencies.includes('99999') - ); - - if (tasksWithBadDep.length > 0) { - throw new Error('Partial update occurred - no rollback'); - } - }); - - // Test 14: Bulk update with template - await runTest('Bulk update with template', async () => { + describe('Research mode updates', () => { + it('should update tasks with research-backed information', async () => { const result = await helpers.taskMaster( 'update-tasks', [ - '--tag', 'backend', - '--apply-template', 'standard-backend-task', - '--template-fields', 'add testing requirements, add documentation needs' + '--ids', '1', + '--prompt', 'Add OAuth2 best practices', + '--research' ], - { cwd: testDir, timeout: 120000 } + { cwd: testDir, timeout: 90000 } ); - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } - // Check tasks were updated with template - const listResult = await helpers.taskMaster('list', ['--tag', 'backend'], { cwd: testDir }); - const backendTaskIds = (listResult.stdout.match(/\d+(?=\s*\|)/g) || []); + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated'); - if (backendTaskIds.length > 0) { - const showResult = await helpers.taskMaster('show', [backendTaskIds[0]], { cwd: testDir }); - const hasTemplateContent = showResult.stdout.toLowerCase().includes('test') || - showResult.stdout.toLowerCase().includes('documentation'); - - if (!hasTemplateContent) { - throw new Error('Template not applied to tasks'); + // Research mode should produce more detailed updates + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const authTask = tasks.master.tasks.find(t => t.id === 1); + + // Check for detailed OAuth2 information + expect(authTask.details.length).toBeGreaterThan(100); + const hasOAuthInfo = authTask.details.toLowerCase().includes('oauth') || + authTask.details.toLowerCase().includes('authorization'); + expect(hasOAuthInfo).toBe(true); + }, 120000); + }); + + describe('Multiple filter combinations', () => { + it('should update tasks matching all filters', async () => { + // Add more tasks with different combinations + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const currentTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + currentTasks.master.tasks.push( + { + id: 4, + title: "Security audit", + description: "Perform security review", + priority: "high", + status: "pending", + details: "Initial security check" + }, + { + id: 5, + title: "Performance testing", + description: "Load testing", + priority: "high", + status: "in_progress", + details: "Using JMeter" } - } + ); + writeFileSync(tasksPath, JSON.stringify(currentTasks, null, 2)); + + // Update only high priority pending tasks + const result = await helpers.taskMaster( + 'update-tasks', + [ + '--status', 'pending', + '--priority', 'high', + '--prompt', 'Add compliance requirements' + ], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + // Should only update task 2 and 4 + expect(result.stdout).toContain('Updated 2 task'); + }, 60000); + + it('should handle empty filter results gracefully', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + [ + '--status', 'completed', + '--prompt', 'This should not update anything' + ], + { cwd: testDir, timeout: 30000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('No tasks found matching the criteria'); + }, 45000); + }); + + describe('Tag support', () => { + it('should update tasks in specific tag', async () => { + // Create a new tag with tasks + await helpers.taskMaster('add-tag', ['feature-x'], { cwd: testDir }); + + // Add task to the tag + await helpers.taskMaster( + 'add-task', + ['--prompt', 'Feature X implementation', '--tag', 'feature-x'], + { cwd: testDir } + ); + + const result = await helpers.taskMaster( + 'update-tasks', + [ + '--tag', 'feature-x', + '--prompt', 'Add deployment considerations' + ], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated'); + + // Verify task in tag was updated + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const featureXTasks = tasks['feature-x'].tasks; + const hasDeploymentInfo = featureXTasks.some(t => + t.details && t.details.toLowerCase().includes('deploy') + ); + expect(hasDeploymentInfo).toBe(true); + }, 60000); + + it('should update tasks across multiple tags', async () => { + // Create multiple tags + await helpers.taskMaster('add-tag', ['backend'], { cwd: testDir }); + await helpers.taskMaster('add-tag', ['frontend'], { cwd: testDir }); + + // Update all tasks across all tags + const result = await helpers.taskMaster( + 'update-tasks', + ['--prompt', 'Add error handling strategies'], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated'); + }, 60000); + }); + + describe('Output formats', () => { + it('should support JSON output format', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + [ + '--ids', '1', + '--prompt', 'Add monitoring requirements', + '--output', 'json' + ], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + + // Output should be valid JSON + const jsonOutput = JSON.parse(result.stdout); + expect(jsonOutput.success).toBe(true); + expect(jsonOutput.updated).toBeDefined(); + expect(jsonOutput.tasks).toBeDefined(); + }, 60000); + }); + + describe('Error handling', () => { + it('should fail without prompt', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + ['--ids', '1'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('prompt'); }); - // Test 15: Performance test - bulk update many tasks - await runTest('Performance - update 50 tasks', async () => { - // Create 50 tasks - const perfTaskIds = []; - for (let i = 1; i <= 50; i++) { - const result = await helpers.taskMaster( - 'add-task', - ['--title', `Performance test task ${i}`], - { cwd: testDir } - ); - perfTaskIds.push(helpers.extractTaskId(result.stdout)); + it('should handle invalid task IDs gracefully', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + ['--ids', '999,1000', '--prompt', 'Update non-existent tasks'], + { cwd: testDir } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('No tasks found'); + }); + + it('should handle invalid status filter', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + ['--status', 'invalid-status', '--prompt', 'Test invalid status'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid status'); + }); + + it('should handle invalid priority filter', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + ['--priority', 'urgent', '--prompt', 'Test invalid priority'], + { cwd: testDir, allowFailure: true } + ); + + expect(result.exitCode).not.toBe(0); + expect(result.stderr).toContain('Invalid priority'); + }); + }); + + describe('Performance and edge cases', () => { + it('should handle updating many tasks efficiently', async () => { + // Add many tasks + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const currentTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + + for (let i = 4; i <= 20; i++) { + currentTasks.master.tasks.push({ + id: i, + title: `Task ${i}`, + description: `Description for task ${i}`, + priority: i % 3 === 0 ? 'high' : 'medium', + status: 'pending', + details: `Details for task ${i}` + }); } + writeFileSync(tasksPath, JSON.stringify(currentTasks, null, 2)); const startTime = Date.now(); const result = await helpers.taskMaster( 'update-tasks', - ['--ids', perfTaskIds.join(','), '--set-priority', 'low', '--add-metadata', 'perf_test=true'], - { cwd: testDir } + ['--prompt', 'Add brief implementation notes'], + { cwd: testDir, timeout: 120000 } ); const duration = Date.now() - startTime; - if (result.exitCode !== 0) { - throw new Error(`Command failed: ${result.stderr}`); - } + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('Updated 20 task'); + expect(duration).toBeLessThan(120000); // Should complete within 2 minutes + }, 150000); + + it('should preserve task relationships during updates', async () => { + // Add tasks with dependencies + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const currentTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + currentTasks.master.tasks[1].dependencies = [1]; + currentTasks.master.tasks[2].dependencies = [1, 2]; + writeFileSync(tasksPath, JSON.stringify(currentTasks, null, 2)); - logger.info(`Updated 50 tasks in ${duration}ms`); - if (duration > 10000) { - throw new Error(`Bulk update too slow: ${duration}ms`); - } + const result = await helpers.taskMaster( + 'update-tasks', + ['--prompt', 'Clarify implementation order'], + { cwd: testDir, timeout: 45000 } + ); - // Verify all were updated - const updateMatch = result.stdout.match(/(\d+) tasks? updated/); - if (!updateMatch || parseInt(updateMatch[1]) !== 50) { - throw new Error('Not all tasks were updated'); - } - }); + expect(result).toHaveExitCode(0); + + // Verify dependencies are preserved + const updatedTasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + expect(updatedTasks.master.tasks[1].dependencies).toEqual([1]); + expect(updatedTasks.master.tasks[2].dependencies).toEqual([1, 2]); + }, 60000); + }); - // Calculate summary - const totalTests = results.tests.length; - const passedTests = results.tests.filter(t => t.status === 'passed').length; - const failedTests = results.tests.filter(t => t.status === 'failed').length; + describe('Dry run mode', () => { + it('should preview updates without applying them', async () => { + const result = await helpers.taskMaster( + 'update-tasks', + [ + '--ids', '1,2', + '--prompt', 'Add test coverage requirements', + '--dry-run' + ], + { cwd: testDir, timeout: 45000 } + ); + + expect(result).toHaveExitCode(0); + expect(result.stdout).toContain('DRY RUN'); + expect(result.stdout).toContain('Would update'); + + // Verify tasks were NOT actually updated + const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json'); + const tasks = JSON.parse(readFileSync(tasksPath, 'utf8')); + const hasTestCoverage = tasks.master.tasks.some(t => + t.details && t.details.toLowerCase().includes('test coverage') + ); + expect(hasTestCoverage).toBe(false); + }, 60000); + }); - logger.info('\n=== Update-Tasks Test Summary ==='); - logger.info(`Total tests: ${totalTests}`); - logger.info(`Passed: ${passedTests}`); - logger.info(`Failed: ${failedTests}`); - - if (failedTests > 0) { - results.status = 'failed'; - logger.error(`\n${failedTests} tests failed`); - } else { - logger.success('\n✅ All update-tasks tests passed!'); - } - - } catch (error) { - results.status = 'failed'; - results.errors.push({ - test: 'update-tasks test suite', - error: error.message, - stack: error.stack - }); - logger.error(`Update-tasks test suite failed: ${error.message}`); - } - - return results; -} \ No newline at end of file + describe('Integration with other commands', () => { + it('should work with expand command on bulk-updated tasks', async () => { + // First bulk update + await helpers.taskMaster( + 'update-tasks', + ['--ids', '1', '--prompt', 'Add detailed specifications'], + { cwd: testDir, timeout: 45000 } + ); + + // Then expand the updated task + const expandResult = await helpers.taskMaster( + 'expand', + ['--id', '1'], + { cwd: testDir, timeout: 45000 } + ); + + expect(expandResult).toHaveExitCode(0); + expect(expandResult.stdout).toContain('Expanded task'); + }, 90000); + }); +}); \ No newline at end of file