@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
178 lines (153 loc) • 6.25 kB
text/typescript
import { describe, it, expect, beforeAll, afterAll, beforeEach, vi } from 'vitest';
import express, { Application } from 'express';
import request from 'supertest';
import { setupAgileAPI } from '../../api/agile.js';
import { SQLiteManager } from '../../../storage/sqlite-manager.js';
import { randomUUID } from 'crypto';
import path from 'path';
import fs from 'fs/promises';
// Mock the ensureDatabaseReady function before imports
let testDb: SQLiteManager;
vi.mock('../../../storage/sqlite-manager.js', async () => {
const actual = await vi.importActual('../../../storage/sqlite-manager.js') as any;
return {
...actual,
ensureDatabaseReady: () => {
if (!testDb) {
throw new Error('Test database not initialized');
}
return Promise.resolve(testDb);
}
};
});
describe('Agile API Integration Tests', () => {
let app: Application;
const testDbPath = '.atlas/test-agile-integration.db';
beforeAll(async () => {
// Ensure test directory exists
await fs.mkdir(path.dirname(testDbPath), { recursive: true });
// Initialize test database
testDb = new SQLiteManager(testDbPath);
await testDb.initialize();
// Setup Express app
app = express();
app.use(express.json());
setupAgileAPI(app, null);
});
afterAll(async () => {
// Clean up test database
await testDb.close();
try {
await fs.unlink(testDbPath);
} catch (e) {
// Ignore if file doesn't exist
}
});
beforeEach(async () => {
// Clear data in correct order to respect foreign keys
await testDb.run('DELETE FROM agile_stories');
await testDb.run('DELETE FROM agile_sprints');
await testDb.run('DELETE FROM projects');
// Create default project for tests
await testDb.run(`
INSERT INTO projects (id, name, description, config)
VALUES ('default', 'Default Test Project', 'Project for integration tests', '{}')
`);
});
describe('Sprint Board 404 Regression Test', () => {
it('should find individual sprints that appear in the list endpoint', async () => {
const sprintId = 'sprint-' + randomUUID();
const sprintName = 'Test Sprint for 404 Bug';
// Insert a test sprint directly into the database
const insertResult = await testDb.run(`
INSERT INTO agile_sprints (
id, project_id, name, goal, status,
start_date, end_date, duration, team,
story_points_planned, story_points_completed,
stories_total, stories_completed, velocity,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
sprintId,
'default',
sprintName,
'Test goal',
'active',
Date.now(),
Date.now() + 14 * 24 * 60 * 60 * 1000,
14,
'["Developer 1", "Developer 2"]',
20,
10,
5,
2,
10,
Date.now(),
Date.now()
]);
expect(insertResult.success).toBe(true);
// Step 1: Verify the sprint appears in the list endpoint
const listResponse = await request(app).get('/api/agile/sprints');
expect(listResponse.status).toBe(200);
expect(listResponse.body.success).toBe(true);
expect(listResponse.body.data.sprints).toHaveLength(1);
const sprintInList = listResponse.body.data.sprints[0];
expect(sprintInList.id).toBe(sprintId);
expect(sprintInList.name).toBe(sprintName);
expect(sprintInList.status).toBe('active');
// Step 2: Verify the individual sprint endpoint also finds it
// This is the regression test - it should NOT return 404
const individualResponse = await request(app).get(`/api/agile/sprints/${sprintId}`);
expect(individualResponse.status).toBe(200);
expect(individualResponse.body.success).toBe(true);
expect(individualResponse.body.data).toBeDefined();
expect(individualResponse.body.data.id).toBe(sprintId);
expect(individualResponse.body.data.name).toBe(sprintName);
expect(individualResponse.body.data.status).toBe('active');
// Verify field mapping is correct
expect(individualResponse.body.data.startDate).toBeDefined();
expect(individualResponse.body.data.endDate).toBeDefined();
expect(individualResponse.body.data.team).toEqual(['Developer 1', 'Developer 2']);
expect(individualResponse.body.data.storyPointsPlanned).toBe(20);
// Note: storyPointsCompleted is recalculated based on actual stories
// Since we haven't added any stories, it should be 0
expect(individualResponse.body.data.storyPointsCompleted).toBe(0);
});
it('should handle complex sprint IDs with hyphens and UUIDs', async () => {
// Test with a complex ID format similar to what was failing
const complexSprintId = 'sprint-' + randomUUID();
await testDb.run(`
INSERT INTO agile_sprints (
id, project_id, name, goal, status,
start_date, end_date, duration, team,
created_at, updated_at
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
`, [
complexSprintId,
'default',
'Sprint with Complex ID',
'Test complex IDs',
'active',
Date.now(),
Date.now() + 14 * 24 * 60 * 60 * 1000,
14,
'[]',
Date.now(),
Date.now()
]);
// Should successfully retrieve the sprint with complex ID
const response = await request(app).get(`/api/agile/sprints/${complexSprintId}`);
expect(response.status).toBe(200);
expect(response.body.success).toBe(true);
expect(response.body.data.id).toBe(complexSprintId);
});
it('should properly return 404 for truly non-existent sprints', async () => {
// Ensure we're testing a sprint that really doesn't exist
const response = await request(app).get('/api/agile/sprints/definitely-does-not-exist');
expect(response.status).toBe(404);
expect(response.body.success).toBe(false);
expect(response.body.error).toBe('Sprint not found');
expect(response.body.sprintId).toBe('definitely-does-not-exist');
});
});
});