221 lines
7.8 KiB
JavaScript
221 lines
7.8 KiB
JavaScript
const { describe, it, expect, beforeEach } = require('@jest/globals');
|
|
const path = require('path');
|
|
const fs = require('fs').promises;
|
|
|
|
// Mock the PRP runner adapter
|
|
jest.mock('../../tools/prp-runner-adapter');
|
|
|
|
describe('Story to PRP Converter', () => {
|
|
let converter;
|
|
let mockStory;
|
|
let mockArchitecture;
|
|
|
|
beforeEach(() => {
|
|
// Reset mocks
|
|
jest.clearAllMocks();
|
|
|
|
// Mock story content
|
|
mockStory = {
|
|
id: '1.1',
|
|
title: 'User Authentication',
|
|
status: 'Draft',
|
|
story: 'As a user, I want to authenticate with email and password, so that I can access protected resources',
|
|
acceptanceCriteria: [
|
|
'User can login with valid email and password',
|
|
'User receives JWT token upon successful login',
|
|
'Invalid credentials return appropriate error message'
|
|
],
|
|
tasks: [
|
|
'Create authentication service',
|
|
'Implement login endpoint',
|
|
'Add JWT token generation',
|
|
'Write unit tests'
|
|
],
|
|
devNotes: {
|
|
technicalContext: 'Use JWT tokens for authentication',
|
|
architecture: 'Follow REST API patterns',
|
|
testing: 'Use pytest for unit tests'
|
|
}
|
|
};
|
|
|
|
// Mock architecture content
|
|
mockArchitecture = {
|
|
techStack: 'Python, FastAPI, SQLAlchemy',
|
|
patterns: 'REST API, JWT authentication',
|
|
testing: 'pytest, coverage > 80%'
|
|
};
|
|
});
|
|
|
|
describe('Story Parsing', () => {
|
|
it('should parse BMad story format correctly', async () => {
|
|
// Test story parsing functionality
|
|
const parsedStory = await converter.parseStory(mockStory);
|
|
|
|
expect(parsedStory).toBeDefined();
|
|
expect(parsedStory.goal).toBeDefined();
|
|
expect(parsedStory.acceptanceCriteria).toHaveLength(3);
|
|
expect(parsedStory.tasks).toHaveLength(4);
|
|
});
|
|
|
|
it('should extract story components accurately', async () => {
|
|
const components = await converter.extractComponents(mockStory);
|
|
|
|
expect(components.storyStatement).toContain('As a user');
|
|
expect(components.acceptanceCriteria).toContain('User can login');
|
|
expect(components.tasks).toContain('Create authentication service');
|
|
});
|
|
|
|
it('should validate story completeness', async () => {
|
|
const validation = await converter.validateStory(mockStory);
|
|
|
|
expect(validation.isComplete).toBe(true);
|
|
expect(validation.missingComponents).toHaveLength(0);
|
|
});
|
|
});
|
|
|
|
describe('Context Mapping', () => {
|
|
it('should map BMad architecture to PRP context', async () => {
|
|
const context = await converter.mapContext(mockStory, mockArchitecture);
|
|
|
|
expect(context.documentation).toBeDefined();
|
|
expect(context.codebaseContext).toBeDefined();
|
|
expect(context.technicalConstraints).toBeDefined();
|
|
});
|
|
|
|
it('should preserve technical context during mapping', async () => {
|
|
const context = await converter.mapContext(mockStory, mockArchitecture);
|
|
|
|
expect(context.technicalConstraints).toContain('JWT');
|
|
expect(context.codebaseContext).toContain('FastAPI');
|
|
});
|
|
|
|
it('should handle missing architecture gracefully', async () => {
|
|
const context = await converter.mapContext(mockStory, null);
|
|
|
|
expect(context.documentation).toBeDefined();
|
|
expect(context.warnings).toContain('No architecture context available');
|
|
});
|
|
});
|
|
|
|
describe('PRP Generation', () => {
|
|
it('should generate valid PRP format', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
|
|
expect(prp).toContain('## Goal');
|
|
expect(prp).toContain('## Why');
|
|
expect(prp).toContain('## What');
|
|
expect(prp).toContain('## All Needed Context');
|
|
expect(prp).toContain('## Implementation Blueprint');
|
|
expect(prp).toContain('## Validation Loop');
|
|
});
|
|
|
|
it('should include all story requirements in PRP', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
|
|
expect(prp).toContain('User can login with valid email and password');
|
|
expect(prp).toContain('Create authentication service');
|
|
expect(prp).toContain('JWT tokens');
|
|
});
|
|
|
|
it('should create executable validation loops', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
|
|
expect(prp).toContain('### Level 1: Syntax & Style');
|
|
expect(prp).toContain('### Level 2: Unit Tests');
|
|
expect(prp).toContain('### Level 3: Integration Test');
|
|
});
|
|
});
|
|
|
|
describe('Validation', () => {
|
|
it('should validate PRP context completeness', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
const validation = await converter.validatePRP(prp);
|
|
|
|
expect(validation.contextComplete).toBe(true);
|
|
expect(validation.validationLoopsExecutable).toBe(true);
|
|
});
|
|
|
|
it('should detect missing context', async () => {
|
|
const incompleteStory = { ...mockStory, devNotes: {} };
|
|
const prp = await converter.generatePRP(incompleteStory, mockArchitecture);
|
|
const validation = await converter.validatePRP(prp);
|
|
|
|
expect(validation.contextComplete).toBe(false);
|
|
expect(validation.missingContext).toBeDefined();
|
|
});
|
|
|
|
it('should validate information density', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
const validation = await converter.validatePRP(prp);
|
|
|
|
expect(validation.informationDensity).toBeGreaterThan(0.7);
|
|
});
|
|
});
|
|
|
|
describe('File Operations', () => {
|
|
it('should create PRP file with correct naming', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
const filePath = await converter.savePRP(prp, mockStory);
|
|
|
|
expect(filePath).toContain('PRPs/1.1.user-authentication.md');
|
|
});
|
|
|
|
it('should maintain traceability to original story', async () => {
|
|
const prp = await converter.generatePRP(mockStory, mockArchitecture);
|
|
await converter.savePRP(prp, mockStory);
|
|
|
|
const savedPRP = await fs.readFile('PRPs/1.1.user-authentication.md', 'utf8');
|
|
expect(savedPRP).toContain('Original Story: 1.1');
|
|
expect(savedPRP).toContain('Conversion Date:');
|
|
});
|
|
});
|
|
|
|
describe('Error Handling', () => {
|
|
it('should handle invalid story format', async () => {
|
|
const invalidStory = { id: '1.1' }; // Missing required fields
|
|
|
|
await expect(converter.generatePRP(invalidStory, mockArchitecture))
|
|
.rejects.toThrow('Invalid story format');
|
|
});
|
|
|
|
it('should handle missing dependencies', async () => {
|
|
// Mock missing dependencies
|
|
jest.spyOn(fs, 'access').mockRejectedValue(new Error('File not found'));
|
|
|
|
await expect(converter.validateEnvironment())
|
|
.rejects.toThrow('Missing required dependencies');
|
|
});
|
|
|
|
it('should provide helpful error messages', async () => {
|
|
try {
|
|
await converter.generatePRP(null, mockArchitecture);
|
|
} catch (error) {
|
|
expect(error.message).toContain('Story is required');
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('Integration', () => {
|
|
it('should integrate with BMad workflow', async () => {
|
|
const result = await converter.convertStoryToPRP(mockStory, mockArchitecture);
|
|
|
|
expect(result.success).toBe(true);
|
|
expect(result.prpPath).toBeDefined();
|
|
expect(result.traceability).toBeDefined();
|
|
});
|
|
|
|
it('should maintain BMad story format compatibility', async () => {
|
|
const result = await converter.convertStoryToPRP(mockStory, mockArchitecture);
|
|
|
|
expect(result.bmadCompatible).toBe(true);
|
|
expect(result.storyId).toBe('1.1');
|
|
});
|
|
|
|
it('should support workflow continuation', async () => {
|
|
const result = await converter.convertStoryToPRP(mockStory, mockArchitecture);
|
|
|
|
expect(result.nextSteps).toBeDefined();
|
|
expect(result.workflowState).toBe('ready_for_execution');
|
|
});
|
|
});
|
|
});
|