405 lines
10 KiB
JavaScript
Raw Normal View History

/**
* Tests for the add-task.js module
*/
2025-06-07 20:30:51 -04:00
import { jest } from "@jest/globals";
// 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-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(),
}));
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",
},
}),
})
);
jest.unstable_mockModule(
2025-06-07 20:30:51 -04:00
"../../../../../scripts/modules/config-manager.js",
() => ({
getDefaultPriority: jest.fn(() => "medium"),
})
);
jest.unstable_mockModule(
2025-06-07 20:30:51 -04:00
"../../../../../scripts/modules/task-manager/generate-task-files.js",
() => ({
default: jest.fn().mockResolvedValue(),
})
);
// 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-06-07 20:30:51 -04:00
jest.unstable_mockModule("boxen", () => ({
default: jest.fn((text) => text),
}));
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"),
})),
}));
// Import the mocked modules
const { readJSON, writeJSON, log } = await import(
2025-06-07 20:30:51 -04:00
"../../../../../scripts/modules/utils.js"
);
const { generateObjectService } = await import(
2025-06-07 20:30:51 -04:00
"../../../../../scripts/modules/ai-services-unified.js"
);
const generateTaskFiles = await import(
2025-06-07 20:30:51 -04:00
"../../../../../scripts/modules/task-manager/generate-task-files.js"
);
// 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-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");
});
});