Co-authored-by: Ralph Khreish <Crunchyman-ralph@users.noreply.github.com> Co-authored-by: claude[bot] <209825114+claude[bot]@users.noreply.github.com> Resolves issue #1271 - MCP Connection Closed Error After Upgrading to v0.27.3
172 lines
4.8 KiB
JavaScript
172 lines
4.8 KiB
JavaScript
/**
|
|
* Tests for the addSubtask function
|
|
*/
|
|
import { jest } from '@jest/globals';
|
|
|
|
// Mock dependencies before importing the module
|
|
const mockUtils = {
|
|
readJSON: jest.fn(),
|
|
writeJSON: jest.fn(),
|
|
log: jest.fn(),
|
|
getCurrentTag: jest.fn()
|
|
};
|
|
const mockTaskManager = {
|
|
isTaskDependentOn: jest.fn()
|
|
};
|
|
const mockGenerateTaskFiles = jest.fn();
|
|
|
|
jest.unstable_mockModule(
|
|
'../../../../../scripts/modules/utils.js',
|
|
() => mockUtils
|
|
);
|
|
jest.unstable_mockModule(
|
|
'../../../../../scripts/modules/task-manager.js',
|
|
() => mockTaskManager
|
|
);
|
|
jest.unstable_mockModule(
|
|
'../../../../../scripts/modules/task-manager/generate-task-files.js',
|
|
() => ({
|
|
default: mockGenerateTaskFiles
|
|
})
|
|
);
|
|
|
|
const addSubtask = (
|
|
await import('../../../../../scripts/modules/task-manager/add-subtask.js')
|
|
).default;
|
|
|
|
describe('addSubtask function', () => {
|
|
const multiTagData = {
|
|
master: {
|
|
tasks: [{ id: 1, title: 'Master Task', subtasks: [] }],
|
|
metadata: { description: 'Master tasks' }
|
|
},
|
|
'feature-branch': {
|
|
tasks: [{ id: 1, title: 'Feature Task', subtasks: [] }],
|
|
metadata: { description: 'Feature tasks' }
|
|
}
|
|
};
|
|
|
|
beforeEach(() => {
|
|
jest.clearAllMocks();
|
|
mockTaskManager.isTaskDependentOn.mockReturnValue(false);
|
|
});
|
|
|
|
test('should add a new subtask and preserve other tags', async () => {
|
|
const context = { projectRoot: '/fake/root', tag: 'feature-branch' };
|
|
const newSubtaskData = { title: 'My New Subtask' };
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [{ id: 1, title: 'Feature Task', subtasks: [] }],
|
|
metadata: { description: 'Feature tasks' }
|
|
});
|
|
|
|
await addSubtask('tasks.json', '1', null, newSubtaskData, true, context);
|
|
|
|
expect(mockUtils.writeJSON).toHaveBeenCalledWith(
|
|
'tasks.json',
|
|
expect.any(Object),
|
|
'/fake/root',
|
|
'feature-branch'
|
|
);
|
|
const writtenData = mockUtils.writeJSON.mock.calls[0][1];
|
|
const parentTask = writtenData.tasks.find((t) => t.id === 1);
|
|
expect(parentTask.subtasks).toHaveLength(1);
|
|
expect(parentTask.subtasks[0].title).toBe('My New Subtask');
|
|
});
|
|
|
|
test('should add a new subtask to a parent task', async () => {
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [{ id: 1, title: 'Parent Task', subtasks: [] }]
|
|
});
|
|
const context = {};
|
|
const newSubtask = await addSubtask(
|
|
'tasks.json',
|
|
'1',
|
|
null,
|
|
{ title: 'New Subtask' },
|
|
true,
|
|
context
|
|
);
|
|
expect(newSubtask).toBeDefined();
|
|
expect(newSubtask.id).toBe(1);
|
|
expect(newSubtask.parentTaskId).toBe(1);
|
|
expect(mockUtils.writeJSON).toHaveBeenCalled();
|
|
const writeCallArgs = mockUtils.writeJSON.mock.calls[0][1]; // data is the second arg now
|
|
const parentTask = writeCallArgs.tasks.find((t) => t.id === 1);
|
|
expect(parentTask.subtasks).toHaveLength(1);
|
|
expect(parentTask.subtasks[0].title).toBe('New Subtask');
|
|
});
|
|
|
|
test('should convert an existing task to a subtask', async () => {
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [
|
|
{ id: 1, title: 'Parent Task', subtasks: [] },
|
|
{ id: 2, title: 'Existing Task 2', subtasks: [] }
|
|
]
|
|
});
|
|
const context = {};
|
|
const convertedSubtask = await addSubtask(
|
|
'tasks.json',
|
|
'1',
|
|
'2',
|
|
null,
|
|
true,
|
|
context
|
|
);
|
|
expect(convertedSubtask.id).toBe(1);
|
|
expect(convertedSubtask.parentTaskId).toBe(1);
|
|
expect(convertedSubtask.title).toBe('Existing Task 2');
|
|
expect(mockUtils.writeJSON).toHaveBeenCalled();
|
|
const writeCallArgs = mockUtils.writeJSON.mock.calls[0][1];
|
|
const parentTask = writeCallArgs.tasks.find((t) => t.id === 1);
|
|
expect(parentTask.subtasks).toHaveLength(1);
|
|
expect(parentTask.subtasks[0].title).toBe('Existing Task 2');
|
|
});
|
|
|
|
test('should throw an error if parent task does not exist', async () => {
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [{ id: 1, title: 'Task 1', subtasks: [] }]
|
|
});
|
|
const context = {};
|
|
await expect(
|
|
addSubtask(
|
|
'tasks.json',
|
|
'99',
|
|
null,
|
|
{ title: 'New Subtask' },
|
|
true,
|
|
context
|
|
)
|
|
).rejects.toThrow('Parent task with ID 99 not found');
|
|
});
|
|
|
|
test('should throw an error if trying to convert a non-existent task', async () => {
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [{ id: 1, title: 'Parent Task', subtasks: [] }]
|
|
});
|
|
const context = {};
|
|
await expect(
|
|
addSubtask('tasks.json', '1', '99', null, true, context)
|
|
).rejects.toThrow('Task with ID 99 not found');
|
|
});
|
|
|
|
test('should throw an error for circular dependency', async () => {
|
|
mockUtils.readJSON.mockReturnValueOnce({
|
|
tasks: [
|
|
{ id: 1, title: 'Parent Task', subtasks: [] },
|
|
{ id: 2, title: 'Child Task', subtasks: [] }
|
|
]
|
|
});
|
|
mockTaskManager.isTaskDependentOn.mockImplementation(
|
|
(tasks, parentTask, existingTaskIdNum) => {
|
|
return parentTask.id === 1 && existingTaskIdNum === 2;
|
|
}
|
|
);
|
|
const context = {};
|
|
await expect(
|
|
addSubtask('tasks.json', '1', '2', null, true, context)
|
|
).rejects.toThrow(
|
|
'Cannot create circular dependency: task 1 is already a subtask or dependent of task 2'
|
|
);
|
|
});
|
|
});
|