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