This commit is contained in:
Ralph Khreish
2025-07-11 13:29:52 +03:00
parent 74232d0e0d
commit 14cc09d241
27 changed files with 5699 additions and 958 deletions

View File

@@ -3,7 +3,14 @@
* Tests all aspects of task expansion including single, multiple, and recursive expansion
*/
const { mkdtempSync, existsSync, readFileSync, rmSync, writeFileSync, mkdirSync } = require('fs');
const {
mkdtempSync,
existsSync,
readFileSync,
rmSync,
writeFileSync,
mkdirSync
} = require('fs');
const { join } = require('path');
const { tmpdir } = require('os');
@@ -17,11 +24,11 @@ describe('expand-task command', () => {
beforeEach(async () => {
// Create test directory
testDir = mkdtempSync(join(tmpdir(), 'task-master-expand-task-'));
// Initialize test helpers
const context = global.createTestContext('expand-task');
helpers = context.helpers;
// Copy .env file if it exists
const mainEnvPath = join(__dirname, '../../../../.env');
const testEnvPath = join(testDir, '.env');
@@ -29,18 +36,20 @@ describe('expand-task command', () => {
const envContent = readFileSync(mainEnvPath, 'utf8');
writeFileSync(testEnvPath, envContent);
}
// Initialize task-master project
const initResult = await helpers.taskMaster('init', ['-y'], { cwd: testDir });
const initResult = await helpers.taskMaster('init', ['-y'], {
cwd: testDir
});
expect(initResult).toHaveExitCode(0);
// Ensure tasks.json exists (bug workaround)
const tasksJsonPath = join(testDir, '.taskmaster/tasks/tasks.json');
if (!existsSync(tasksJsonPath)) {
mkdirSync(join(testDir, '.taskmaster/tasks'), { recursive: true });
writeFileSync(tasksJsonPath, JSON.stringify({ master: { tasks: [] } }));
}
// Create simple task for expansion
const simpleResult = await helpers.taskMaster(
'add-task',
@@ -48,19 +57,27 @@ describe('expand-task command', () => {
{ cwd: testDir }
);
simpleTaskId = helpers.extractTaskId(simpleResult.stdout);
// Create complex task for expansion
const complexResult = await helpers.taskMaster(
'add-task',
['--prompt', 'Build a full-stack web application with React frontend and Node.js backend'],
[
'--prompt',
'Build a full-stack web application with React frontend and Node.js backend'
],
{ cwd: testDir }
);
complexTaskId = helpers.extractTaskId(complexResult.stdout);
// Create manual task (no AI prompt)
const manualResult = await helpers.taskMaster(
'add-task',
['--title', 'Manual task for expansion', '--description', 'This is a manually created task'],
[
'--title',
'Manual task for expansion',
'--description',
'This is a manually created task'
],
{ cwd: testDir }
);
manualTaskId = helpers.extractTaskId(manualResult.stdout);
@@ -80,12 +97,14 @@ describe('expand-task command', () => {
['--id', simpleTaskId],
{ cwd: testDir, timeout: 45000 }
);
expect(result).toHaveExitCode(0);
expect(result.stdout).toContain('Expanded');
// Verify subtasks were created
const showResult = await helpers.taskMaster('show', [simpleTaskId], { cwd: testDir });
const showResult = await helpers.taskMaster('show', [simpleTaskId], {
cwd: testDir
});
expect(showResult.stdout).toContain('Subtasks:');
}, 60000);
@@ -95,11 +114,13 @@ describe('expand-task command', () => {
['--id', complexTaskId, '--num', '3'],
{ cwd: testDir, timeout: 45000 }
);
expect(result).toHaveExitCode(0);
// Check that we got approximately 3 subtasks
const showResult = await helpers.taskMaster('show', [complexTaskId], { cwd: testDir });
const showResult = await helpers.taskMaster('show', [complexTaskId], {
cwd: testDir
});
const subtaskMatches = showResult.stdout.match(/\d+\.\d+/g);
expect(subtaskMatches).toBeTruthy();
expect(subtaskMatches.length).toBeGreaterThanOrEqual(2);
@@ -112,7 +133,7 @@ describe('expand-task command', () => {
['--id', simpleTaskId, '--research'],
{ cwd: testDir, timeout: 60000 }
);
expect(result).toHaveExitCode(0);
expect(result.stdout).toContain('research');
}, 90000);
@@ -120,14 +141,21 @@ describe('expand-task command', () => {
it('should expand with additional context', async () => {
const result = await helpers.taskMaster(
'expand',
['--id', manualTaskId, '--prompt', 'Focus on security best practices and testing'],
[
'--id',
manualTaskId,
'--prompt',
'Focus on security best practices and testing'
],
{ cwd: testDir, timeout: 45000 }
);
expect(result).toHaveExitCode(0);
// Verify context was used
const showResult = await helpers.taskMaster('show', [manualTaskId], { cwd: testDir });
const showResult = await helpers.taskMaster('show', [manualTaskId], {
cwd: testDir
});
const outputLower = showResult.stdout.toLowerCase();
expect(outputLower).toMatch(/security|test/);
}, 60000);
@@ -135,39 +163,37 @@ describe('expand-task command', () => {
describe('Bulk expansion', () => {
it('should expand all tasks', async () => {
const result = await helpers.taskMaster(
'expand',
['--all'],
{ cwd: testDir, timeout: 120000 }
);
const result = await helpers.taskMaster('expand', ['--all'], {
cwd: testDir,
timeout: 120000
});
expect(result).toHaveExitCode(0);
expect(result.stdout).toContain('Expanding all');
// Verify all tasks have subtasks
const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json');
const tasksData = JSON.parse(readFileSync(tasksPath, 'utf8'));
const tasks = tasksData.master.tasks;
const tasksWithSubtasks = tasks.filter(t => t.subtasks && t.subtasks.length > 0);
const tasksWithSubtasks = tasks.filter(
(t) => t.subtasks && t.subtasks.length > 0
);
expect(tasksWithSubtasks.length).toBeGreaterThanOrEqual(2);
}, 150000);
it('should expand all with force flag', async () => {
// First expand one task
await helpers.taskMaster(
'expand',
['--id', simpleTaskId],
{ cwd: testDir }
);
await helpers.taskMaster('expand', ['--id', simpleTaskId], {
cwd: testDir
});
// Then expand all with force
const result = await helpers.taskMaster(
'expand',
['--all', '--force'],
{ cwd: testDir, timeout: 120000 }
);
const result = await helpers.taskMaster('expand', ['--all', '--force'], {
cwd: testDir,
timeout: 120000
});
expect(result).toHaveExitCode(0);
expect(result.stdout).toContain('force');
}, 150000);
@@ -176,30 +202,32 @@ describe('expand-task command', () => {
describe('Specific task ranges', () => {
it('should expand tasks by ID range', async () => {
// Create more tasks
await helpers.taskMaster(
'add-task',
['--prompt', 'Additional task 1'],
{ cwd: testDir }
);
await helpers.taskMaster(
'add-task',
['--prompt', 'Additional task 2'],
{ cwd: testDir }
);
await helpers.taskMaster('add-task', ['--prompt', 'Additional task 1'], {
cwd: testDir
});
await helpers.taskMaster('add-task', ['--prompt', 'Additional task 2'], {
cwd: testDir
});
const result = await helpers.taskMaster(
'expand',
['--from', '2', '--to', '4'],
{ cwd: testDir, timeout: 90000 }
);
expect(result).toHaveExitCode(0);
// Verify tasks 2-4 were expanded
const showResult2 = await helpers.taskMaster('show', ['2'], { cwd: testDir });
const showResult3 = await helpers.taskMaster('show', ['3'], { cwd: testDir });
const showResult4 = await helpers.taskMaster('show', ['4'], { cwd: testDir });
const showResult2 = await helpers.taskMaster('show', ['2'], {
cwd: testDir
});
const showResult3 = await helpers.taskMaster('show', ['3'], {
cwd: testDir
});
const showResult4 = await helpers.taskMaster('show', ['4'], {
cwd: testDir
});
expect(showResult2.stdout).toContain('Subtasks:');
expect(showResult3.stdout).toContain('Subtasks:');
expect(showResult4.stdout).toContain('Subtasks:');
@@ -211,13 +239,17 @@ describe('expand-task command', () => {
['--id', `${simpleTaskId},${complexTaskId}`],
{ cwd: testDir, timeout: 90000 }
);
expect(result).toHaveExitCode(0);
// Both tasks should have subtasks
const showResult1 = await helpers.taskMaster('show', [simpleTaskId], { cwd: testDir });
const showResult2 = await helpers.taskMaster('show', [complexTaskId], { cwd: testDir });
const showResult1 = await helpers.taskMaster('show', [simpleTaskId], {
cwd: testDir
});
const showResult2 = await helpers.taskMaster('show', [complexTaskId], {
cwd: testDir
});
expect(showResult1.stdout).toContain('Subtasks:');
expect(showResult2.stdout).toContain('Subtasks:');
}, 120000);
@@ -225,31 +257,28 @@ describe('expand-task command', () => {
describe('Error handling', () => {
it('should fail for non-existent task ID', async () => {
const result = await helpers.taskMaster(
'expand',
['--id', '99999'],
{ cwd: testDir, allowFailure: true }
);
const result = await helpers.taskMaster('expand', ['--id', '99999'], {
cwd: testDir,
allowFailure: true
});
expect(result.exitCode).not.toBe(0);
expect(result.stderr).toContain('not found');
});
it('should skip already expanded tasks without force', async () => {
// First expansion
await helpers.taskMaster(
'expand',
['--id', simpleTaskId],
{ cwd: testDir }
);
await helpers.taskMaster('expand', ['--id', simpleTaskId], {
cwd: testDir
});
// Second expansion without force
const result = await helpers.taskMaster(
'expand',
['--id', simpleTaskId],
{ cwd: testDir }
);
expect(result).toHaveExitCode(0);
expect(result.stdout.toLowerCase()).toMatch(/already|skip/);
});
@@ -260,7 +289,7 @@ describe('expand-task command', () => {
['--id', simpleTaskId, '--num', '-1'],
{ cwd: testDir, allowFailure: true }
);
expect(result.exitCode).not.toBe(0);
});
});
@@ -269,22 +298,22 @@ describe('expand-task command', () => {
it('should expand tasks in specific tag', async () => {
// Create tag and tagged task
await helpers.taskMaster('add-tag', ['feature-tag'], { cwd: testDir });
const taggedResult = await helpers.taskMaster(
'add-task',
['--prompt', 'Tagged task for expansion', '--tag', 'feature-tag'],
{ cwd: testDir }
);
const taggedId = helpers.extractTaskId(taggedResult.stdout);
const result = await helpers.taskMaster(
'expand',
['--id', taggedId, '--tag', 'feature-tag'],
{ cwd: testDir, timeout: 45000 }
);
expect(result).toHaveExitCode(0);
// Verify expansion in correct tag
const showResult = await helpers.taskMaster(
'show',
@@ -302,27 +331,27 @@ describe('expand-task command', () => {
['--id', simpleTaskId, '--model', 'gpt-3.5-turbo'],
{ cwd: testDir, timeout: 45000 }
);
expect(result).toHaveExitCode(0);
}, 60000);
});
describe('Output validation', () => {
it('should create valid subtask structure', async () => {
await helpers.taskMaster(
'expand',
['--id', complexTaskId],
{ cwd: testDir }
);
await helpers.taskMaster('expand', ['--id', complexTaskId], {
cwd: testDir
});
const tasksPath = join(testDir, '.taskmaster/tasks/tasks.json');
const tasksData = JSON.parse(readFileSync(tasksPath, 'utf8'));
const task = tasksData.master.tasks.find(t => t.id === parseInt(complexTaskId));
const task = tasksData.master.tasks.find(
(t) => t.id === parseInt(complexTaskId)
);
expect(task.subtasks).toBeDefined();
expect(Array.isArray(task.subtasks)).toBe(true);
expect(task.subtasks.length).toBeGreaterThan(0);
// Validate subtask structure
task.subtasks.forEach((subtask, index) => {
expect(subtask.id).toBe(`${complexTaskId}.${index + 1}`);
@@ -340,17 +369,15 @@ describe('expand-task command', () => {
{ cwd: testDir }
);
const depTaskId = helpers.extractTaskId(depResult.stdout);
// Expand the task
await helpers.taskMaster(
'expand',
['--id', depTaskId],
{ cwd: testDir }
);
await helpers.taskMaster('expand', ['--id', depTaskId], { cwd: testDir });
// Check dependencies are preserved
const showResult = await helpers.taskMaster('show', [depTaskId], { cwd: testDir });
const showResult = await helpers.taskMaster('show', [depTaskId], {
cwd: testDir
});
expect(showResult.stdout).toContain(`Dependencies: ${simpleTaskId}`);
});
});
});
});