@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
452 lines (389 loc) • 14.5 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { RoadmapWizard } from '../roadmap-wizard.js';
import { RoadmapManager } from '../roadmap-manager.js';
import { PrioritizationEngine } from '../prioritization-engine.js';
import { ConfigManager } from '../../../config/config-manager.js';
// Mock the dependencies
vi.mock('../roadmap-manager.js');
vi.mock('../prioritization-engine.js');
describe('RoadmapWizard', () => {
let wizard: RoadmapWizard;
let mockRoadmapManager: any;
let mockPrioritizationEngine: any;
const sessionId = 'test-session-123';
beforeEach(() => {
// Create mock instances
mockRoadmapManager = {
createRoadmap: vi.fn().mockResolvedValue({
id: 'roadmap-123',
name: 'Test Roadmap',
vision: 'Test Vision',
timeHorizon: 'annual',
owner: 'Test Owner',
stakeholders: [],
status: 'draft',
themes: [],
milestones: [],
releases: [],
createdAt: new Date(),
updatedAt: new Date(),
metrics: {
featuresPlanned: 0,
featuresCompleted: 0,
initiativesActive: 0,
velocityTrend: 'stable',
onTimeDelivery: 100,
valueDelivered: 0
}
}),
addTheme: vi.fn().mockResolvedValue({
id: 'theme-123',
name: 'Test Theme',
description: 'Test Description',
objectives: ['Objective 1'],
priority: 'must-have',
timeframe: {
startQuarter: 'Q1 2024',
endQuarter: 'Q4 2024'
},
status: 'planned',
initiatives: [],
metrics: {
initiativesTotal: 0,
initiativesCompleted: 0,
featuresTotal: 0,
featuresCompleted: 0,
progressPercentage: 0,
valueScore: 0
}
}),
createInitiative: vi.fn().mockImplementation((options) => Promise.resolve({
id: 'init-123',
themeId: options.themeId,
title: options.title,
description: options.description,
features: [],
epicIds: [],
value: options.estimatedValue,
effort: options.estimatedEffort,
risks: options.risks || [],
dependencies: [],
status: 'ideation'
})),
addFeature: vi.fn().mockImplementation((options) => Promise.resolve({
id: 'feature-123',
initiativeId: options.initiativeId,
name: options.name,
description: options.description,
userStories: [],
priority: 0,
businessValue: options.businessValue,
technicalComplexity: options.technicalComplexity,
status: 'proposed'
})),
getRoadmap: vi.fn().mockResolvedValue({
id: 'roadmap-123',
name: 'Test Roadmap',
themes: []
}),
getThemeDetails: vi.fn().mockResolvedValue({
theme: {
id: 'theme-123',
name: 'Test Theme'
},
initiatives: [],
features: []
})
};
mockPrioritizationEngine = {};
wizard = new RoadmapWizard(mockRoadmapManager, mockPrioritizationEngine);
});
describe('Roadmap Wizard', () => {
it('should start roadmap wizard and return step 1 instructions', async () => {
const result = await wizard.startRoadmapWizard(sessionId);
expect(result).toContain('Product Roadmap Creation Wizard');
expect(result).toContain('Step 1/4: Basic Information');
expect(result).toContain('Product/Roadmap Name');
expect(result).toContain('Vision Statement');
expect(result).toContain('Time Horizon');
expect(result).toContain('Your Name/Role');
});
it('should progress through wizard steps', async () => {
// Start wizard
await wizard.startRoadmapWizard(sessionId);
// Step 1 -> Step 2
const step2Result = await wizard.continueRoadmapWizard(sessionId, {
name: 'My Product',
vision: 'Amazing product vision',
timeHorizon: 'annual',
owner: 'John Doe'
});
expect(step2Result).toContain('Step 2/4: Key Stakeholders');
expect(step2Result).toContain('stakeholders');
// Step 2 -> Step 3
const step3Result = await wizard.continueRoadmapWizard(sessionId, {
stakeholders: ['Jane Smith', 'Bob Johnson']
});
expect(step3Result).toContain('Step 3/4: Strategic Context');
expect(step3Result).toContain('Primary Goal');
expect(step3Result).toContain('Key Challenges');
// Step 3 -> Step 4
const step4Result = await wizard.continueRoadmapWizard(sessionId, {
goal: 'Customer satisfaction',
challenges: ['High support volume', 'Poor UX'],
metrics: ['NPS > 50', 'Support tickets -50%']
});
expect(step4Result).toContain('Step 4/4: Initial Themes');
expect(step4Result).toContain('strategic themes');
});
it('should complete wizard and create roadmap', async () => {
// Progress through all steps
await wizard.startRoadmapWizard(sessionId);
await wizard.continueRoadmapWizard(sessionId, {
name: 'My Product',
vision: 'Amazing product vision',
timeHorizon: 'annual',
owner: 'John Doe'
});
await wizard.continueRoadmapWizard(sessionId, {
stakeholders: ['Jane Smith']
});
await wizard.continueRoadmapWizard(sessionId, {
goal: 'Customer satisfaction',
challenges: ['High support volume'],
metrics: ['NPS > 50']
});
// Complete wizard with themes
const result = await wizard.continueRoadmapWizard(sessionId, {
themes: [
{
name: 'Self-Service',
description: 'Build self-service capabilities',
priority: 'must-have',
startQuarter: 'Q1 2024',
endQuarter: 'Q4 2024'
}
]
});
expect(result).toContain('Roadmap Created Successfully!');
expect(result).toContain('roadmap-123');
expect(mockRoadmapManager.createRoadmap).toHaveBeenCalledWith({
name: 'My Product',
vision: 'Amazing product vision',
timeHorizon: 'annual',
owner: 'John Doe',
stakeholders: ['Jane Smith']
});
expect(mockRoadmapManager.addTheme).toHaveBeenCalled();
});
it('should handle "none" for stakeholders', async () => {
await wizard.startRoadmapWizard(sessionId);
await wizard.continueRoadmapWizard(sessionId, {
name: 'My Product',
vision: 'Amazing product vision',
timeHorizon: 'annual',
owner: 'John Doe'
});
const result = await wizard.continueRoadmapWizard(sessionId, {
stakeholders: 'none'
});
expect(result).toContain('Step 3/4');
});
it('should return error for invalid session', async () => {
const result = await wizard.continueRoadmapWizard('invalid-session', {});
expect(result).toContain('No active roadmap wizard found');
});
});
describe('Initiative Wizard', () => {
it('should start initiative wizard', async () => {
const result = await wizard.startInitiativeWizard(
sessionId,
'roadmap-123',
'theme-123'
);
expect(result).toContain('Initiative Creation Wizard');
expect(result).toContain('Step 1/5: Initiative Overview');
expect(result).toContain('Title');
expect(result).toContain('Description');
expect(result).toContain('Key Outcomes');
});
it('should progress through initiative wizard steps', async () => {
// Start wizard
await wizard.startInitiativeWizard(sessionId, 'roadmap-123', 'theme-123');
// Step 1 -> Step 2
const step2Result = await wizard.continueInitiativeWizard(sessionId, {
title: 'Smart Search',
description: 'AI-powered search implementation',
outcomes: ['Instant answers', 'Reduced support']
});
expect(step2Result).toContain('Step 2/5: Business Value Assessment');
expect(step2Result).toContain('User Impact');
expect(step2Result).toContain('Revenue Impact');
// Step 2 -> Step 3
const step3Result = await wizard.continueInitiativeWizard(sessionId, {
userImpact: 'high',
revenueImpact: 500000,
costSavings: 200000,
strategicValue: 8
});
expect(step3Result).toContain('Step 3/5: Effort Estimation');
expect(step3Result).toContain('Development Weeks');
// Step 3 -> Step 4
const step4Result = await wizard.continueInitiativeWizard(sessionId, {
developmentWeeks: 12,
designWeeks: 4,
qaWeeks: 3,
confidence: 'medium'
});
expect(step4Result).toContain('Step 4/5: Risk Assessment');
// Step 4 -> Step 5
const step5Result = await wizard.continueInitiativeWizard(sessionId, {
risks: [{
description: 'Technical complexity',
likelihood: 'medium',
impact: 'high',
mitigation: 'Technical spike'
}]
});
expect(step5Result).toContain('Step 5/5: Initial Features');
});
it('should complete initiative wizard and create initiative', async () => {
await wizard.startInitiativeWizard(sessionId, 'roadmap-123', 'theme-123');
// Progress through all steps
await wizard.continueInitiativeWizard(sessionId, {
title: 'Smart Search',
description: 'AI-powered search',
outcomes: ['Instant answers']
});
await wizard.continueInitiativeWizard(sessionId, {
userImpact: 'high',
revenueImpact: 500000,
costSavings: 200000,
strategicValue: 8
});
await wizard.continueInitiativeWizard(sessionId, {
developmentWeeks: 12,
designWeeks: 4,
qaWeeks: 3,
confidence: 'medium'
});
await wizard.continueInitiativeWizard(sessionId, {
risks: 'none'
});
const result = await wizard.continueInitiativeWizard(sessionId, {
features: [
{
name: 'Natural Language Search',
description: 'NLP search capability',
value: 85,
complexity: 'high'
}
]
});
expect(result).toContain('Initiative Created Successfully!');
expect(result).toContain('Smart Search');
expect(mockRoadmapManager.createInitiative).toHaveBeenCalled();
expect(mockRoadmapManager.addFeature).toHaveBeenCalled();
});
it('should handle invalid roadmap or theme', async () => {
mockRoadmapManager.getRoadmap.mockResolvedValueOnce(null);
const result = await wizard.startInitiativeWizard(
sessionId,
'invalid-roadmap',
'theme-123'
);
expect(result).toContain('Invalid roadmap or theme ID');
});
});
describe('Quick Feature Wizard', () => {
it('should start quick feature wizard', async () => {
const result = await wizard.startQuickFeatureWizard(
sessionId,
'roadmap-123',
'init-123'
);
expect(result).toContain('Quick Feature Addition');
expect(result).toContain('Name');
expect(result).toContain('Description');
expect(result).toContain('Value');
expect(result).toContain('Complexity');
expect(result).toContain('Success Metrics');
});
it('should complete quick feature wizard', async () => {
await wizard.startQuickFeatureWizard(sessionId, 'roadmap-123', 'init-123');
const result = await wizard.continueQuickFeatureWizard(sessionId, {
name: 'Real-time Notifications',
description: 'Push notifications for updates',
value: 75,
complexity: 'medium',
metrics: ['User engagement +30%', 'Response time < 2s']
});
expect(result).toContain('Feature Added Successfully!');
expect(result).toContain('Real-time Notifications');
expect(mockRoadmapManager.addFeature).toHaveBeenCalledWith({
roadmapId: 'roadmap-123',
initiativeId: 'init-123',
name: 'Real-time Notifications',
description: 'Push notifications for updates',
businessValue: {
score: 75,
rationale: 'Added via quick wizard',
metrics: ['User engagement +30%', 'Response time < 2s']
},
technicalComplexity: 'medium',
targetRelease: undefined
});
});
});
describe('Wizard State Management', () => {
it('should track active wizards', async () => {
await wizard.startRoadmapWizard('session-1');
await wizard.startInitiativeWizard('session-2', 'roadmap-123', 'theme-123');
const activeWizards = wizard.getActiveWizards();
expect(activeWizards).toContain('session-1');
expect(activeWizards).toContain('session-2');
});
it('should cancel wizard', async () => {
await wizard.startRoadmapWizard(sessionId);
const cancelled = wizard.cancelWizard(sessionId);
expect(cancelled).toBe(true);
const result = await wizard.continueRoadmapWizard(sessionId, {});
expect(result).toContain('No active roadmap wizard found');
});
it('should not cancel non-existent wizard', () => {
const cancelled = wizard.cancelWizard('non-existent');
expect(cancelled).toBe(false);
});
});
describe('Helper Methods', () => {
it('should derive objectives from context', async () => {
await wizard.startRoadmapWizard(sessionId);
// Progress to step 4
await wizard.continueRoadmapWizard(sessionId, {
name: 'My Product',
vision: 'Vision',
timeHorizon: 'annual',
owner: 'Owner'
});
await wizard.continueRoadmapWizard(sessionId, {
stakeholders: []
});
await wizard.continueRoadmapWizard(sessionId, {
goal: 'Revenue growth',
challenges: ['Low conversion'],
metrics: ['Revenue +50%']
});
await wizard.continueRoadmapWizard(sessionId, {
themes: [{
name: 'Conversion Optimization',
description: 'Improve conversion rates',
priority: 'must-have'
}]
});
// Check that addTheme was called with derived objectives
const addThemeCall = mockRoadmapManager.addTheme.mock.calls[0][0];
expect(addThemeCall.objectives).toContain('Drive Revenue growth through conversion optimization');
expect(addThemeCall.objectives).toContain('Address key challenge: Low conversion');
});
});
});