Skip to main content
Glama
BMADService.test.ts11.8 kB
import { BMADService } from '../services/BMADService'; import { ParsedSpec, Requirement, UserStory, UseCase, Task, Assignment, ValidationResult } from '../types/bmad.types'; const markdownContent = `--- title: Test Markdown Spec summary: This is a summary. metadata: version: 1.0 --- # Introduction This is an introduction. ## Requirements - Requirement 1: The system must do X. (functional) - Requirement 2: The system should do Y. (functional) - Acceptance Criteria 1 - Acceptance Criteria 2 ## User Stories As a user, I want to log in so that I can access my account. As an admin, I want to manage users so that I can control access. ## Use Cases ### Use Case: User Login Description: User logs into the system. Actors: User Preconditions: User has an account. Postconditions: User is logged in. `; const yamlContent = ` title: Test YAML Spec summary: This is a YAML summary. requirements: - id: req-yaml-1 description: YAML Requirement 1 type: functional priority: high userStories: - id: story-yaml-1 actor: YAML User action: do something benefit: get a benefit useCases: - id: uc-yaml-1 name: YAML Use Case description: A use case from YAML `; const plainTextContent = `Plain Text Spec This is a plain text summary. Requirement: System must be fast. - Criteria A - Criteria B `; describe('BMADService', () => { let bmadService: BMADService; beforeEach(() => { bmadService = new BMADService(); }); it('should be defined', () => { expect(bmadService).toBeDefined(); }); describe('parseSpecification', () => { it('should parse markdown content correctly', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: false, autoAssign: false, validate: false }, }); expect(response.spec).toBeDefined(); expect(response.spec.title).toBe('Test Markdown Spec'); expect(response.spec.summary).toBe('This is a summary.'); expect(response.spec.sections.length).toBeGreaterThan(0); expect(response.spec.requirements.length).toBe(2); expect(response.spec.userStories?.length).toBe(2); expect(response.spec.useCases?.length).toBe(1); expect(response.tasks).toEqual([]); expect(response.assignments).toEqual([]); expect(response.validation).toBeUndefined(); }); it('should parse YAML content correctly', async () => { const response = await bmadService.parseSpecification({ content: yamlContent, format: 'yaml', options: { generateTasks: false, autoAssign: false, validate: false }, }); expect(response.spec).toBeDefined(); expect(response.spec.title).toBe('Test YAML Spec'); expect(response.spec.summary).toBe('This is a YAML summary.'); expect(response.spec.requirements.length).toBe(1); expect(response.spec.userStories?.length).toBe(1); expect(response.spec.useCases?.length).toBe(1); }); it('should parse plain text content correctly', async () => { const response = await bmadService.parseSpecification({ content: plainTextContent, format: 'plain', options: { generateTasks: false, autoAssign: false, validate: false }, }); expect(response.spec).toBeDefined(); expect(response.spec.title).toBe('Plain Text Spec'); expect(response.spec.summary).toBe('This is a plain text summary.'); expect(response.spec.requirements.length).toBe(1); }); it('should generate tasks when generateTasks is true', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true, autoAssign: false, validate: false }, }); expect(response.tasks).toBeDefined(); expect(response.tasks.length).toBeGreaterThan(0); // Check that task has a title (flexible matching) expect(response.tasks[0].title).toBeDefined(); expect(response.tasks[0].title.length).toBeGreaterThan(0); }); it('should auto-assign tasks when autoAssign is true and tasks are generated', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true, autoAssign: true, validate: false }, }); expect(response.assignments).toBeDefined(); expect(response.assignments.length).toBeGreaterThan(0); expect(response.assignments[0].agentId).toBeDefined(); }); it('should validate tasks when validate is true and tasks are generated', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true, autoAssign: false, validate: true }, }); expect(response.validation).toBeDefined(); expect(response.validation?.length).toBeGreaterThan(0); expect(response.validation?.[0].passed).toBe(true); // Assuming default tasks pass basic validation }); it('should throw error for unsupported format', async () => { await expect(bmadService.parseSpecification({ content: 'some content', format: 'unsupported' as any, })).rejects.toThrow('Unsupported format'); }); it('should handle parsing errors gracefully', async () => { // Test with invalid format to trigger error handling await expect(bmadService.parseSpecification({ content: 'some content', format: 'invalid' as any, })).rejects.toThrow('Unsupported format'); }); }); describe('Public API methods', () => { it('should return tasks', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true }, }); const tasks = await bmadService.getTasks(); expect(tasks.length).toBe(response.tasks.length); }); it('should return agents', async () => { const agents = await bmadService.getAgents(); expect(agents.length).toBeGreaterThan(0); expect(agents[0].name).toBe('Full-Stack Developer'); }); it('should return assignments', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true, autoAssign: true }, }); const assignments = await bmadService.getAssignments(); expect(assignments.length).toBe(response.assignments.length); }); it('should update task status', async () => { const response = await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true }, }); const taskId = response.tasks[0].id; const updated = await bmadService.updateTaskStatus(taskId, 'completed'); expect(updated).toBe(true); const tasks = await bmadService.getTasks(); expect(tasks.find(t => t.id === taskId)?.status).toBe('completed'); }); it('should return false if task not found for status update', async () => { const updated = await bmadService.updateTaskStatus('non-existent-id', 'completed'); expect(updated).toBe(false); }); }); describe('Enhanced parsing with complex scenarios', () => { it('should handle markdown with mixed requirement formats', async () => { const mixedContent = `--- title: Mixed Requirements Spec --- # Requirements - REQ-001: System must support authentication (functional, high) - The system should handle 1000+ concurrent users (non-functional) ## User Stories As a user, I want to register so that I can access the system. ## Use Cases ### Login Process Description: User authentication flow Actors: User, System Preconditions: Valid credentials Postconditions: User authenticated`; const response = await bmadService.parseSpecification({ content: mixedContent, format: 'markdown', options: { generateTasks: true, autoAssign: true, validate: true } }); expect(response.spec.requirements.length).toBeGreaterThan(0); expect(response.spec.userStories?.length).toBeGreaterThan(0); expect(response.spec.useCases?.length).toBeGreaterThan(0); expect(response.tasks.length).toBeGreaterThan(0); expect(response.assignments.length).toBeGreaterThan(0); expect(response.validation).toBeDefined(); }); it('should handle YAML with nested structures', async () => { const nestedYamlContent = ` title: Complex YAML Spec requirements: - id: req-1 description: Primary requirement type: functional priority: high acceptanceCriteria: - criterion1 - criterion2 userStories: - id: story-1 actor: Admin action: manage users benefit: control access priority: high estimatedHours: 8`; const response = await bmadService.parseSpecification({ content: nestedYamlContent, format: 'yaml' }); expect(response.spec.requirements.length).toBe(1); expect(response.spec.requirements[0].id).toBe('req-1'); expect(response.spec.userStories?.length).toBe(1); expect(response.spec.userStories?.[0].actor).toBe('Admin'); }); it('should handle plain text with unstructured requirements', async () => { const unstructuredContent = ` Project Requirements Document The system needs to support user registration and login. Performance should handle 500 users simultaneously. Security requirements include data encryption. User Story: As an admin, I need to view user reports. Use Case: Generate monthly analytics reports.`; const response = await bmadService.parseSpecification({ content: unstructuredContent, format: 'plain', options: { generateTasks: false } }); expect(response.spec.requirements.length).toBeGreaterThan(0); expect(response.tasks.length).toBe(0); // generateTasks is false }); }); describe('Task management edge cases', () => { beforeEach(async () => { // Generate some tasks first await bmadService.parseSpecification({ content: markdownContent, format: 'markdown', options: { generateTasks: true, autoAssign: true } }); }); it('should handle multiple status updates', async () => { const tasks = await bmadService.getTasks(); const taskId = tasks[0].id; let updated = await bmadService.updateTaskStatus(taskId, 'in-progress'); expect(updated).toBe(true); updated = await bmadService.updateTaskStatus(taskId, 'completed'); expect(updated).toBe(true); const updatedTasks = await bmadService.getTasks(); const updatedTask = updatedTasks.find(t => t.id === taskId); expect(updatedTask?.status).toBe('completed'); }); it('should maintain agent availability during assignments', async () => { const initialAgents = await bmadService.getAgents(); const availableAgents = initialAgents.filter(a => a.availability === 'available'); expect(availableAgents.length).toBeGreaterThan(0); const assignments = await bmadService.getAssignments(); expect(assignments.length).toBeGreaterThan(0); // Agents should still be available after assignment in this simple implementation const agentsAfter = await bmadService.getAgents(); const stillAvailable = agentsAfter.filter(a => a.availability === 'available'); expect(stillAvailable.length).toBeGreaterThanOrEqual(0); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Ghostseller/CastPlan_mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server