mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2026-01-30 06:12:05 +00:00
Co-authored-by: Ben Coombs <bjcoombs@users.noreply.github.com> Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> - fixes #1532
201 lines
5.6 KiB
TypeScript
201 lines
5.6 KiB
TypeScript
/**
|
|
* @fileoverview Integration tests for 'task-master loop' command
|
|
*
|
|
* Tests the loop command's CLI integration including option parsing,
|
|
* validation errors, and help text. Note: The loop command spawns
|
|
* Claude Code which is not available in test environments, so these
|
|
* tests focus on pre-execution validation and CLI structure.
|
|
*
|
|
* @integration
|
|
*/
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { createTask, createTasksFile } from '@tm/core/testing';
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
import { getCliBinPath } from '../../helpers/test-utils.js';
|
|
|
|
// Increase hook timeout for this file - init command can be slow in CI
|
|
vi.setConfig({ hookTimeout: 30000 });
|
|
|
|
// Capture initial working directory at module load time
|
|
const initialCwd = process.cwd();
|
|
|
|
describe('loop command', () => {
|
|
let testDir: string;
|
|
let tasksPath: string;
|
|
let binPath: string;
|
|
|
|
beforeEach(() => {
|
|
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'tm-loop-test-'));
|
|
process.chdir(testDir);
|
|
process.env.TASKMASTER_SKIP_AUTO_UPDATE = '1';
|
|
|
|
binPath = getCliBinPath();
|
|
|
|
execSync(`node "${binPath}" init --yes`, {
|
|
stdio: 'pipe',
|
|
env: { ...process.env, TASKMASTER_SKIP_AUTO_UPDATE: '1' }
|
|
});
|
|
|
|
tasksPath = path.join(testDir, '.taskmaster', 'tasks', 'tasks.json');
|
|
|
|
// Use fixture to create initial tasks file with some tasks
|
|
const initialTasks = createTasksFile({
|
|
tasks: [
|
|
createTask({
|
|
id: 1,
|
|
title: 'Test Task',
|
|
description: 'A task for testing loop',
|
|
status: 'pending'
|
|
})
|
|
]
|
|
});
|
|
fs.writeFileSync(tasksPath, JSON.stringify(initialTasks, null, 2));
|
|
});
|
|
|
|
afterEach(() => {
|
|
try {
|
|
// Restore to the original working directory captured at module load
|
|
process.chdir(initialCwd);
|
|
} catch {
|
|
// Fallback to home directory if initial directory no longer exists
|
|
process.chdir(os.homedir());
|
|
}
|
|
|
|
if (testDir && fs.existsSync(testDir)) {
|
|
fs.rmSync(testDir, { recursive: true, force: true });
|
|
}
|
|
|
|
delete process.env.TASKMASTER_SKIP_AUTO_UPDATE;
|
|
});
|
|
|
|
const runLoop = (args = ''): { output: string; exitCode: number } => {
|
|
try {
|
|
const output = execSync(`node "${binPath}" loop ${args}`, {
|
|
encoding: 'utf-8',
|
|
stdio: 'pipe',
|
|
timeout: 5000, // Short timeout since we can't actually run claude
|
|
env: { ...process.env, TASKMASTER_SKIP_AUTO_UPDATE: '1' }
|
|
});
|
|
return { output, exitCode: 0 };
|
|
} catch (error: any) {
|
|
return {
|
|
output: error.stderr?.toString() || error.stdout?.toString() || '',
|
|
exitCode: error.status || 1
|
|
};
|
|
}
|
|
};
|
|
|
|
const runHelp = (): { output: string; exitCode: number } => {
|
|
try {
|
|
const output = execSync(`node "${binPath}" loop --help`, {
|
|
encoding: 'utf-8',
|
|
stdio: 'pipe',
|
|
env: { ...process.env, TASKMASTER_SKIP_AUTO_UPDATE: '1' }
|
|
});
|
|
return { output, exitCode: 0 };
|
|
} catch (error: any) {
|
|
return {
|
|
output: error.stdout?.toString() || error.stderr?.toString() || '',
|
|
exitCode: error.status || 1
|
|
};
|
|
}
|
|
};
|
|
|
|
describe('command registration', () => {
|
|
it('should be registered and show in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('loop');
|
|
});
|
|
|
|
it('should show description in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output.toLowerCase()).toContain('claude');
|
|
expect(output.toLowerCase()).toContain('loop');
|
|
});
|
|
});
|
|
|
|
describe('option documentation', () => {
|
|
it('should show -n/--iterations option in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('-n');
|
|
expect(output).toContain('--iterations');
|
|
});
|
|
|
|
it('should show -p/--prompt option in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('-p');
|
|
expect(output).toContain('--prompt');
|
|
});
|
|
|
|
it('should show -t/--tag option in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('-t');
|
|
expect(output).toContain('--tag');
|
|
});
|
|
|
|
it('should show --progress-file option in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('--progress-file');
|
|
});
|
|
|
|
it('should show --project option in help', () => {
|
|
const { output, exitCode } = runHelp();
|
|
|
|
expect(exitCode).toBe(0);
|
|
expect(output).toContain('--project');
|
|
});
|
|
});
|
|
|
|
describe('validation errors', () => {
|
|
it('should reject invalid iterations (non-numeric)', () => {
|
|
const { output, exitCode } = runLoop('-n abc');
|
|
|
|
expect(exitCode).toBe(1);
|
|
expect(output.toLowerCase()).toContain('invalid');
|
|
expect(output.toLowerCase()).toContain('iterations');
|
|
});
|
|
|
|
it('should reject invalid iterations (negative)', () => {
|
|
const { output, exitCode } = runLoop('-n -5');
|
|
|
|
expect(exitCode).toBe(1);
|
|
expect(output.toLowerCase()).toContain('invalid');
|
|
});
|
|
|
|
it('should reject invalid iterations (zero)', () => {
|
|
const { output, exitCode } = runLoop('-n 0');
|
|
|
|
expect(exitCode).toBe(1);
|
|
expect(output.toLowerCase()).toContain('invalid');
|
|
expect(output.toLowerCase()).toContain('iterations');
|
|
});
|
|
});
|
|
|
|
describe('error messages', () => {
|
|
it('should show helpful error for invalid iterations', () => {
|
|
const { output, exitCode } = runLoop('-n invalid');
|
|
|
|
expect(exitCode).toBe(1);
|
|
// Should mention what's wrong and what's expected
|
|
expect(output.toLowerCase()).toContain('iterations');
|
|
expect(output.toLowerCase()).toContain('positive');
|
|
});
|
|
});
|
|
});
|