@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
256 lines (226 loc) • 7.96 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { AgileManager } from '../manager.js';
import { ConfigManager } from '../../../config/config-manager.js';
import { Story, AcceptanceCriterion } from '../types.js';
vi.mock('../../../config/config-manager.js');
vi.mock('fs', () => ({
promises: {
readFile: vi.fn().mockResolvedValue('[]'),
writeFile: vi.fn().mockResolvedValue(undefined),
mkdir: vi.fn().mockResolvedValue(undefined),
access: vi.fn().mockRejectedValue(new Error('ENOENT')),
},
}));
describe('Agile Enhanced Features Tests', () => {
let agileManager: AgileManager;
let mockConfigManager: vi.Mocked<ConfigManager>;
beforeEach(async () => {
vi.clearAllMocks();
mockConfigManager = new ConfigManager() as vi.Mocked<ConfigManager>;
vi.mocked(mockConfigManager.getDataPath).mockReturnValue('/mock/data/path');
agileManager = new AgileManager(mockConfigManager);
await agileManager.initialize();
});
describe('Structured Acceptance Criteria', () => {
it('should create story with structured acceptance criteria', async () => {
const story = await agileManager.addStory({
title: 'User authentication',
description: 'Implement user login',
storyPoints: 5,
priority: 'high',
acceptanceCriteria: [
{
id: 'ac-1',
description: 'User can login with email and password',
status: 'pending'
},
{
id: 'ac-2',
description: 'Invalid credentials show error message',
status: 'pending'
}
] as AcceptanceCriterion[]
});
expect(story.acceptanceCriteria).toHaveLength(2);
expect(story.acceptanceCriteria[0]).toHaveProperty('status', 'pending');
});
it('should convert legacy string criteria to structured format', async () => {
const story = await agileManager.addStory({
title: 'Legacy story',
description: 'Story with string criteria',
storyPoints: 3,
priority: 'medium',
acceptanceCriteria: [
'Criterion 1',
'Criterion 2'
]
});
expect(story.acceptanceCriteria).toHaveLength(2);
expect(story.acceptanceCriteria[0]).toHaveProperty('description', 'Criterion 1');
expect(story.acceptanceCriteria[0]).toHaveProperty('status', 'pending');
expect(story.acceptanceCriteriaLegacy).toEqual(['Criterion 1', 'Criterion 2']);
});
it('should update acceptance criteria status', async () => {
const story = await agileManager.addStory({
title: 'Story',
description: 'Test',
storyPoints: 2,
priority: 'low',
acceptanceCriteria: [
{
id: 'ac-1',
description: 'Test criterion',
status: 'pending'
}
] as AcceptanceCriterion[]
});
const updated = await agileManager.updateStoryStatus(story.id, {
acceptanceCriteria: [
{
id: 'ac-1',
description: 'Test criterion',
status: 'completed',
verifiedBy: 'qa-tester',
verifiedAt: new Date()
}
] as AcceptanceCriterion[]
});
expect(updated.acceptanceCriteria[0].status).toBe('completed');
expect(updated.acceptanceCriteria[0].verifiedBy).toBe('qa-tester');
});
});
describe('Story Dependencies', () => {
it('should create story with dependencies', async () => {
const blocker = await agileManager.addStory({
title: 'Database setup',
description: 'Setup database',
storyPoints: 3,
priority: 'high',
acceptanceCriteria: []
});
const dependent = await agileManager.addStory({
title: 'API implementation',
description: 'Build API',
storyPoints: 5,
priority: 'medium',
acceptanceCriteria: [],
dependencies: [
{
taskId: blocker.id,
type: 'depends_on'
}
]
});
expect(dependent.dependencies).toHaveLength(1);
expect(dependent.dependencies![0].taskId).toBe(blocker.id);
});
});
describe('Time Tracking', () => {
it('should create story with time estimate', async () => {
const story = await agileManager.addStory({
title: 'Feature',
description: 'New feature',
storyPoints: 8,
priority: 'high',
acceptanceCriteria: [],
timeEstimate: 16
});
expect(story.timeTracking).toBeDefined();
expect(story.timeTracking!.estimated).toBe(16);
});
it('should update time tracking', async () => {
const story = await agileManager.addStory({
title: 'Task',
description: 'Task desc',
storyPoints: 3,
priority: 'medium',
acceptanceCriteria: [],
timeEstimate: 6
});
const updated = await agileManager.updateStoryStatus(story.id, {
timeTracking: {
estimated: 6,
spent: 4,
logs: [{
date: new Date(),
hours: 4,
description: 'Initial implementation'
}]
}
});
expect(updated.timeTracking!.spent).toBe(4);
// hoursSpent is updated separately with the hoursSpent field, not timeTracking
});
});
describe('Tags and Watchers', () => {
it('should create story with tags', async () => {
const story = await agileManager.addStory({
title: 'Tagged story',
description: 'Story with tags',
storyPoints: 5,
priority: 'medium',
acceptanceCriteria: [],
tags: ['backend', 'api', 'security']
});
expect(story.tags).toEqual(['backend', 'api', 'security']);
});
it('should add watchers to story', async () => {
const story = await agileManager.addStory({
title: 'Watched story',
description: 'Story being watched',
storyPoints: 3,
priority: 'low',
acceptanceCriteria: [],
watchers: ['pm@example.com', 'dev@example.com']
});
expect(story.watchers).toEqual(['pm@example.com', 'dev@example.com']);
});
});
describe('Enhanced Property Updates', () => {
it('should update multiple enhanced properties', async () => {
const story = await agileManager.addStory({
title: 'Original title',
description: 'Original desc',
storyPoints: 5,
priority: 'medium',
acceptanceCriteria: ['Original criterion']
});
const updated = await agileManager.updateStoryStatus(story.id, {
title: 'Updated title',
description: 'Updated description',
priority: 'high',
storyPoints: 8,
tags: ['urgent', 'customer-request'],
acceptanceCriteria: [
{
id: 'ac-new',
description: 'New criterion',
status: 'pending'
}
] as AcceptanceCriterion[]
});
expect(updated.title).toBe('Updated title');
expect(updated.description).toBe('Updated description');
expect(updated.priority).toBe('high');
expect(updated.storyPoints).toBe(8);
expect(updated.tags).toEqual(['urgent', 'customer-request']);
expect(updated.acceptanceCriteria).toHaveLength(1);
expect(updated.acceptanceCriteria[0].description).toBe('New criterion');
});
});
describe('Comments and Attachments', () => {
it('should initialize with empty comments and attachments', async () => {
const story = await agileManager.addStory({
title: 'Story',
description: 'Description',
storyPoints: 3,
priority: 'medium',
acceptanceCriteria: []
});
expect(story.comments).toEqual([]);
expect(story.attachments).toEqual([]);
expect(story.customFields).toEqual([]);
expect(story.subtasks).toEqual([]);
});
});
});