@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
217 lines (172 loc) • 8.64 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { MethodologyManager } from '../methodology-manager.js';
import { ConfigManager } from '../../../config/config-manager.js';
import { AGILE_TOOLS, KANBAN_TOOLS, COMMON_TOOLS } from '../types.js';
vi.mock('../../../config/config-manager.js');
describe('MethodologyManager', () => {
let methodologyManager: MethodologyManager;
let mockConfigManager: vi.Mocked<ConfigManager>;
beforeEach(async () => {
mockConfigManager = new ConfigManager() as vi.Mocked<ConfigManager>;
mockConfigManager.getStorageManager = vi.fn().mockReturnValue({
loadData: vi.fn().mockResolvedValue(null),
saveData: vi.fn().mockResolvedValue(undefined),
ensureStorageDirectories: vi.fn().mockResolvedValue(undefined)
});
methodologyManager = new MethodologyManager(mockConfigManager);
await methodologyManager.init();
});
describe('setMethodology', () => {
it('should set agile methodology with correct tools', async () => {
const methodology = await methodologyManager.setMethodology('agile', 'user123');
expect(methodology.type).toBe('agile');
expect(methodology.allowedTools).toContain(...COMMON_TOOLS);
expect(methodology.allowedTools).toContain(...AGILE_TOOLS);
expect(methodology.disallowedTools).toEqual(KANBAN_TOOLS);
});
it('should set kanban methodology with correct tools', async () => {
const methodology = await methodologyManager.setMethodology('kanban', 'user123');
expect(methodology.type).toBe('kanban');
expect(methodology.allowedTools).toContain(...COMMON_TOOLS);
expect(methodology.allowedTools).toContain(...KANBAN_TOOLS);
expect(methodology.disallowedTools).toEqual(AGILE_TOOLS);
});
it('should record migration when switching methodologies', async () => {
// Set initial methodology
await methodologyManager.setMethodology('agile', 'user123');
// Switch to kanban
const methodology = await methodologyManager.setMethodology('kanban', 'user123');
expect(methodology.migrationHistory).toHaveLength(1);
const migration = methodology.migrationHistory[0];
expect(migration.fromType).toBe('agile');
expect(migration.toType).toBe('kanban');
expect(migration.migratedBy).toBe('user123');
expect(migration.dataLost).toContain('Sprint information will be lost');
});
it('should emit methodology-changed event', async () => {
let eventEmitted = false;
methodologyManager.once('methodology-changed', (methodology) => {
eventEmitted = true;
expect(methodology.type).toBe('agile');
});
await methodologyManager.setMethodology('agile', 'user123');
expect(eventEmitted).toBe(true);
});
});
describe('lockMethodology', () => {
it('should lock methodology preventing changes', async () => {
await methodologyManager.setMethodology('agile', 'user123');
await methodologyManager.lockMethodology('admin');
// Try to change methodology
await expect(
methodologyManager.setMethodology('kanban', 'user456')
).rejects.toThrow('Methodology is locked');
});
it('should record who locked the methodology', async () => {
await methodologyManager.setMethodology('agile', 'user123');
await methodologyManager.lockMethodology('admin');
const methodology = methodologyManager.getCurrentMethodology();
expect(methodology?.lockedBy).toBe('admin');
expect(methodology?.lockedAt).toBeDefined();
});
it('should not allow locking twice', async () => {
await methodologyManager.setMethodology('agile', 'user123');
await methodologyManager.lockMethodology('admin');
await expect(
methodologyManager.lockMethodology('admin2')
).rejects.toThrow('Methodology is already locked');
});
it('should emit methodology-locked event', async () => {
await methodologyManager.setMethodology('agile', 'user123');
let eventEmitted = false;
methodologyManager.once('methodology-locked', (methodology) => {
eventEmitted = true;
expect(methodology.type).toBe('agile');
});
await methodologyManager.lockMethodology('admin');
expect(eventEmitted).toBe(true);
});
});
describe('isToolAllowed', () => {
it('should allow common tools in both methodologies', async () => {
await methodologyManager.setMethodology('agile', 'user123');
for (const tool of COMMON_TOOLS) {
expect(methodologyManager.isToolAllowed(tool)).toBe(true);
}
});
it('should allow agile tools only in agile methodology', async () => {
await methodologyManager.setMethodology('agile', 'user123');
for (const tool of AGILE_TOOLS) {
expect(methodologyManager.isToolAllowed(tool)).toBe(true);
}
for (const tool of KANBAN_TOOLS) {
expect(methodologyManager.isToolAllowed(tool)).toBe(false);
}
});
it('should allow kanban tools only in kanban methodology', async () => {
await methodologyManager.setMethodology('kanban', 'user123');
for (const tool of KANBAN_TOOLS) {
expect(methodologyManager.isToolAllowed(tool)).toBe(true);
}
for (const tool of AGILE_TOOLS) {
expect(methodologyManager.isToolAllowed(tool)).toBe(false);
}
});
it('should allow all tools when no methodology is set', () => {
expect(methodologyManager.isToolAllowed('any-tool')).toBe(true);
});
});
describe('warnAboutConflict', () => {
it('should emit warning for conflicting tool usage', async () => {
await methodologyManager.setMethodology('agile', 'user123');
let warningEmitted = false;
methodologyManager.once('methodology-conflict-warning', (warning) => {
warningEmitted = true;
expect(warning.toolName).toBe('create_board');
expect(warning.currentMethodology).toBe('agile');
});
const shouldWarn = await methodologyManager.warnAboutConflict('create_board');
expect(shouldWarn).toBe(true);
expect(warningEmitted).toBe(true);
});
it('should throttle warnings to once per hour', async () => {
await methodologyManager.setMethodology('agile', 'user123');
const firstWarn = await methodologyManager.warnAboutConflict('create_board');
expect(firstWarn).toBe(true);
const secondWarn = await methodologyManager.warnAboutConflict('create_board');
expect(secondWarn).toBe(false);
});
});
describe('suggestMigrationPath', () => {
it('should suggest steps for agile to kanban migration', async () => {
const suggestions = await methodologyManager.suggestMigrationPath('agile', 'kanban');
expect(suggestions).toContain('1. Complete or cancel active sprints');
expect(suggestions).toContain('2. Convert stories to tasks');
expect(suggestions).toContain('3. Set up WIP limits for columns');
});
it('should suggest steps for kanban to agile migration', async () => {
const suggestions = await methodologyManager.suggestMigrationPath('kanban', 'agile');
expect(suggestions).toContain('1. Group related tasks into stories');
expect(suggestions).toContain('2. Estimate story points for all items');
expect(suggestions).toContain('3. Create initial sprint backlog');
});
});
describe('Default configurations', () => {
it('should set appropriate defaults for agile', async () => {
const methodology = await methodologyManager.setMethodology('agile', 'user123');
expect(methodology.configuration.sprintDuration).toBe(14);
expect(methodology.configuration.velocityTracking).toBe(true);
expect(methodology.configuration.storyPointScale).toEqual([1, 2, 3, 5, 8, 13, 21]);
expect(methodology.configuration.requiredFields).toContain('storyPoints');
expect(methodology.configuration.requiredFields).toContain('acceptanceCriteria');
});
it('should set appropriate defaults for kanban', async () => {
const methodology = await methodologyManager.setMethodology('kanban', 'user123');
expect(methodology.configuration.wip_limits).toBeDefined();
expect(methodology.configuration.wip_limits['In Progress']).toBe(3);
expect(methodology.configuration.cycleTimeTracking).toBe(true);
expect(methodology.configuration.leadTimeTracking).toBe(true);
expect(methodology.configuration.requiredFields).not.toContain('storyPoints');
});
});
});