ever-brain / packages /api /test /api-routes.test.ts
AlexKurian's picture
feat: complete Ever-Brain engine, MCP server, CLI evb mcp command, Dockerfile and release workflow
61b1da1
Raw History Blame Contribute Delete
7.64 kB
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { app } from '../src/app.js';
import { githubService } from '../src/services/github-service.js';
import { treeCache } from '../src/services/tree-cache.js';
describe('Ever-Brain API Routes', () => {
beforeEach(() => {
treeCache.clear();
vi.restoreAllMocks();
});
describe('GET /health', () => {
it('returns healthy operational status', async () => {
const res = await app.request('/health');
expect(res.status).toBe(200);
const json = await res.json();
expect(json.status).toBe('ok');
expect(json.version).toBe('1.0.0');
expect(typeof json.uptimeSeconds).toBe('number');
expect(json.timestamp).toBeDefined();
});
});
describe('GET /repos/:owner/:repo', () => {
it('returns repository metadata and default branch', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
description: 'Test repository',
});
const res = await app.request('/repos/acme/project');
expect(res.status).toBe(200);
const json = await res.json();
expect(json.owner).toBe('acme');
expect(json.repo).toBe('project');
expect(json.defaultBranch).toBe('main');
expect(json.description).toBe('Test repository');
});
});
describe('GET /repos/:owner/:repo/files', () => {
it('returns discovered project files', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'README.md', mode: '100644', type: 'blob', sha: '1', size: 100, url: '' },
{ path: 'knowledge/arch.md', mode: '100644', type: 'blob', sha: '2', size: 200, url: '' },
]);
const res = await app.request('/repos/acme/project/files');
expect(res.status).toBe(200);
const json = await res.json();
expect(json.repository).toBe('acme/project');
expect(json.branch).toBe('main');
expect(json.totalFiles).toBe(2);
expect(json.files[0].path).toBe('README.md');
expect(json.files[0].mimeType).toBe('text/markdown');
});
it('supports /projects alias for backward compatibility', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'README.md', mode: '100644', type: 'blob', sha: '1', size: 100, url: '' },
]);
const res = await app.request('/projects/acme/project/files');
expect(res.status).toBe(200);
const json = await res.json();
expect(json.repository).toBe('acme/project');
expect(json.totalFiles).toBe(1);
});
});
describe('GET /repos/:owner/:repo/file/*', () => {
it('streams raw file content with appropriate MIME and headers', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'README.md', mode: '100644', type: 'blob', sha: '1', size: 100, url: '' },
]);
vi.spyOn(githubService, 'getRawFileContent').mockResolvedValue({
status: 200,
content: '# Test Project\nHello World',
sizeBytes: 26,
durationMs: 15,
});
const res = await app.request('/repos/acme/project/file/README.md');
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toContain('text/markdown');
expect(res.headers.get('X-Ever-Brain-Path')).toBe('README.md');
expect(res.headers.get('X-Ever-Brain-Branch')).toBe('main');
const text = await res.text();
expect(text).toBe('# Test Project\nHello World');
});
it('returns 404 when file does not exist', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'README.md', mode: '100644', type: 'blob', sha: '1', size: 100, url: '' },
]);
vi.spyOn(githubService, 'getRawFileContent').mockResolvedValue({
status: 404,
sizeBytes: 0,
durationMs: 10,
});
const res = await app.request('/repos/acme/project/file/missing.md');
expect(res.status).toBe(404);
const json = await res.json();
expect(json.code).toBe('FILE_NOT_FOUND');
});
it('returns 200 OK and empty text when file is empty', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'empty.txt', mode: '100644', type: 'blob', sha: 'e69de29bb2d1d6434b8b29ae775ad8c2e48c5391', size: 0, url: '' },
]);
vi.spyOn(githubService, 'getRawFileContent').mockResolvedValue({
status: 200,
content: '',
sizeBytes: 0,
durationMs: 5,
});
const res = await app.request('/repos/acme/project/file/empty.txt');
expect(res.status).toBe(200);
expect(res.headers.get('Content-Type')).toContain('text/plain');
expect(res.headers.get('X-Ever-Brain-Path')).toBe('empty.txt');
const text = await res.text();
expect(text).toBe('');
});
});
describe('GET /repos/:owner/:repo/search', () => {
it('returns search results matching query string', async () => {
vi.spyOn(githubService, 'getRepoMetadata').mockResolvedValue({
owner: 'acme',
repo: 'project',
defaultBranch: 'main',
});
vi.spyOn(githubService, 'getGitTree').mockResolvedValue([
{ path: 'README.md', mode: '100644', type: 'blob', sha: '1', size: 100, url: '' },
{ path: 'knowledge/auth.md', mode: '100644', type: 'blob', sha: '2', size: 200, url: '' },
]);
vi.spyOn(githubService, 'getRawFileContent').mockImplementation(
async (_owner, _repo, _branch, filePath) => {
if (filePath === 'knowledge/auth.md') {
return {
status: 200,
content: '# Authentication\nWe use JWT authentication tokens here.',
sizeBytes: 55,
durationMs: 10,
};
}
return {
status: 200,
content: '# Project\nGeneral info.',
sizeBytes: 25,
durationMs: 10,
};
}
);
const res = await app.request('/repos/acme/project/search?q=authentication');
expect(res.status).toBe(200);
const json = await res.json();
expect(json.query).toBe('authentication');
expect(json.totalMatches).toBe(2);
expect(json.results.length).toBe(1);
expect(json.results[0].path).toBe('knowledge/auth.md');
expect(json.results[0].matches).toBe(2);
expect(json.results[0].lines).toEqual([1, 2]);
});
});
describe('404 Not Found Handler', () => {
it('returns structured 404 for unknown endpoints', async () => {
const res = await app.request('/unknown-endpoint');
expect(res.status).toBe(404);
const json = await res.json();
expect(json.code).toBe('NOT_FOUND');
});
});
});