/** * Tests for the add-task.js module */ import { jest } from "@jest/globals"; // Mock the dependencies before importing the module under test 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), })); 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( "../../../../../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( "../../../../../scripts/modules/config-manager.js", () => ({ getDefaultPriority: jest.fn(() => "medium"), }) ); jest.unstable_mockModule( "../../../../../scripts/modules/task-manager/generate-task-files.js", () => ({ default: jest.fn().mockResolvedValue(), }) ); // Mock external UI libraries 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), }, })); jest.unstable_mockModule("boxen", () => ({ default: jest.fn((text) => text), })); 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( "../../../../../scripts/modules/utils.js" ); const { generateObjectService } = await import( "../../../../../scripts/modules/ai-services-unified.js" ); const generateTaskFiles = await import( "../../../../../scripts/modules/task-manager/generate-task-files.js" ); // Import the module under test const { default: addTask } = await import( "../../../../../scripts/modules/task-manager/add-task.js" ); 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"); }); });