UNPKG

@boundless-oss/atlas

Version:

Atlas - MCP Server for comprehensive startup project management

372 lines (330 loc) 12 kB
import { describe, it, expect, beforeEach, vi } from 'vitest'; import { setupAgileManagementTools } from '../tools.js'; import { SqliteManager } from '../../../storage/sqlite-manager.js'; // Mock SqliteManager vi.mock('../../../storage/sqlite-manager.js', () => ({ SqliteManager: { getInstance: vi.fn() } })); describe('Agile Management Tools', () => { let tools: any; let mockDb: vi.Mocked<SqliteManager>; beforeEach(async () => { vi.clearAllMocks(); // Mock database operations mockDb = { run: vi.fn().mockResolvedValue({ success: true, changes: 1 }), get: vi.fn().mockResolvedValue({ success: true, data: null }), query: vi.fn().mockResolvedValue({ success: true, data: [] }), transaction: vi.fn().mockImplementation(async (callback) => { return await callback(mockDb); }) } as any; vi.mocked(SqliteManager.getInstance).mockReturnValue(mockDb); // Setup tools const toolRegistration = await setupAgileManagementTools(); tools = {}; for (const tool of toolRegistration.tools) { tools[tool.name] = tool; } }); const createContext = (toolName: string) => ({ toolName, requestId: 'test-req-1', projectId: 'test-project', userId: 'test-user', timestamp: Date.now(), db: mockDb }); describe('create_agile_sprint', () => { it('should create a new sprint with default values', async () => { const input = { name: 'Sprint 1', goal: 'Implement user authentication', duration: 14, }; const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.sprint).toBeDefined(); expect(result.data.sprint.name).toBe('Sprint 1'); expect(result.data.sprint.goal).toBe('Implement user authentication'); expect(result.data.sprint.duration).toBe(14); expect(result.data.sprint.status).toBe('active'); expect(result.data.sprint.id).toMatch(/^[a-zA-Z0-9-]+$/); expect(mockDb.run).toHaveBeenCalled(); }); it('should create sprint with team members', async () => { const input = { name: 'Sprint 2', goal: 'Build dashboard', duration: 10, team: ['alice@example.com', 'bob@example.com'], }; const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.sprint).toBeDefined(); expect(result.data.sprint.team).toEqual(['alice@example.com', 'bob@example.com']); }); }); describe('create_user_story', () => { it('should create a new story', async () => { const input = { title: 'User login functionality', description: 'As a user, I want to log in', priority: 'high' as const, storyPoints: 5 }; const result = await tools.create_user_story.execute(input, createContext('create_user_story')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.story).toBeDefined(); expect(result.data.story.title).toBe('User login functionality'); expect(result.data.story.description).toBe('As a user, I want to log in'); expect(result.data.story.priority).toBe('high'); expect(result.data.story.storyPoints).toBe(5); expect(result.data.story.id).toMatch(/^[a-zA-Z0-9-]+$/); expect(mockDb.run).toHaveBeenCalled(); }); }); describe('update_story_status', () => { it('should update story status', async () => { // Mock story check mockDb.get.mockResolvedValueOnce({ success: true, data: { id: 'STORY-0001', title: 'Test Story', status: 'todo' } }); const result = await tools.update_story_status.execute({ storyId: 'STORY-0001', status: 'in_progress', notes: 'Started working on this', skipWorkflowValidation: true // Skip validation for this test }, createContext('update_story_status')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.oldStatus).toBe('todo'); expect(result.data.newStatus).toBe('in_progress'); expect(mockDb.run).toHaveBeenCalled(); }); }); describe('get_sprint_stats', () => { it('should get sprint statistics', async () => { // Mock sprint data mockDb.get.mockResolvedValueOnce({ success: true, data: { id: 'SPRINT-0001', name: 'Sprint 1', goal: 'Test goal', status: 'active', duration_days: 14, start_date: Date.now(), end_date: Date.now() + (14 * 24 * 60 * 60 * 1000), team_members: '[]' } }); // Mock stats data mockDb.query.mockResolvedValueOnce({ success: true, data: [{ total_stories: 5, completed_stories: 2, in_progress_stories: 1, review_stories: 1, todo_stories: 1, total_points: 20, completed_points: 8 }] }); const result = await tools.get_sprint_stats.execute({ sprintId: 'SPRINT-0001' }, createContext('get_sprint_stats')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.sprint).toBeDefined(); expect(result.data.sprint.name).toBe('Sprint 1'); expect(result.data.progress).toBeDefined(); expect(result.data.progress.stories).toBeDefined(); expect(result.data.progress.stories.total).toBe(5); expect(mockDb.get).toHaveBeenCalled(); expect(mockDb.query).toHaveBeenCalled(); }); }); describe('list_stories', () => { it('should list stories with no filters', async () => { mockDb.query.mockResolvedValueOnce({ success: true, data: [ { id: 'STORY-0001', title: 'Test Story', description: 'Test description', status: 'todo', priority: 'medium', story_points: 3, acceptance_criteria: '[]', tags: '[]', assigned_to: null, sprint_id: null, epic_id: null, created_at: Date.now(), updated_at: Date.now() } ] }); const result = await tools.list_stories.execute({}, createContext('list_stories')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.stories).toBeDefined(); expect(result.data.stories).toHaveLength(1); expect(result.data.stories[0].title).toBe('Test Story'); expect(mockDb.query).toHaveBeenCalled(); }); }); describe('add_story_to_sprint', () => { it('should add story to sprint', async () => { // Mock story check mockDb.get.mockResolvedValueOnce({ success: true, data: { id: 'STORY-0001', title: 'Test Story', sprint_id: null } }); // Mock sprint check mockDb.get.mockResolvedValueOnce({ success: true, data: { id: 'SPRINT-0001', name: 'Sprint 1', status: 'planning' } }); const result = await tools.add_story_to_sprint.execute({ storyId: 'STORY-0001', sprintId: 'SPRINT-0001' }, createContext('add_story_to_sprint')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.message).toContain('Test Story'); expect(result.data.message).toContain('Sprint 1'); expect(mockDb.run).toHaveBeenCalled(); }); }); describe('list_stories_with_details', () => { it('should list stories with documentation fields', async () => { mockDb.query.mockResolvedValueOnce({ success: true, data: [ { id: 'STORY-0001', title: 'Test Story with Docs', description: 'Test description', status: 'in_progress', priority: 'high', story_points: 5, implementation_doc_url: 'https://docs.example.com/impl', design_doc_url: 'https://docs.example.com/design', documentation_status: 'approved', acceptance_criteria: '[]', tags: '[]', assigned_to: 'john@example.com', sprint_id: 'SPRINT-0001', epic_id: null, created_at: Date.now(), updated_at: Date.now() }, { id: 'STORY-0002', title: 'Test Story without Docs', description: 'Another test', status: 'todo', priority: 'medium', story_points: 3, implementation_doc_url: null, design_doc_url: null, documentation_status: null, acceptance_criteria: '[]', tags: '[]', assigned_to: null, sprint_id: 'SPRINT-0001', epic_id: null, created_at: Date.now(), updated_at: Date.now() } ] }); const result = await tools.list_stories_with_details.execute({ sprintId: 'SPRINT-0001', includeFields: ['id', 'title', 'status', 'implementationDocumentUrl', 'designDocumentUrl', 'documentationStatus'] }, createContext('list_stories_with_details')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.stories).toBeDefined(); expect(result.data.stories).toHaveLength(2); expect(result.data.stories[0].implementationDocumentUrl).toBe('https://docs.example.com/impl'); expect(result.data.stories[1].implementationDocumentUrl).toBeNull(); expect(result.data.summary).toBeDefined(); expect(result.data.summary.missingDocumentation).toBe(1); expect(mockDb.query).toHaveBeenCalled(); }); it('should filter by status', async () => { mockDb.query.mockResolvedValueOnce({ success: true, data: [ { id: 'STORY-0001', title: 'In Progress Story', status: 'in_progress', story_points: 5, sprint_id: 'SPRINT-0001' } ] }); const result = await tools.list_stories_with_details.execute({ status: 'in_progress', includeFields: ['id', 'title', 'status'] }, createContext('list_stories_with_details')); expect(result.success).toBe(true); expect(result.data).toBeDefined(); expect(result.data.stories).toBeDefined(); expect(result.data.stories).toHaveLength(1); expect(result.data.stories[0].status).toBe('in_progress'); }); }); describe('tool registration', () => { it('should register all expected tools', () => { const expectedTools = [ 'create_agile_sprint', 'create_user_story', 'add_story_to_sprint', 'update_story_status', 'get_sprint_stats', 'list_stories', 'list_stories_with_details' ]; for (const toolName of expectedTools) { expect(tools[toolName]).toBeDefined(); expect(typeof tools[toolName].execute).toBe('function'); } }); }); describe('error handling', () => { it('should handle database errors gracefully', async () => { mockDb.run.mockResolvedValueOnce({ success: false, error: 'Database error' }); const input = { name: 'Sprint 1', goal: 'Test goal', duration: 14, }; const result = await tools.create_agile_sprint.execute(input, createContext('create_agile_sprint')); expect(result.success).toBe(false); expect(result.error).toBeDefined(); }); }); });