chore: prettier formatting

This commit is contained in:
Eyal Toledano
2025-04-09 18:20:47 -04:00
parent 8ad1749036
commit 0a657fb9b2
7 changed files with 487 additions and 376 deletions

View File

@@ -27,7 +27,7 @@ import {
* @param {string} [args.title] - Task title (for manual task creation) * @param {string} [args.title] - Task title (for manual task creation)
* @param {string} [args.description] - Task description (for manual task creation) * @param {string} [args.description] - Task description (for manual task creation)
* @param {string} [args.details] - Implementation details (for manual task creation) * @param {string} [args.details] - Implementation details (for manual task creation)
* @param {string} [args.testStrategy] - Test strategy (for manual task creation) * @param {string} [args.testStrategy] - Test strategy (for manual task creation)
* @param {string} [args.dependencies] - Comma-separated list of task IDs this task depends on * @param {string} [args.dependencies] - Comma-separated list of task IDs this task depends on
* @param {string} [args.priority='medium'] - Task priority (high, medium, low) * @param {string} [args.priority='medium'] - Task priority (high, medium, low)
* @param {string} [args.file='tasks/tasks.json'] - Path to the tasks file * @param {string} [args.file='tasks/tasks.json'] - Path to the tasks file
@@ -47,16 +47,19 @@ export async function addTaskDirect(args, log, context = {}) {
// Check if this is manual task creation or AI-driven task creation // Check if this is manual task creation or AI-driven task creation
const isManualCreation = args.title && args.description; const isManualCreation = args.title && args.description;
// Check required parameters // Check required parameters
if (!args.prompt && !isManualCreation) { if (!args.prompt && !isManualCreation) {
log.error('Missing required parameters: either prompt or title+description must be provided'); log.error(
'Missing required parameters: either prompt or title+description must be provided'
);
disableSilentMode(); disableSilentMode();
return { return {
success: false, success: false,
error: { error: {
code: 'MISSING_PARAMETER', code: 'MISSING_PARAMETER',
message: 'Either the prompt parameter or both title and description parameters are required for adding a task' message:
'Either the prompt parameter or both title and description parameters are required for adding a task'
} }
}; };
} }
@@ -76,7 +79,7 @@ export async function addTaskDirect(args, log, context = {}) {
const { session } = context; const { session } = context;
let manualTaskData = null; let manualTaskData = null;
if (isManualCreation) { if (isManualCreation) {
// Create manual task data object // Create manual task data object
manualTaskData = { manualTaskData = {
@@ -85,11 +88,11 @@ export async function addTaskDirect(args, log, context = {}) {
details: args.details || '', details: args.details || '',
testStrategy: args.testStrategy || '' testStrategy: args.testStrategy || ''
}; };
log.info( log.info(
`Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}` `Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
); );
// Call the addTask function with manual task data // Call the addTask function with manual task data
const newTaskId = await addTask( const newTaskId = await addTask(
tasksPath, tasksPath,
@@ -104,10 +107,10 @@ export async function addTaskDirect(args, log, context = {}) {
null, // No custom environment null, // No custom environment
manualTaskData // Pass the manual task data manualTaskData // Pass the manual task data
); );
// Restore normal logging // Restore normal logging
disableSilentMode(); disableSilentMode();
return { return {
success: true, success: true,
data: { data: {

View File

@@ -22,11 +22,28 @@ export function registerAddTaskTool(server) {
name: 'add_task', name: 'add_task',
description: 'Add a new task using AI', description: 'Add a new task using AI',
parameters: z.object({ parameters: z.object({
prompt: z.string().optional().describe('Description of the task to add (required if not using manual fields)'), prompt: z
title: z.string().optional().describe('Task title (for manual task creation)'), .string()
description: z.string().optional().describe('Task description (for manual task creation)'), .optional()
details: z.string().optional().describe('Implementation details (for manual task creation)'), .describe(
testStrategy: z.string().optional().describe('Test strategy (for manual task creation)'), 'Description of the task to add (required if not using manual fields)'
),
title: z
.string()
.optional()
.describe('Task title (for manual task creation)'),
description: z
.string()
.optional()
.describe('Task description (for manual task creation)'),
details: z
.string()
.optional()
.describe('Implementation details (for manual task creation)'),
testStrategy: z
.string()
.optional()
.describe('Test strategy (for manual task creation)'),
dependencies: z dependencies: z
.string() .string()
.optional() .optional()
@@ -35,11 +52,16 @@ export function registerAddTaskTool(server) {
.string() .string()
.optional() .optional()
.describe('Task priority (high, medium, low)'), .describe('Task priority (high, medium, low)'),
file: z.string().optional().describe('Path to the tasks file (default: tasks/tasks.json)'), file: z
.string()
.optional()
.describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z projectRoot: z
.string() .string()
.optional() .optional()
.describe('Root directory of the project (default: current working directory)'), .describe(
'Root directory of the project (default: current working directory)'
),
research: z research: z
.boolean() .boolean()
.optional() .optional()

View File

@@ -791,20 +791,46 @@ function registerCommands(programInstance) {
.command('add-task') .command('add-task')
.description('Add a new task using AI or manual input') .description('Add a new task using AI or manual input')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option('-p, --prompt <prompt>', 'Description of the task to add (required if not using manual fields)') .option(
'-p, --prompt <prompt>',
'Description of the task to add (required if not using manual fields)'
)
.option('-t, --title <title>', 'Task title (for manual task creation)') .option('-t, --title <title>', 'Task title (for manual task creation)')
.option('-d, --description <description>', 'Task description (for manual task creation)') .option(
.option('--details <details>', 'Implementation details (for manual task creation)') '-d, --description <description>',
.option('--test-strategy <testStrategy>', 'Test strategy (for manual task creation)') 'Task description (for manual task creation)'
.option('--dependencies <dependencies>', 'Comma-separated list of task IDs this task depends on') )
.option('--priority <priority>', 'Task priority (high, medium, low)', 'medium') .option(
.option('-r, --research', 'Whether to use research capabilities for task creation') '--details <details>',
'Implementation details (for manual task creation)'
)
.option(
'--test-strategy <testStrategy>',
'Test strategy (for manual task creation)'
)
.option(
'--dependencies <dependencies>',
'Comma-separated list of task IDs this task depends on'
)
.option(
'--priority <priority>',
'Task priority (high, medium, low)',
'medium'
)
.option(
'-r, --research',
'Whether to use research capabilities for task creation'
)
.action(async (options) => { .action(async (options) => {
const isManualCreation = options.title && options.description; const isManualCreation = options.title && options.description;
// Validate that either prompt or title+description are provided // Validate that either prompt or title+description are provided
if (!options.prompt && !isManualCreation) { if (!options.prompt && !isManualCreation) {
console.error(chalk.red('Error: Either --prompt or both --title and --description must be provided')); console.error(
chalk.red(
'Error: Either --prompt or both --title and --description must be provided'
)
);
process.exit(1); process.exit(1);
} }
@@ -812,7 +838,9 @@ function registerCommands(programInstance) {
// Prepare dependencies if provided // Prepare dependencies if provided
let dependencies = []; let dependencies = [];
if (options.dependencies) { if (options.dependencies) {
dependencies = options.dependencies.split(',').map(id => parseInt(id.trim(), 10)); dependencies = options.dependencies
.split(',')
.map((id) => parseInt(id.trim(), 10));
} }
// Create manual task data if title and description are provided // Create manual task data if title and description are provided
@@ -825,17 +853,27 @@ function registerCommands(programInstance) {
testStrategy: options.testStrategy || '' testStrategy: options.testStrategy || ''
}; };
console.log(chalk.blue(`Creating task manually with title: "${options.title}"`)); console.log(
chalk.blue(`Creating task manually with title: "${options.title}"`)
);
if (dependencies.length > 0) { if (dependencies.length > 0) {
console.log(chalk.blue(`Dependencies: [${dependencies.join(', ')}]`)); console.log(
chalk.blue(`Dependencies: [${dependencies.join(', ')}]`)
);
} }
if (options.priority) { if (options.priority) {
console.log(chalk.blue(`Priority: ${options.priority}`)); console.log(chalk.blue(`Priority: ${options.priority}`));
} }
} else { } else {
console.log(chalk.blue(`Creating task with AI using prompt: "${options.prompt}"`)); console.log(
chalk.blue(
`Creating task with AI using prompt: "${options.prompt}"`
)
);
if (dependencies.length > 0) { if (dependencies.length > 0) {
console.log(chalk.blue(`Dependencies: [${dependencies.join(', ')}]`)); console.log(
chalk.blue(`Dependencies: [${dependencies.join(', ')}]`)
);
} }
if (options.priority) { if (options.priority) {
console.log(chalk.blue(`Priority: ${options.priority}`)); console.log(chalk.blue(`Priority: ${options.priority}`));

View File

@@ -3434,18 +3434,18 @@ async function addTask(
'Failed to generate task data after all model attempts' 'Failed to generate task data after all model attempts'
); );
} }
// Set the AI-generated task data // Set the AI-generated task data
taskData = aiGeneratedTaskData; taskData = aiGeneratedTaskData;
} catch (error) { } catch (error) {
// Handle AI errors // Handle AI errors
log('error', `Error generating task with AI: ${error.message}`); log('error', `Error generating task with AI: ${error.message}`);
// Stop any loading indicator // Stop any loading indicator
if (outputFormat === 'text' && loadingIndicator) { if (outputFormat === 'text' && loadingIndicator) {
stopLoadingIndicator(loadingIndicator); stopLoadingIndicator(loadingIndicator);
} }
throw error; throw error;
} }
} }
@@ -3506,7 +3506,9 @@ async function addTask(
'\n' + '\n' +
chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) +
'\n' + '\n' +
chalk.white(`Priority: ${chalk.keyword(getPriorityColor(newTask.priority))(newTask.priority)}`) + chalk.white(
`Priority: ${chalk.keyword(getPriorityColor(newTask.priority))(newTask.priority)}`
) +
'\n' + '\n' +
(dependencies.length > 0 (dependencies.length > 0
? chalk.white(`Dependencies: ${dependencies.join(', ')}`) + '\n' ? chalk.white(`Dependencies: ${dependencies.join(', ')}`) + '\n'
@@ -3514,11 +3516,17 @@ async function addTask(
'\n' + '\n' +
chalk.white.bold('Next Steps:') + chalk.white.bold('Next Steps:') +
'\n' + '\n' +
chalk.cyan(`1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details`) + chalk.cyan(
`1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details`
) +
'\n' + '\n' +
chalk.cyan(`2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it`) + chalk.cyan(
`2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it`
) +
'\n' + '\n' +
chalk.cyan(`3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks`), chalk.cyan(
`3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks`
),
{ padding: 1, borderColor: 'green', borderStyle: 'round' } { padding: 1, borderColor: 'green', borderStyle: 'round' }
) )
); );

View File

@@ -1,14 +1,14 @@
{ {
"tasks": [ "tasks": [
{ {
"id": 1, "id": 1,
"dependencies": [], "dependencies": [],
"subtasks": [ "subtasks": [
{ {
"id": 1, "id": 1,
"dependencies": [] "dependencies": []
} }
] ]
} }
] ]
} }

View File

@@ -3,7 +3,10 @@
*/ */
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import { sampleTasks, emptySampleTasks } from '../../tests/fixtures/sample-tasks.js'; import {
sampleTasks,
emptySampleTasks
} from '../../tests/fixtures/sample-tasks.js';
// Mock functions that need jest.fn methods // Mock functions that need jest.fn methods
const mockParsePRD = jest.fn().mockResolvedValue(undefined); const mockParsePRD = jest.fn().mockResolvedValue(undefined);
@@ -655,34 +658,49 @@ describe('Commands Module', () => {
existsSync: jest.fn().mockReturnValue(true), existsSync: jest.fn().mockReturnValue(true),
readFileSync: jest.fn().mockReturnValue(JSON.stringify(sampleTasks)) readFileSync: jest.fn().mockReturnValue(JSON.stringify(sampleTasks))
}; };
// Create a mock task manager with an addTask function that resolves to taskId 5 // Create a mock task manager with an addTask function that resolves to taskId 5
mockTaskManager = { mockTaskManager = {
addTask: jest.fn().mockImplementation((file, prompt, dependencies, priority, session, research, generateFiles, manualTaskData) => { addTask: jest
// Return the next ID after the last one in sample tasks .fn()
const newId = sampleTasks.tasks.length + 1; .mockImplementation(
return Promise.resolve(newId.toString()); (
}) file,
prompt,
dependencies,
priority,
session,
research,
generateFiles,
manualTaskData
) => {
// Return the next ID after the last one in sample tasks
const newId = sampleTasks.tasks.length + 1;
return Promise.resolve(newId.toString());
}
)
}; };
// Create a simplified version of the add-task action function for testing // Create a simplified version of the add-task action function for testing
addTaskAction = async (cmd, options) => { addTaskAction = async (cmd, options) => {
options = options || {}; // Ensure options is not undefined options = options || {}; // Ensure options is not undefined
const isManualCreation = options.title && options.description; const isManualCreation = options.title && options.description;
// Get prompt directly or from p shorthand // Get prompt directly or from p shorthand
const prompt = options.prompt || options.p; const prompt = options.prompt || options.p;
// Validate that either prompt or title+description are provided // Validate that either prompt or title+description are provided
if (!prompt && !isManualCreation) { if (!prompt && !isManualCreation) {
throw new Error('Either --prompt or both --title and --description must be provided'); throw new Error(
'Either --prompt or both --title and --description must be provided'
);
} }
// Prepare dependencies if provided // Prepare dependencies if provided
let dependencies = []; let dependencies = [];
if (options.dependencies) { if (options.dependencies) {
dependencies = options.dependencies.split(',').map(id => id.trim()); dependencies = options.dependencies.split(',').map((id) => id.trim());
} }
// Create manual task data if title and description are provided // Create manual task data if title and description are provided
@@ -695,13 +713,13 @@ describe('Commands Module', () => {
testStrategy: options.testStrategy || '' testStrategy: options.testStrategy || ''
}; };
} }
// Call addTask with the right parameters // Call addTask with the right parameters
return await mockTaskManager.addTask( return await mockTaskManager.addTask(
options.file || 'tasks/tasks.json', options.file || 'tasks/tasks.json',
prompt, prompt,
dependencies, dependencies,
options.priority || 'medium', options.priority || 'medium',
{ session: process.env }, { session: process.env },
options.research || options.r || false, options.research || options.r || false,
null, null,
@@ -713,19 +731,21 @@ describe('Commands Module', () => {
test('should throw error if no prompt or manual task data provided', async () => { test('should throw error if no prompt or manual task data provided', async () => {
// Call without required params // Call without required params
const options = { file: 'tasks/tasks.json' }; const options = { file: 'tasks/tasks.json' };
await expect(async () => { await expect(async () => {
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
}).rejects.toThrow('Either --prompt or both --title and --description must be provided'); }).rejects.toThrow(
'Either --prompt or both --title and --description must be provided'
);
}); });
test('should handle short-hand flag -p for prompt', async () => { test('should handle short-hand flag -p for prompt', async () => {
// Use -p as prompt short-hand // Use -p as prompt short-hand
const options = { const options = {
p: 'Create a login component', p: 'Create a login component',
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that task manager was called with correct arguments // Check that task manager was called with correct arguments
@@ -737,17 +757,17 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
false, // Research flag false, // Research flag
null, // Generate files parameter null, // Generate files parameter
null // Manual task data null // Manual task data
); );
}); });
test('should handle short-hand flag -r for research', async () => { test('should handle short-hand flag -r for research', async () => {
const options = { const options = {
prompt: 'Create authentication system', prompt: 'Create authentication system',
r: true, r: true,
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that task manager was called with correct research flag // Check that task manager was called with correct research flag
@@ -759,18 +779,18 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
true, // Research flag should be true true, // Research flag should be true
null, // Generate files parameter null, // Generate files parameter
null // Manual task data null // Manual task data
); );
}); });
test('should handle manual task creation with title and description', async () => { test('should handle manual task creation with title and description', async () => {
const options = { const options = {
title: 'Login Component', title: 'Login Component',
description: 'Create a reusable login form', description: 'Create a reusable login form',
details: 'Implementation details here', details: 'Implementation details here',
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that task manager was called with correct manual task data // Check that task manager was called with correct manual task data
@@ -782,7 +802,8 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
false, false,
null, // Generate files parameter null, // Generate files parameter
{ // Manual task data {
// Manual task data
title: 'Login Component', title: 'Login Component',
description: 'Create a reusable login form', description: 'Create a reusable login form',
details: 'Implementation details here', details: 'Implementation details here',
@@ -792,12 +813,12 @@ describe('Commands Module', () => {
}); });
test('should handle dependencies parameter', async () => { test('should handle dependencies parameter', async () => {
const options = { const options = {
prompt: 'Create user settings page', prompt: 'Create user settings page',
dependencies: '1, 3, 5', // Dependencies with spaces dependencies: '1, 3, 5', // Dependencies with spaces
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that dependencies are parsed correctly // Check that dependencies are parsed correctly
@@ -809,17 +830,17 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
false, false,
null, // Generate files parameter null, // Generate files parameter
null // Manual task data null // Manual task data
); );
}); });
test('should handle priority parameter', async () => { test('should handle priority parameter', async () => {
const options = { const options = {
prompt: 'Create navigation menu', prompt: 'Create navigation menu',
priority: 'high', priority: 'high',
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that priority is passed correctly // Check that priority is passed correctly
@@ -831,16 +852,16 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
false, false,
null, // Generate files parameter null, // Generate files parameter
null // Manual task data null // Manual task data
); );
}); });
test('should use default values for optional parameters', async () => { test('should use default values for optional parameters', async () => {
const options = { const options = {
prompt: 'Basic task', prompt: 'Basic task',
file: 'tasks/tasks.json' file: 'tasks/tasks.json'
}; };
await addTaskAction(undefined, options); await addTaskAction(undefined, options);
// Check that default values are used // Check that default values are used
@@ -852,7 +873,7 @@ describe('Commands Module', () => {
{ session: process.env }, { session: process.env },
false, // Research is false by default false, // Research is false by default
null, // Generate files parameter null, // Generate files parameter
null // Manual task data null // Manual task data
); );
}); });
}); });

View File

@@ -1,326 +1,345 @@
/** /**
* Tests for the add-task MCP tool * Tests for the add-task MCP tool
* *
* Note: This test does NOT test the actual implementation. It tests that: * Note: This test does NOT test the actual implementation. It tests that:
* 1. The tool is registered correctly with the correct parameters * 1. The tool is registered correctly with the correct parameters
* 2. Arguments are passed correctly to addTaskDirect * 2. Arguments are passed correctly to addTaskDirect
* 3. Error handling works as expected * 3. Error handling works as expected
* *
* We do NOT import the real implementation - everything is mocked * We do NOT import the real implementation - everything is mocked
*/ */
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import { sampleTasks, emptySampleTasks } from '../../../fixtures/sample-tasks.js'; import {
sampleTasks,
emptySampleTasks
} from '../../../fixtures/sample-tasks.js';
// Mock EVERYTHING // Mock EVERYTHING
const mockAddTaskDirect = jest.fn(); const mockAddTaskDirect = jest.fn();
jest.mock('../../../../mcp-server/src/core/task-master-core.js', () => ({ jest.mock('../../../../mcp-server/src/core/task-master-core.js', () => ({
addTaskDirect: mockAddTaskDirect addTaskDirect: mockAddTaskDirect
})); }));
const mockHandleApiResult = jest.fn(result => result); const mockHandleApiResult = jest.fn((result) => result);
const mockGetProjectRootFromSession = jest.fn(() => '/mock/project/root'); const mockGetProjectRootFromSession = jest.fn(() => '/mock/project/root');
const mockCreateErrorResponse = jest.fn(msg => ({ const mockCreateErrorResponse = jest.fn((msg) => ({
success: false, success: false,
error: { code: 'ERROR', message: msg } error: { code: 'ERROR', message: msg }
})); }));
jest.mock('../../../../mcp-server/src/tools/utils.js', () => ({ jest.mock('../../../../mcp-server/src/tools/utils.js', () => ({
getProjectRootFromSession: mockGetProjectRootFromSession, getProjectRootFromSession: mockGetProjectRootFromSession,
handleApiResult: mockHandleApiResult, handleApiResult: mockHandleApiResult,
createErrorResponse: mockCreateErrorResponse, createErrorResponse: mockCreateErrorResponse,
createContentResponse: jest.fn(content => ({ success: true, data: content })), createContentResponse: jest.fn((content) => ({
executeTaskMasterCommand: jest.fn() success: true,
data: content
})),
executeTaskMasterCommand: jest.fn()
})); }));
// Mock the z object from zod // Mock the z object from zod
const mockZod = { const mockZod = {
object: jest.fn(() => mockZod), object: jest.fn(() => mockZod),
string: jest.fn(() => mockZod), string: jest.fn(() => mockZod),
boolean: jest.fn(() => mockZod), boolean: jest.fn(() => mockZod),
optional: jest.fn(() => mockZod), optional: jest.fn(() => mockZod),
describe: jest.fn(() => mockZod), describe: jest.fn(() => mockZod),
_def: { shape: () => ({ _def: {
prompt: {}, shape: () => ({
dependencies: {}, prompt: {},
priority: {}, dependencies: {},
research: {}, priority: {},
file: {}, research: {},
projectRoot: {} file: {},
})} projectRoot: {}
})
}
}; };
jest.mock('zod', () => ({ jest.mock('zod', () => ({
z: mockZod z: mockZod
})); }));
// DO NOT import the real module - create a fake implementation // DO NOT import the real module - create a fake implementation
// This is the fake implementation of registerAddTaskTool // This is the fake implementation of registerAddTaskTool
const registerAddTaskTool = (server) => { const registerAddTaskTool = (server) => {
// Create simplified version of the tool config // Create simplified version of the tool config
const toolConfig = { const toolConfig = {
name: 'add_task', name: 'add_task',
description: 'Add a new task using AI', description: 'Add a new task using AI',
parameters: mockZod, parameters: mockZod,
// Create a simplified mock of the execute function // Create a simplified mock of the execute function
execute: (args, context) => { execute: (args, context) => {
const { log, reportProgress, session } = context; const { log, reportProgress, session } = context;
try { try {
log.info && log.info(`Starting add-task with args: ${JSON.stringify(args)}`); log.info &&
log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
// Get project root
const rootFolder = mockGetProjectRootFromSession(session, log); // Get project root
const rootFolder = mockGetProjectRootFromSession(session, log);
// Call addTaskDirect
const result = mockAddTaskDirect({ // Call addTaskDirect
...args, const result = mockAddTaskDirect(
projectRoot: rootFolder {
}, log, { reportProgress, session }); ...args,
projectRoot: rootFolder
// Handle result },
return mockHandleApiResult(result, log); log,
} catch (error) { { reportProgress, session }
log.error && log.error(`Error in add-task tool: ${error.message}`); );
return mockCreateErrorResponse(error.message);
} // Handle result
} return mockHandleApiResult(result, log);
}; } catch (error) {
log.error && log.error(`Error in add-task tool: ${error.message}`);
// Register the tool with the server return mockCreateErrorResponse(error.message);
server.addTool(toolConfig); }
}
};
// Register the tool with the server
server.addTool(toolConfig);
}; };
describe('MCP Tool: add-task', () => { describe('MCP Tool: add-task', () => {
// Create mock server // Create mock server
let mockServer; let mockServer;
let executeFunction; let executeFunction;
// Create mock logger // Create mock logger
const mockLogger = { const mockLogger = {
debug: jest.fn(), debug: jest.fn(),
info: jest.fn(), info: jest.fn(),
warn: jest.fn(), warn: jest.fn(),
error: jest.fn() error: jest.fn()
}; };
// Test data // Test data
const validArgs = { const validArgs = {
prompt: 'Create a new task', prompt: 'Create a new task',
dependencies: '1,2', dependencies: '1,2',
priority: 'high', priority: 'high',
research: true research: true
}; };
// Standard responses // Standard responses
const successResponse = { const successResponse = {
success: true, success: true,
data: { data: {
taskId: '5', taskId: '5',
message: 'Successfully added new task #5' message: 'Successfully added new task #5'
} }
}; };
const errorResponse = { const errorResponse = {
success: false, success: false,
error: { error: {
code: 'ADD_TASK_ERROR', code: 'ADD_TASK_ERROR',
message: 'Failed to add task' message: 'Failed to add task'
} }
}; };
beforeEach(() => { beforeEach(() => {
// Reset all mocks // Reset all mocks
jest.clearAllMocks(); jest.clearAllMocks();
// Create mock server // Create mock server
mockServer = { mockServer = {
addTool: jest.fn(config => { addTool: jest.fn((config) => {
executeFunction = config.execute; executeFunction = config.execute;
}) })
}; };
// Setup default successful response // Setup default successful response
mockAddTaskDirect.mockReturnValue(successResponse); mockAddTaskDirect.mockReturnValue(successResponse);
// Register the tool // Register the tool
registerAddTaskTool(mockServer); registerAddTaskTool(mockServer);
}); });
test('should register the tool correctly', () => { test('should register the tool correctly', () => {
// Verify tool was registered // Verify tool was registered
expect(mockServer.addTool).toHaveBeenCalledWith( expect(mockServer.addTool).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
name: 'add_task', name: 'add_task',
description: 'Add a new task using AI', description: 'Add a new task using AI',
parameters: expect.any(Object), parameters: expect.any(Object),
execute: expect.any(Function) execute: expect.any(Function)
}) })
); );
// Verify the tool config was passed // Verify the tool config was passed
const toolConfig = mockServer.addTool.mock.calls[0][0]; const toolConfig = mockServer.addTool.mock.calls[0][0];
expect(toolConfig).toHaveProperty('parameters'); expect(toolConfig).toHaveProperty('parameters');
expect(toolConfig).toHaveProperty('execute'); expect(toolConfig).toHaveProperty('execute');
}); });
test('should execute the tool with valid parameters', () => { test('should execute the tool with valid parameters', () => {
// Setup context // Setup context
const mockContext = { const mockContext = {
log: mockLogger, log: mockLogger,
reportProgress: jest.fn(), reportProgress: jest.fn(),
session: { workingDirectory: '/mock/dir' } session: { workingDirectory: '/mock/dir' }
}; };
// Execute the function // Execute the function
executeFunction(validArgs, mockContext); executeFunction(validArgs, mockContext);
// Verify getProjectRootFromSession was called // Verify getProjectRootFromSession was called
expect(mockGetProjectRootFromSession).toHaveBeenCalledWith( expect(mockGetProjectRootFromSession).toHaveBeenCalledWith(
mockContext.session, mockContext.session,
mockLogger mockLogger
); );
// Verify addTaskDirect was called with correct arguments // Verify addTaskDirect was called with correct arguments
expect(mockAddTaskDirect).toHaveBeenCalledWith( expect(mockAddTaskDirect).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
...validArgs, ...validArgs,
projectRoot: '/mock/project/root' projectRoot: '/mock/project/root'
}), }),
mockLogger, mockLogger,
{ {
reportProgress: mockContext.reportProgress, reportProgress: mockContext.reportProgress,
session: mockContext.session session: mockContext.session
} }
); );
// Verify handleApiResult was called // Verify handleApiResult was called
expect(mockHandleApiResult).toHaveBeenCalledWith( expect(mockHandleApiResult).toHaveBeenCalledWith(
successResponse, successResponse,
mockLogger mockLogger
); );
}); });
test('should handle errors from addTaskDirect', () => { test('should handle errors from addTaskDirect', () => {
// Setup error response // Setup error response
mockAddTaskDirect.mockReturnValueOnce(errorResponse); mockAddTaskDirect.mockReturnValueOnce(errorResponse);
// Setup context // Setup context
const mockContext = { const mockContext = {
log: mockLogger, log: mockLogger,
reportProgress: jest.fn(), reportProgress: jest.fn(),
session: { workingDirectory: '/mock/dir' } session: { workingDirectory: '/mock/dir' }
}; };
// Execute the function // Execute the function
executeFunction(validArgs, mockContext); executeFunction(validArgs, mockContext);
// Verify addTaskDirect was called // Verify addTaskDirect was called
expect(mockAddTaskDirect).toHaveBeenCalled(); expect(mockAddTaskDirect).toHaveBeenCalled();
// Verify handleApiResult was called with error response // Verify handleApiResult was called with error response
expect(mockHandleApiResult).toHaveBeenCalledWith( expect(mockHandleApiResult).toHaveBeenCalledWith(errorResponse, mockLogger);
errorResponse, });
mockLogger
); test('should handle unexpected errors', () => {
}); // Setup error
const testError = new Error('Unexpected error');
test('should handle unexpected errors', () => { mockAddTaskDirect.mockImplementationOnce(() => {
// Setup error throw testError;
const testError = new Error('Unexpected error'); });
mockAddTaskDirect.mockImplementationOnce(() => {
throw testError; // Setup context
}); const mockContext = {
log: mockLogger,
// Setup context reportProgress: jest.fn(),
const mockContext = { session: { workingDirectory: '/mock/dir' }
log: mockLogger, };
reportProgress: jest.fn(),
session: { workingDirectory: '/mock/dir' } // Execute the function
}; executeFunction(validArgs, mockContext);
// Execute the function // Verify error was logged
executeFunction(validArgs, mockContext); expect(mockLogger.error).toHaveBeenCalledWith(
'Error in add-task tool: Unexpected error'
// Verify error was logged );
expect(mockLogger.error).toHaveBeenCalledWith(
'Error in add-task tool: Unexpected error' // Verify error response was created
); expect(mockCreateErrorResponse).toHaveBeenCalledWith('Unexpected error');
});
// Verify error response was created
expect(mockCreateErrorResponse).toHaveBeenCalledWith('Unexpected error'); test('should pass research parameter correctly', () => {
}); // Setup context
const mockContext = {
test('should pass research parameter correctly', () => { log: mockLogger,
// Setup context reportProgress: jest.fn(),
const mockContext = { session: { workingDirectory: '/mock/dir' }
log: mockLogger, };
reportProgress: jest.fn(),
session: { workingDirectory: '/mock/dir' } // Test with research=true
}; executeFunction(
{
// Test with research=true ...validArgs,
executeFunction({ research: true
...validArgs, },
research: true mockContext
}, mockContext); );
// Verify addTaskDirect was called with research=true // Verify addTaskDirect was called with research=true
expect(mockAddTaskDirect).toHaveBeenCalledWith( expect(mockAddTaskDirect).toHaveBeenCalledWith(
expect.objectContaining({ expect.objectContaining({
research: true research: true
}), }),
expect.any(Object), expect.any(Object),
expect.any(Object) expect.any(Object)
); );
// Reset mocks // Reset mocks
jest.clearAllMocks(); jest.clearAllMocks();
// Test with research=false // Test with research=false
executeFunction({ executeFunction(
...validArgs, {
research: false ...validArgs,
}, mockContext); research: false
},
// Verify addTaskDirect was called with research=false mockContext
expect(mockAddTaskDirect).toHaveBeenCalledWith( );
expect.objectContaining({
research: false // Verify addTaskDirect was called with research=false
}), expect(mockAddTaskDirect).toHaveBeenCalledWith(
expect.any(Object), expect.objectContaining({
expect.any(Object) research: false
); }),
}); expect.any(Object),
expect.any(Object)
test('should pass priority parameter correctly', () => { );
// Setup context });
const mockContext = {
log: mockLogger, test('should pass priority parameter correctly', () => {
reportProgress: jest.fn(), // Setup context
session: { workingDirectory: '/mock/dir' } const mockContext = {
}; log: mockLogger,
reportProgress: jest.fn(),
// Test different priority values session: { workingDirectory: '/mock/dir' }
['high', 'medium', 'low'].forEach(priority => { };
// Reset mocks
jest.clearAllMocks(); // Test different priority values
['high', 'medium', 'low'].forEach((priority) => {
// Execute with specific priority // Reset mocks
executeFunction({ jest.clearAllMocks();
...validArgs,
priority // Execute with specific priority
}, mockContext); executeFunction(
{
// Verify addTaskDirect was called with correct priority ...validArgs,
expect(mockAddTaskDirect).toHaveBeenCalledWith( priority
expect.objectContaining({ },
priority mockContext
}), );
expect.any(Object),
expect.any(Object) // Verify addTaskDirect was called with correct priority
); expect(mockAddTaskDirect).toHaveBeenCalledWith(
}); expect.objectContaining({
}); priority
}); }),
expect.any(Object),
expect.any(Object)
);
});
});
});