Adds a test for parse-prd.

This commit is contained in:
Eyal Toledano
2025-03-24 17:33:57 -04:00
parent 193d07d580
commit 287923f60d
3 changed files with 219 additions and 10 deletions

View File

@@ -0,0 +1,44 @@
/**
* Sample Claude API response for testing
*/
export const sampleClaudeResponse = {
tasks: [
{
id: 1,
title: "Setup Task Data Structure",
description: "Implement the core task data structure and file operations",
status: "pending",
dependencies: [],
priority: "high",
details: "Create the tasks.json file structure with support for task properties including ID, title, description, status, dependencies, priority, details, and test strategy. Implement file system operations for reading and writing task data.",
testStrategy: "Verify tasks.json is created with the correct structure and that task data can be read from and written to the file."
},
{
id: 2,
title: "Implement CLI Foundation",
description: "Create the command-line interface foundation with basic commands",
status: "pending",
dependencies: [1],
priority: "high",
details: "Set up Commander.js for handling CLI commands. Implement the basic command structure including help documentation. Create the foundational command parsing logic.",
testStrategy: "Test each command to ensure it properly parses arguments and options. Verify help documentation is displayed correctly."
},
{
id: 3,
title: "Develop Task Management Operations",
description: "Implement core operations for creating, reading, updating, and deleting tasks",
status: "pending",
dependencies: [1],
priority: "medium",
details: "Implement functions for listing tasks, adding new tasks, updating task status, and removing tasks. Include support for filtering tasks by status and other properties.",
testStrategy: "Create unit tests for each CRUD operation to verify they correctly modify the task data."
}
],
metadata: {
projectName: "Task Management CLI",
totalTasks: 3,
sourceFile: "tests/fixtures/sample-prd.txt",
generatedAt: "2023-12-15"
}
};

42
tests/fixtures/sample-prd.txt vendored Normal file
View File

@@ -0,0 +1,42 @@
# Sample PRD for Testing
<PRD>
# Technical Architecture
## System Components
1. **Task Management Core**
- Tasks.json file structure
- Task model with dependencies
- Task state management
2. **Command Line Interface**
- Command parsing and execution
- Display utilities
## Data Models
### Task Model
```json
{
"id": 1,
"title": "Task Title",
"description": "Brief task description",
"status": "pending|done|deferred",
"dependencies": [0],
"priority": "high|medium|low",
"details": "Implementation instructions",
"testStrategy": "Verification approach"
}
```
# Development Roadmap
## Phase 1: Core Task Management System
1. **Task Data Structure**
- Implement the tasks.json structure
- Create file system interactions
2. **Command Line Interface Foundation**
- Implement command parsing
- Create help documentation
</PRD>

View File

@@ -3,17 +3,63 @@
*/ */
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import { findNextTask } from '../../scripts/modules/task-manager.js';
// Mock dependencies // Mock implementations
jest.mock('fs'); const mockReadFileSync = jest.fn();
jest.mock('path'); const mockExistsSync = jest.fn();
jest.mock('@anthropic-ai/sdk'); const mockMkdirSync = jest.fn();
jest.mock('cli-table3'); const mockDirname = jest.fn();
jest.mock('../../scripts/modules/ui.js'); const mockCallClaude = jest.fn();
jest.mock('../../scripts/modules/ai-services.js'); const mockWriteJSON = jest.fn();
jest.mock('../../scripts/modules/dependency-manager.js'); const mockGenerateTaskFiles = jest.fn();
jest.mock('../../scripts/modules/utils.js');
// Mock fs module
jest.mock('fs', () => ({
readFileSync: mockReadFileSync,
existsSync: mockExistsSync,
mkdirSync: mockMkdirSync
}));
// Mock path module
jest.mock('path', () => ({
dirname: mockDirname
}));
// Mock AI services
jest.mock('../../scripts/modules/ai-services.js', () => ({
callClaude: mockCallClaude
}));
// Mock utils
jest.mock('../../scripts/modules/utils.js', () => ({
writeJSON: mockWriteJSON,
log: jest.fn()
}));
// Create a simplified version of parsePRD for testing
const testParsePRD = async (prdPath, outputPath, numTasks) => {
try {
const prdContent = mockReadFileSync(prdPath, 'utf8');
const tasks = await mockCallClaude(prdContent, prdPath, numTasks);
const dir = mockDirname(outputPath);
if (!mockExistsSync(dir)) {
mockMkdirSync(dir, { recursive: true });
}
mockWriteJSON(outputPath, tasks);
await mockGenerateTaskFiles(outputPath, dir);
return tasks;
} catch (error) {
console.error(`Error parsing PRD: ${error.message}`);
process.exit(1);
}
};
// Import after mocks
import { findNextTask } from '../../scripts/modules/task-manager.js';
import { sampleClaudeResponse } from '../fixtures/sample-claude-response.js';
describe('Task Manager Module', () => { describe('Task Manager Module', () => {
beforeEach(() => { beforeEach(() => {
@@ -182,4 +228,81 @@ describe('Task Manager Module', () => {
expect(true).toBe(true); expect(true).toBe(true);
}); });
}); });
describe('parsePRD function', () => {
// Mock the sample PRD content
const samplePRDContent = '# Sample PRD for Testing';
beforeEach(() => {
// Reset all mocks
jest.clearAllMocks();
// Set up mocks for fs, path and other modules
mockReadFileSync.mockReturnValue(samplePRDContent);
mockExistsSync.mockReturnValue(true);
mockDirname.mockReturnValue('tasks');
mockCallClaude.mockResolvedValue(sampleClaudeResponse);
mockGenerateTaskFiles.mockResolvedValue(undefined);
});
test('should parse a PRD file and generate tasks', async () => {
// Call the test version of parsePRD
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
// Verify fs.readFileSync was called with the correct arguments
expect(mockReadFileSync).toHaveBeenCalledWith('path/to/prd.txt', 'utf8');
// Verify callClaude was called with the correct arguments
expect(mockCallClaude).toHaveBeenCalledWith(samplePRDContent, 'path/to/prd.txt', 3);
// Verify directory check
expect(mockExistsSync).toHaveBeenCalledWith('tasks');
// Verify writeJSON was called with the correct arguments
expect(mockWriteJSON).toHaveBeenCalledWith('tasks/tasks.json', sampleClaudeResponse);
// Verify generateTaskFiles was called
expect(mockGenerateTaskFiles).toHaveBeenCalledWith('tasks/tasks.json', 'tasks');
});
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);
// Call the function
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
// Verify mkdir was called
expect(mockMkdirSync).toHaveBeenCalledWith('tasks', { recursive: true });
});
test('should handle errors in the PRD parsing process', async () => {
// Mock an error in callClaude
const testError = new Error('Test error in Claude API call');
mockCallClaude.mockRejectedValueOnce(testError);
// Mock console.error and process.exit
const mockConsoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
const mockProcessExit = jest.spyOn(process, 'exit').mockImplementation(() => {});
// Call the function
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
// Verify error handling
expect(mockConsoleError).toHaveBeenCalled();
expect(mockProcessExit).toHaveBeenCalledWith(1);
// Restore mocks
mockConsoleError.mockRestore();
mockProcessExit.mockRestore();
});
test('should generate individual task files after creating tasks.json', async () => {
// Call the function
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3);
// Verify generateTaskFiles was called
expect(mockGenerateTaskFiles).toHaveBeenCalledWith('tasks/tasks.json', 'tasks');
});
});
}); });