UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

389 lines (344 loc) 12.4 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { RoadmapManager } from '../roadmap-manager.js'; import { ConfigManager } from '../../../config/config-manager.js'; import { CreateRoadmapOptions, AddThemeOptions, CreateInitiativeOptions, AddFeatureOptions, ProductRoadmap, RoadmapTheme, Initiative, Feature } from '../types.js'; // Mock the storage manager vi.mock('../../../storage/storage-manager.js', () => ({ StorageManager: vi.fn().mockImplementation(() => ({ getStorageLocation: vi.fn().mockResolvedValue({ data: '/tmp/test-atlas' }) })) })); // Mock fs operations vi.mock('fs', () => ({ promises: { access: vi.fn().mockRejectedValue(new Error('File not found')), mkdir: vi.fn().mockResolvedValue(undefined), readFile: vi.fn().mockRejectedValue(new Error('File not found')), writeFile: vi.fn().mockResolvedValue(undefined) } })); describe('RoadmapManager', () => { let roadmapManager: RoadmapManager; let configManager: ConfigManager; beforeEach(async () => { configManager = new ConfigManager(); roadmapManager = new RoadmapManager(configManager); await roadmapManager.initialize(); }); describe('createRoadmap', () => { it('should create a new product roadmap', async () => { const options: CreateRoadmapOptions = { name: 'Test Product Roadmap', vision: 'To create an amazing product that users love', timeHorizon: 'annual', owner: 'John Doe', stakeholders: ['Jane Smith', 'Bob Johnson'] }; const roadmap = await roadmapManager.createRoadmap(options); expect(roadmap).toBeDefined(); expect(roadmap.name).toBe(options.name); expect(roadmap.vision).toBe(options.vision); expect(roadmap.timeHorizon).toBe(options.timeHorizon); expect(roadmap.owner).toBe(options.owner); expect(roadmap.stakeholders).toEqual(options.stakeholders); expect(roadmap.status).toBe('draft'); expect(roadmap.themes).toEqual([]); expect(roadmap.milestones).toEqual([]); expect(roadmap.releases).toEqual([]); expect(roadmap.metrics.featuresPlanned).toBe(0); expect(roadmap.metrics.featuresCompleted).toBe(0); }); it('should generate unique IDs for roadmaps', async () => { const options: CreateRoadmapOptions = { name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'quarterly', owner: 'Test Owner' }; const roadmap1 = await roadmapManager.createRoadmap(options); const roadmap2 = await roadmapManager.createRoadmap(options); expect(roadmap1.id).not.toBe(roadmap2.id); }); }); describe('addTheme', () => { let roadmap: ProductRoadmap; beforeEach(async () => { roadmap = await roadmapManager.createRoadmap({ name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'annual', owner: 'Test Owner' }); }); it('should add a theme to a roadmap', async () => { const themeOptions: AddThemeOptions = { roadmapId: roadmap.id, name: 'User Experience Enhancement', description: 'Improve overall user experience across the platform', objectives: [ 'Reduce user friction', 'Improve onboarding flow', 'Enhance mobile experience' ], priority: 'must-have', startQuarter: 'Q1 2024', endQuarter: 'Q4 2024' }; const theme = await roadmapManager.addTheme(themeOptions); expect(theme).toBeDefined(); expect(theme.name).toBe(themeOptions.name); expect(theme.description).toBe(themeOptions.description); expect(theme.objectives).toEqual(themeOptions.objectives); expect(theme.priority).toBe(themeOptions.priority); expect(theme.timeframe.startQuarter).toBe(themeOptions.startQuarter); expect(theme.timeframe.endQuarter).toBe(themeOptions.endQuarter); expect(theme.status).toBe('planned'); expect(theme.initiatives).toEqual([]); expect(theme.metrics.initiativesTotal).toBe(0); }); it('should throw error for non-existent roadmap', async () => { const themeOptions: AddThemeOptions = { roadmapId: 'non-existent-roadmap', name: 'Test Theme', description: 'Test description', objectives: ['Test objective'], priority: 'nice-to-have', startQuarter: 'Q1 2024', endQuarter: 'Q2 2024' }; await expect(roadmapManager.addTheme(themeOptions)) .rejects.toThrow('Roadmap non-existent-roadmap not found'); }); }); describe('createInitiative', () => { let roadmap: ProductRoadmap; let theme: RoadmapTheme; beforeEach(async () => { roadmap = await roadmapManager.createRoadmap({ name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'annual', owner: 'Test Owner' }); theme = await roadmapManager.addTheme({ roadmapId: roadmap.id, name: 'Test Theme', description: 'Test description', objectives: ['Test objective'], priority: 'must-have', startQuarter: 'Q1 2024', endQuarter: 'Q4 2024' }); }); it('should create an initiative within a theme', async () => { const initiativeOptions: CreateInitiativeOptions = { roadmapId: roadmap.id, themeId: theme.id, title: 'Implement Smart Search', description: 'Build an AI-powered search system', estimatedValue: { userImpact: 'high', revenueImpact: 500000, costSavings: 200000, strategicValue: 8, customerSatisfaction: 15 }, estimatedEffort: { developmentWeeks: 12, designWeeks: 4, qaWeeks: 3, confidence: 'medium' }, risks: [{ id: 'risk-1', description: 'Technical complexity', likelihood: 'medium', impact: 'high', mitigation: 'Conduct technical spike' }] }; const initiative = await roadmapManager.createInitiative(initiativeOptions); expect(initiative).toBeDefined(); expect(initiative.themeId).toBe(theme.id); expect(initiative.title).toBe(initiativeOptions.title); expect(initiative.description).toBe(initiativeOptions.description); expect(initiative.value).toEqual(initiativeOptions.estimatedValue); expect(initiative.effort).toEqual(initiativeOptions.estimatedEffort); expect(initiative.risks).toHaveLength(1); expect(initiative.status).toBe('ideation'); expect(initiative.features).toEqual([]); expect(initiative.epicIds).toEqual([]); }); }); describe('addFeature', () => { let roadmap: ProductRoadmap; let theme: RoadmapTheme; let initiative: Initiative; beforeEach(async () => { roadmap = await roadmapManager.createRoadmap({ name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'annual', owner: 'Test Owner' }); theme = await roadmapManager.addTheme({ roadmapId: roadmap.id, name: 'Test Theme', description: 'Test description', objectives: ['Test objective'], priority: 'must-have', startQuarter: 'Q1 2024', endQuarter: 'Q4 2024' }); initiative = await roadmapManager.createInitiative({ roadmapId: roadmap.id, themeId: theme.id, title: 'Test Initiative', description: 'Test initiative description', estimatedValue: { userImpact: 'medium', revenueImpact: 100000, costSavings: 50000, strategicValue: 7, customerSatisfaction: 10 }, estimatedEffort: { developmentWeeks: 8, designWeeks: 2, qaWeeks: 2, confidence: 'high' } }); }); it('should add a feature to an initiative', async () => { const featureOptions: AddFeatureOptions = { roadmapId: roadmap.id, initiativeId: initiative.id, name: 'Natural Language Search', description: 'Allow users to search using natural language queries', businessValue: { score: 85, rationale: 'High user demand and competitive advantage', metrics: ['Search success rate > 90%', 'User satisfaction > 4.5/5'] }, technicalComplexity: 'high', targetRelease: 'v2.0' }; const feature = await roadmapManager.addFeature(featureOptions); expect(feature).toBeDefined(); expect(feature.initiativeId).toBe(initiative.id); expect(feature.name).toBe(featureOptions.name); expect(feature.description).toBe(featureOptions.description); expect(feature.businessValue).toEqual(featureOptions.businessValue); expect(feature.technicalComplexity).toBe(featureOptions.technicalComplexity); expect(feature.targetRelease).toBe(featureOptions.targetRelease); expect(feature.status).toBe('proposed'); expect(feature.priority).toBe(0); }); }); describe('updateFeatureStatus', () => { let feature: Feature; beforeEach(async () => { const roadmap = await roadmapManager.createRoadmap({ name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'annual', owner: 'Test Owner' }); const theme = await roadmapManager.addTheme({ roadmapId: roadmap.id, name: 'Test Theme', description: 'Test description', objectives: ['Test objective'], priority: 'must-have', startQuarter: 'Q1 2024', endQuarter: 'Q4 2024' }); const initiative = await roadmapManager.createInitiative({ roadmapId: roadmap.id, themeId: theme.id, title: 'Test Initiative', description: 'Test initiative', estimatedValue: { userImpact: 'medium', revenueImpact: 100000, costSavings: 50000, strategicValue: 7, customerSatisfaction: 10 }, estimatedEffort: { developmentWeeks: 8, designWeeks: 2, qaWeeks: 2, confidence: 'high' } }); feature = await roadmapManager.addFeature({ roadmapId: roadmap.id, initiativeId: initiative.id, name: 'Test Feature', description: 'Test feature description', businessValue: { score: 75, rationale: 'Test rationale', metrics: ['Test metric'] }, technicalComplexity: 'medium' }); }); it('should update feature status', async () => { const updatedFeature = await roadmapManager.updateFeatureStatus( feature.id, 'in-progress' ); expect(updatedFeature.status).toBe('in-progress'); }); it('should throw error for non-existent feature', async () => { await expect(roadmapManager.updateFeatureStatus('non-existent', 'completed')) .rejects.toThrow('Feature non-existent not found'); }); }); describe('getRoadmapHealth', () => { let roadmap: ProductRoadmap; beforeEach(async () => { roadmap = await roadmapManager.createRoadmap({ name: 'Test Roadmap', vision: 'Test vision', timeHorizon: 'annual', owner: 'Test Owner' }); }); it('should return health assessment for a roadmap', async () => { const health = await roadmapManager.getRoadmapHealth(roadmap.id); expect(health).toBeDefined(); expect(health.health).toMatch(/excellent|good|at-risk|critical/); expect(health.metrics).toBeDefined(); expect(health.risks).toBeInstanceOf(Array); expect(health.recommendations).toBeInstanceOf(Array); }); it('should identify risks for low on-time delivery', async () => { // This would require mocking the roadmap metrics // For now, we just test the structure const health = await roadmapManager.getRoadmapHealth(roadmap.id); expect(health.metrics.onTimeDelivery).toBeDefined(); expect(health.metrics.velocityTrend).toBeDefined(); }); }); describe('getThemeDetails', () => { it('should return null for non-existent theme', async () => { const details = await roadmapManager.getThemeDetails('non-existent'); expect(details).toBeNull(); }); }); });