@boundless-oss/atlas
Version:
Atlas - MCP Server for comprehensive startup project management
385 lines (329 loc) • 12.3 kB
text/typescript
import { describe, it, expect, beforeEach, vi } from 'vitest';
import { setupDocumentationTools } from '../tools.js';
import { SqliteManager } from '../../../storage/sqlite-manager.js';
// Mock SqliteManager
vi.mock('../../../storage/sqlite-manager.js', () => ({
SqliteManager: {
getInstance: vi.fn()
}
}));
describe('Documentation Module', () => {
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 setupDocumentationTools();
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('generate_readme', () => {
it('should generate README document in database', async () => {
const input = {
projectName: 'Test Project',
description: 'A test project',
features: ['Feature 1', 'Feature 2'],
techStack: ['TypeScript', 'Node.js'],
};
const result = await tools.generate_readme.execute(input, createContext('generate_readme'));
expect(result.success).toBe(true);
expect(result.data.message).toContain('README.md created for project "Test Project"');
expect(result.data.document.title).toBe('README.md');
expect(result.data.document.type).toBe('readme');
expect(result.data.document.created).toBe(true);
expect(mockDb.run).toHaveBeenCalled();
});
it('should update existing README document', async () => {
// Mock existing README
mockDb.get.mockResolvedValueOnce({
success: true,
data: {
id: 'existing-readme-id',
title: 'README.md',
type: 'readme'
}
});
const input = {
projectName: 'Updated Project',
description: 'An updated test project',
repository: 'frontend-repo',
};
const result = await tools.generate_readme.execute(input, createContext('generate_readme'));
expect(result.success).toBe(true);
expect(result.data.message).toContain('README.md updated for project "Updated Project"');
expect(result.data.document.updated).toBe(true);
expect(mockDb.run).toHaveBeenCalledWith(
expect.stringContaining('UPDATE documents'),
expect.any(Array)
);
});
it('should handle database errors gracefully', async () => {
mockDb.run.mockResolvedValueOnce({ success: false, error: 'Database error' });
const input = {
projectName: 'Test Project',
description: 'A test project',
};
const result = await tools.generate_readme.execute(input, createContext('generate_readme'));
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
expect(result.error?.code).toBe('DATABASE_ERROR');
});
});
describe('generate_claude_config', () => {
it('should generate CLAUDE.md configuration', async () => {
const input = {
tddMode: 'strict' as const,
testCommand: 'npm test',
lintCommand: 'npm run lint',
customInstructions: ['Use TypeScript', 'Follow TDD'],
};
const result = await tools.generate_claude_config.execute(input, createContext('generate_claude_config'));
expect(result.success).toBe(true);
expect(result.data.message).toBe('CLAUDE.md configuration created');
expect(result.data.config.tddMode).toBe('strict');
expect(result.data.document.created).toBe(true);
expect(mockDb.run).toHaveBeenCalled();
});
it('should update existing CLAUDE.md configuration', async () => {
// Mock existing CLAUDE.md
mockDb.get.mockResolvedValueOnce({
success: true,
data: {
id: 'existing-claude-id',
title: 'CLAUDE.md',
type: 'claude-config'
}
});
const input = {
tddMode: 'moderate' as const,
buildCommand: 'npm run build:prod',
};
const result = await tools.generate_claude_config.execute(input, createContext('generate_claude_config'));
expect(result.success).toBe(true);
expect(result.data.message).toBe('CLAUDE.md configuration updated');
expect(result.data.document.updated).toBe(true);
expect(mockDb.run).toHaveBeenCalledWith(
expect.stringContaining('UPDATE documents'),
expect.any(Array)
);
});
});
describe('create_documentation', () => {
it('should create API documentation', async () => {
const input = {
type: 'api' as const,
title: 'REST API',
content: 'API endpoints documentation...',
};
const result = await tools.create_documentation.execute(input, createContext('create_documentation'));
expect(result.success).toBe(true);
expect(result.data.message).toBe('Documentation created: API.md');
expect(result.data.document.type).toBe('api');
expect(result.data.document.path).toBe('docs/API.md');
expect(mockDb.run).toHaveBeenCalled();
});
it('should create custom documentation with repository context', async () => {
const input = {
type: 'custom' as const,
title: 'Mobile Setup Guide',
content: 'How to set up the mobile development environment...',
repository: 'mobile-repo',
};
const result = await tools.create_documentation.execute(input, createContext('create_documentation'));
expect(result.success).toBe(true);
expect(result.data.message).toBe('Documentation created: mobile-setup-guide.md');
expect(result.data.document.path).toBe('mobile-repo/docs/mobile-setup-guide.md');
expect(mockDb.run).toHaveBeenCalled();
});
});
describe('list_documents', () => {
it('should list documentation files', async () => {
mockDb.query.mockResolvedValueOnce({
success: true,
data: [
{
id: 'doc-1',
title: 'README.md',
type: 'readme',
path: 'README.md',
status: 'published',
author: 'test-user',
version: 1,
tags: '["readme", "documentation"]',
created_at: Date.now(),
updated_at: Date.now(),
content: '# Test Project\n\nThis is a test project...'
},
{
id: 'doc-2',
title: 'API Documentation',
type: 'api',
path: 'docs/API.md',
status: 'draft',
author: 'test-user',
version: 2,
tags: '["api", "documentation"]',
created_at: Date.now(),
updated_at: Date.now(),
content: '# API\n\nAPI documentation...'
}
]
});
const result = await tools.list_documents.execute({}, createContext('list_documents'));
expect(result.success).toBe(true);
expect(result.data.documents).toHaveLength(2);
expect(result.data.documents[0].title).toBe('README.md');
expect(result.data.documents[1].title).toBe('API Documentation');
expect(mockDb.query).toHaveBeenCalled();
});
it('should filter documents by type', async () => {
mockDb.query.mockResolvedValueOnce({
success: true,
data: []
});
const result = await tools.list_documents.execute({ type: 'api' }, createContext('list_documents'));
expect(result.success).toBe(true);
expect(mockDb.query).toHaveBeenCalledWith(
expect.stringContaining('AND type = ?'),
expect.arrayContaining(['api'])
);
});
});
describe('update_document', () => {
it('should update an existing document', async () => {
// Mock document check
mockDb.get.mockResolvedValueOnce({
success: true,
data: {
id: 'doc-1',
title: 'Old Title',
type: 'api'
}
});
const input = {
documentId: 'doc-1',
title: 'Updated API Documentation',
status: 'published' as const,
tags: ['api', 'v2', 'rest']
};
const result = await tools.update_document.execute(input, createContext('update_document'));
expect(result.success).toBe(true);
expect(result.data.message).toBe('Document "Updated API Documentation" updated successfully');
expect(result.data.changes).toContain('title');
expect(result.data.changes).toContain('status');
expect(result.data.changes).toContain('tags');
expect(mockDb.run).toHaveBeenCalled();
});
it('should handle document not found', async () => {
mockDb.get.mockResolvedValueOnce({
success: true,
data: null
});
const result = await tools.update_document.execute({
documentId: 'non-existent',
title: 'New Title'
}, createContext('update_document'));
expect(result.success).toBe(false);
expect(result.error?.code).toBe('RESOURCE_NOT_FOUND');
});
});
describe('search_documents', () => {
it('should search documents by content or title', async () => {
mockDb.query.mockResolvedValueOnce({
success: true,
data: [
{
id: 'doc-1',
title: 'API Reference',
type: 'api',
path: 'docs/api.md',
content: 'This is the API reference guide for our REST endpoints...',
updated_at: Date.now()
},
{
id: 'doc-2',
title: 'Getting Started',
type: 'guide',
path: 'docs/getting-started.md',
content: 'Welcome to our API. This guide will help you...',
updated_at: Date.now()
}
]
});
const result = await tools.search_documents.execute({
query: 'API'
}, createContext('search_documents'));
expect(result.success).toBe(true);
expect(result.data.results).toHaveLength(2);
expect(result.data.results[0].title).toBe('API Reference');
expect(result.data.results[0].score).toBe(1); // Title match
expect(mockDb.query).toHaveBeenCalledWith(
expect.stringContaining('LIKE ?'),
expect.arrayContaining(['%API%'])
);
});
it('should filter search by document type', async () => {
mockDb.query.mockResolvedValueOnce({
success: true,
data: []
});
const result = await tools.search_documents.execute({
query: 'test',
type: 'api'
}, createContext('search_documents'));
expect(result.success).toBe(true);
expect(mockDb.query).toHaveBeenCalledWith(
expect.stringContaining('AND type = ?'),
expect.arrayContaining(['api'])
);
});
});
describe('tool registration', () => {
it('should register all expected tools', () => {
const expectedTools = [
'generate_readme',
'generate_claude_config',
'create_documentation',
'list_documents',
'update_document',
'search_documents'
];
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 = {
projectName: 'Test Project',
description: 'A test project',
};
const result = await tools.generate_readme.execute(input, createContext('generate_readme'));
expect(result.success).toBe(false);
expect(result.error).toBeDefined();
});
});
});