From 80f933cd82b276ad3d9a9ab6cce8b79e0f2ce539 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Sun, 30 Mar 2025 23:09:05 -0400 Subject: [PATCH] test: Add tests for parse-prd overwrite confirmation and fix existing test Adds unit tests to tests/unit/task-manager.test.js for the parse-prd command confirmation prompt when overwriting an existing tasks.json file. Also fixes the existing directory creation test. Refs #67, Fixes #65 --- tests/unit/task-manager.test.js | 102 +++++++++++++++++++++++++++++++- 1 file changed, 100 insertions(+), 2 deletions(-) diff --git a/tests/unit/task-manager.test.js b/tests/unit/task-manager.test.js index 1db520df..13263fb1 100644 --- a/tests/unit/task-manager.test.js +++ b/tests/unit/task-manager.test.js @@ -25,6 +25,7 @@ const mockIsTaskDependentOn = jest.fn().mockReturnValue(false); const mockCreate = jest.fn(); // Mock for Anthropic messages.create const mockChatCompletionsCreate = jest.fn(); // Mock for Perplexity chat.completions.create const mockGetAvailableAIModel = jest.fn(); // <<<<< Added mock function +const mockPromptYesNo = jest.fn(); // Mock for confirmation prompt // Mock fs module jest.mock('fs', () => ({ @@ -76,6 +77,7 @@ jest.mock('../../scripts/modules/utils.js', () => ({ readComplexityReport: jest.fn(), // <<<<< Added mock findTaskInComplexityReport: jest.fn(), // <<<<< Added mock truncate: jest.fn((str, len) => str.slice(0, len)), // <<<<< Added mock + promptYesNo: mockPromptYesNo, // Added mock for confirmation prompt })); // Mock AI services - Update this mock @@ -129,6 +131,19 @@ jest.mock('../../scripts/modules/task-manager.js', () => { // Create a simplified version of parsePRD for testing const testParsePRD = async (prdPath, outputPath, numTasks) => { try { + // Check if the output file already exists + if (mockExistsSync(outputPath)) { + const confirmOverwrite = await mockPromptYesNo( + `Warning: ${outputPath} already exists. Overwrite?`, + false + ); + + if (!confirmOverwrite) { + console.log(`Operation cancelled. ${outputPath} was not modified.`); + return null; + } + } + const prdContent = mockReadFileSync(prdPath, 'utf8'); const tasks = await mockCallClaude(prdContent, prdPath, numTasks); const dir = mockDirname(outputPath); @@ -563,6 +578,7 @@ describe('Task Manager Module', () => { mockDirname.mockReturnValue('tasks'); mockCallClaude.mockResolvedValue(sampleClaudeResponse); mockGenerateTaskFiles.mockResolvedValue(undefined); + mockPromptYesNo.mockResolvedValue(true); // Default to "yes" for confirmation }); test('should parse a PRD file and generate tasks', async () => { @@ -586,8 +602,13 @@ describe('Task Manager Module', () => { }); test('should create the tasks directory if it does not exist', async () => { - // Mock existsSync to return false to simulate directory doesn't exist - mockExistsSync.mockReturnValueOnce(false); + // Mock existsSync to return false specifically for the directory check + // but true for the output file check (so we don't trigger confirmation path) + mockExistsSync.mockImplementation((path) => { + if (path === 'tasks/tasks.json') return false; // Output file doesn't exist + if (path === 'tasks') return false; // Directory doesn't exist + return true; // Default for other paths + }); // Call the function await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3); @@ -624,6 +645,83 @@ describe('Task Manager Module', () => { // Verify generateTaskFiles was called expect(mockGenerateTaskFiles).toHaveBeenCalledWith('tasks/tasks.json', 'tasks'); }); + + test('should prompt for confirmation when tasks.json already exists', async () => { + // Setup mocks to simulate tasks.json already exists + mockExistsSync.mockImplementation((path) => { + if (path === 'tasks/tasks.json') return true; // Output file exists + if (path === 'tasks') return true; // Directory exists + return false; + }); + + // Call the function + await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3); + + // Verify prompt was called with expected message + expect(mockPromptYesNo).toHaveBeenCalledWith( + 'Warning: tasks/tasks.json already exists. Overwrite?', + false + ); + + // Verify the file was written after confirmation + expect(mockWriteJSON).toHaveBeenCalledWith('tasks/tasks.json', sampleClaudeResponse); + }); + + test('should not overwrite tasks.json when user declines confirmation', async () => { + // Setup mocks to simulate tasks.json already exists + mockExistsSync.mockImplementation((path) => { + if (path === 'tasks/tasks.json') return true; // Output file exists + if (path === 'tasks') return true; // Directory exists + return false; + }); + + // Mock user declining the confirmation + mockPromptYesNo.mockResolvedValueOnce(false); + + // Mock console.log to capture output + const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(() => {}); + + // Call the function + const result = await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3); + + // Verify prompt was called + expect(mockPromptYesNo).toHaveBeenCalledWith( + 'Warning: tasks/tasks.json already exists. Overwrite?', + false + ); + + // Verify the file was NOT written + expect(mockWriteJSON).not.toHaveBeenCalled(); + + // Verify appropriate message was logged + expect(mockConsoleLog).toHaveBeenCalledWith( + 'Operation cancelled. tasks/tasks.json was not modified.' + ); + + // Verify result is null when operation is cancelled + expect(result).toBeNull(); + + // Restore console.log + mockConsoleLog.mockRestore(); + }); + + test('should not prompt for confirmation when tasks.json does not exist', async () => { + // Setup mocks to simulate tasks.json does not exist + mockExistsSync.mockImplementation((path) => { + if (path === 'tasks/tasks.json') return false; // Output file doesn't exist + if (path === 'tasks') return true; // Directory exists + return false; + }); + + // Call the function + await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3); + + // Verify prompt was NOT called + expect(mockPromptYesNo).not.toHaveBeenCalled(); + + // Verify the file was written without confirmation + expect(mockWriteJSON).toHaveBeenCalledWith('tasks/tasks.json', sampleClaudeResponse); + }); }); describe.skip('updateTasks function', () => {