2025-05-28 00:42:31 +02:00
|
|
|
/**
|
|
|
|
* Tests for the add-task.js module
|
|
|
|
*/
|
2025-06-07 20:30:51 -04:00
|
|
|
import { jest } from "@jest/globals";
|
2025-05-28 00:42:31 +02:00
|
|
|
|
|
|
|
// Mock the dependencies before importing the module under test
|
2025-06-07 20:30:51 -04:00
|
|
|
jest.unstable_mockModule("../../../../../scripts/modules/utils.js", () => ({
|
|
|
|
readJSON: jest.fn(),
|
|
|
|
writeJSON: jest.fn(),
|
|
|
|
log: jest.fn(),
|
|
|
|
CONFIG: {
|
|
|
|
model: "mock-claude-model",
|
|
|
|
maxTokens: 4000,
|
|
|
|
temperature: 0.7,
|
|
|
|
debug: false,
|
|
|
|
},
|
|
|
|
truncate: jest.fn((text) => text),
|
2025-05-28 00:42:31 +02:00
|
|
|
}));
|
|
|
|
|
2025-06-07 20:30:51 -04:00
|
|
|
jest.unstable_mockModule("../../../../../scripts/modules/ui.js", () => ({
|
|
|
|
displayBanner: jest.fn(),
|
|
|
|
getStatusWithColor: jest.fn((status) => status),
|
|
|
|
startLoadingIndicator: jest.fn(),
|
|
|
|
stopLoadingIndicator: jest.fn(),
|
|
|
|
succeedLoadingIndicator: jest.fn(),
|
|
|
|
failLoadingIndicator: jest.fn(),
|
|
|
|
warnLoadingIndicator: jest.fn(),
|
|
|
|
infoLoadingIndicator: jest.fn(),
|
|
|
|
displayAiUsageSummary: jest.fn(),
|
2025-05-28 00:42:31 +02:00
|
|
|
}));
|
|
|
|
|
|
|
|
jest.unstable_mockModule(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/ai-services-unified.js",
|
|
|
|
() => ({
|
|
|
|
generateObjectService: jest.fn().mockResolvedValue({
|
|
|
|
mainResult: {
|
|
|
|
object: {
|
|
|
|
title: "Task from prompt: Create a new authentication system",
|
|
|
|
description:
|
|
|
|
"Task generated from: Create a new authentication system",
|
|
|
|
details:
|
|
|
|
"Implementation details for task generated from prompt: Create a new authentication system",
|
|
|
|
testStrategy: "Write unit tests to verify functionality",
|
|
|
|
dependencies: [],
|
|
|
|
},
|
|
|
|
},
|
|
|
|
telemetryData: {
|
|
|
|
timestamp: new Date().toISOString(),
|
|
|
|
userId: "1234567890",
|
|
|
|
commandName: "add-task",
|
|
|
|
modelUsed: "claude-3-5-sonnet",
|
|
|
|
providerName: "anthropic",
|
|
|
|
inputTokens: 1000,
|
|
|
|
outputTokens: 500,
|
|
|
|
totalTokens: 1500,
|
|
|
|
totalCost: 0.012414,
|
|
|
|
currency: "USD",
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
})
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
jest.unstable_mockModule(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/config-manager.js",
|
|
|
|
() => ({
|
|
|
|
getDefaultPriority: jest.fn(() => "medium"),
|
|
|
|
})
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
jest.unstable_mockModule(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/task-manager/generate-task-files.js",
|
|
|
|
() => ({
|
|
|
|
default: jest.fn().mockResolvedValue(),
|
|
|
|
})
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Mock external UI libraries
|
2025-06-07 20:30:51 -04:00
|
|
|
jest.unstable_mockModule("chalk", () => ({
|
|
|
|
default: {
|
|
|
|
white: { bold: jest.fn((text) => text) },
|
|
|
|
cyan: Object.assign(
|
|
|
|
jest.fn((text) => text),
|
|
|
|
{
|
|
|
|
bold: jest.fn((text) => text),
|
|
|
|
}
|
|
|
|
),
|
|
|
|
green: jest.fn((text) => text),
|
|
|
|
yellow: jest.fn((text) => text),
|
|
|
|
bold: jest.fn((text) => text),
|
|
|
|
},
|
2025-05-28 00:42:31 +02:00
|
|
|
}));
|
|
|
|
|
2025-06-07 20:30:51 -04:00
|
|
|
jest.unstable_mockModule("boxen", () => ({
|
|
|
|
default: jest.fn((text) => text),
|
2025-05-28 00:42:31 +02:00
|
|
|
}));
|
|
|
|
|
2025-06-07 20:30:51 -04:00
|
|
|
jest.unstable_mockModule("cli-table3", () => ({
|
|
|
|
default: jest.fn().mockImplementation(() => ({
|
|
|
|
push: jest.fn(),
|
|
|
|
toString: jest.fn(() => "mocked table"),
|
|
|
|
})),
|
2025-05-28 00:42:31 +02:00
|
|
|
}));
|
|
|
|
|
|
|
|
// Import the mocked modules
|
|
|
|
const { readJSON, writeJSON, log } = await import(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/utils.js"
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const { generateObjectService } = await import(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/ai-services-unified.js"
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
const generateTaskFiles = await import(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/task-manager/generate-task-files.js"
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
// Import the module under test
|
|
|
|
const { default: addTask } = await import(
|
2025-06-07 20:30:51 -04:00
|
|
|
"../../../../../scripts/modules/task-manager/add-task.js"
|
2025-05-28 00:42:31 +02:00
|
|
|
);
|
|
|
|
|
2025-06-07 20:30:51 -04:00
|
|
|
describe("addTask", () => {
|
|
|
|
const sampleTasks = {
|
|
|
|
tasks: [
|
|
|
|
{
|
|
|
|
id: 1,
|
|
|
|
title: "Task 1",
|
|
|
|
description: "First task",
|
|
|
|
status: "pending",
|
|
|
|
dependencies: [],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 2,
|
|
|
|
title: "Task 2",
|
|
|
|
description: "Second task",
|
|
|
|
status: "pending",
|
|
|
|
dependencies: [],
|
|
|
|
},
|
|
|
|
{
|
|
|
|
id: 3,
|
|
|
|
title: "Task 3",
|
|
|
|
description: "Third task",
|
|
|
|
status: "pending",
|
|
|
|
dependencies: [1],
|
|
|
|
},
|
|
|
|
],
|
|
|
|
};
|
|
|
|
|
|
|
|
// Create a helper function for consistent mcpLog mock
|
|
|
|
const createMcpLogMock = () => ({
|
|
|
|
info: jest.fn(),
|
|
|
|
warn: jest.fn(),
|
|
|
|
error: jest.fn(),
|
|
|
|
debug: jest.fn(),
|
|
|
|
success: jest.fn(),
|
|
|
|
});
|
|
|
|
|
|
|
|
beforeEach(() => {
|
|
|
|
jest.clearAllMocks();
|
|
|
|
readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks)));
|
|
|
|
|
|
|
|
// Mock console.log to avoid output during tests
|
|
|
|
jest.spyOn(console, "log").mockImplementation(() => {});
|
|
|
|
});
|
|
|
|
|
|
|
|
afterEach(() => {
|
|
|
|
console.log.mockRestore();
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should add a new task using AI", async () => {
|
|
|
|
// Arrange
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await addTask(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
prompt,
|
|
|
|
[],
|
|
|
|
"medium",
|
|
|
|
context,
|
|
|
|
"json"
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(readJSON).toHaveBeenCalledWith("tasks/tasks.json");
|
|
|
|
expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object));
|
|
|
|
expect(writeJSON).toHaveBeenCalledWith(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
expect.objectContaining({
|
|
|
|
tasks: expect.arrayContaining([
|
|
|
|
expect.objectContaining({
|
|
|
|
id: 4, // Next ID after existing tasks
|
|
|
|
title: expect.stringContaining(
|
|
|
|
"Create a new authentication system"
|
|
|
|
),
|
|
|
|
status: "pending",
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
expect(generateTaskFiles.default).toHaveBeenCalled();
|
|
|
|
expect(result).toEqual(
|
|
|
|
expect.objectContaining({
|
|
|
|
newTaskId: 4,
|
|
|
|
telemetryData: expect.any(Object),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should validate dependencies when adding a task", async () => {
|
|
|
|
// Arrange
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const validDependencies = [1, 2]; // These exist in sampleTasks
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await addTask(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
prompt,
|
|
|
|
validDependencies,
|
|
|
|
"medium",
|
|
|
|
context,
|
|
|
|
"json"
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(writeJSON).toHaveBeenCalledWith(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
expect.objectContaining({
|
|
|
|
tasks: expect.arrayContaining([
|
|
|
|
expect.objectContaining({
|
|
|
|
id: 4,
|
|
|
|
dependencies: validDependencies,
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should filter out invalid dependencies", async () => {
|
|
|
|
// Arrange
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const invalidDependencies = [999]; // Non-existent task ID
|
|
|
|
const context = { mcpLog: createMcpLogMock() };
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await addTask(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
prompt,
|
|
|
|
invalidDependencies,
|
|
|
|
"medium",
|
|
|
|
context,
|
|
|
|
"json"
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(writeJSON).toHaveBeenCalledWith(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
expect.objectContaining({
|
|
|
|
tasks: expect.arrayContaining([
|
|
|
|
expect.objectContaining({
|
|
|
|
id: 4,
|
|
|
|
dependencies: [], // Invalid dependencies should be filtered out
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
expect(context.mcpLog.warn).toHaveBeenCalledWith(
|
|
|
|
expect.stringContaining(
|
|
|
|
"The following dependencies do not exist or are invalid: 999"
|
|
|
|
)
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should use specified priority", async () => {
|
|
|
|
// Arrange
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const priority = "high";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
await addTask("tasks/tasks.json", prompt, [], priority, context, "json");
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(writeJSON).toHaveBeenCalledWith(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
expect.objectContaining({
|
|
|
|
tasks: expect.arrayContaining([
|
|
|
|
expect.objectContaining({
|
|
|
|
priority: priority,
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should handle empty tasks file", async () => {
|
|
|
|
// Arrange
|
|
|
|
readJSON.mockReturnValue({ tasks: [] });
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await addTask(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
prompt,
|
|
|
|
[],
|
|
|
|
"medium",
|
|
|
|
context,
|
|
|
|
"json"
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
|
|
|
expect(writeJSON).toHaveBeenCalledWith(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
expect.objectContaining({
|
|
|
|
tasks: expect.arrayContaining([
|
|
|
|
expect.objectContaining({
|
|
|
|
id: 1,
|
|
|
|
}),
|
|
|
|
]),
|
|
|
|
})
|
|
|
|
);
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should handle missing tasks file", async () => {
|
|
|
|
// Arrange
|
|
|
|
readJSON.mockReturnValue(null);
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act
|
|
|
|
const result = await addTask(
|
|
|
|
"tasks/tasks.json",
|
|
|
|
prompt,
|
|
|
|
[],
|
|
|
|
"medium",
|
|
|
|
context,
|
|
|
|
"json"
|
|
|
|
);
|
|
|
|
|
|
|
|
// Assert
|
|
|
|
expect(result.newTaskId).toBe(1); // First task should have ID 1
|
|
|
|
expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should handle AI service errors", async () => {
|
|
|
|
// Arrange
|
|
|
|
generateObjectService.mockRejectedValueOnce(new Error("AI service failed"));
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
await expect(
|
|
|
|
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
|
|
|
).rejects.toThrow("AI service failed");
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should handle file read errors", async () => {
|
|
|
|
// Arrange
|
|
|
|
readJSON.mockImplementation(() => {
|
|
|
|
throw new Error("File read failed");
|
|
|
|
});
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
await expect(
|
|
|
|
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
|
|
|
).rejects.toThrow("File read failed");
|
|
|
|
});
|
|
|
|
|
|
|
|
test("should handle file write errors", async () => {
|
|
|
|
// Arrange
|
|
|
|
writeJSON.mockImplementation(() => {
|
|
|
|
throw new Error("File write failed");
|
|
|
|
});
|
|
|
|
const prompt = "Create a new authentication system";
|
|
|
|
const context = {
|
|
|
|
mcpLog: createMcpLogMock(),
|
|
|
|
};
|
|
|
|
|
|
|
|
// Act & Assert
|
|
|
|
await expect(
|
|
|
|
addTask("tasks/tasks.json", prompt, [], "medium", context, "json")
|
|
|
|
).rejects.toThrow("File write failed");
|
|
|
|
});
|
2025-05-28 00:42:31 +02:00
|
|
|
});
|