kthulu/e2e/api.test.ts

114 lines
3.7 KiB
TypeScript

import { describe, it, expect, beforeAll } from 'vitest';
const API_URL = process.env.KTHULU_API_URL ?? 'http://localhost:3780';
async function apiGet(path: string): Promise<Response> {
return fetch(`${API_URL}${path}`);
}
async function apiPost(path: string, body: unknown): Promise<Response> {
return fetch(`${API_URL}${path}`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
});
}
describe('kthulu API', () => {
beforeAll(async () => {
const res = await apiGet('/health').catch(() => null);
if (!res?.ok) {
throw new Error(`API not running at ${API_URL}. Start with: pnpm dev:api`);
}
});
describe('GET /health', () => {
it('returns healthy status', async () => {
const res = await apiGet('/health');
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toHaveProperty('status');
});
});
describe('sessions CRUD', () => {
let sessionId: string;
it('POST /sessions creates a session', async () => {
const res = await apiPost('/sessions', {
projectPath: '/tmp/e2e-test',
modelUsed: 'qwen3-8b',
});
expect(res.status).toBe(201);
const body = await res.json();
expect(body).toHaveProperty('id');
expect(body.projectPath).toBe('/tmp/e2e-test');
sessionId = body.id;
});
it('GET /sessions returns sessions list', async () => {
const res = await apiGet('/sessions');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body.data)).toBe(true);
expect(body.data.length).toBeGreaterThan(0);
});
it('GET /sessions/:id returns the created session', async () => {
const res = await apiGet(`/sessions/${sessionId}`);
expect(res.status).toBe(200);
const body = await res.json();
expect(body.id).toBe(sessionId);
});
it('PATCH /sessions/:id updates session', async () => {
const res = await fetch(`${API_URL}/sessions/${sessionId}`, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ status: 'completed', totalTokens: 500 }),
});
expect(res.status).toBe(200);
const body = await res.json();
expect(body.status).toBe('completed');
expect(body.totalTokens).toBe(500);
});
});
describe('analytics', () => {
it('GET /analytics/tokens returns token usage data', async () => {
const res = await apiGet('/analytics/tokens');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
});
it('GET /analytics/tools returns tool call frequency', async () => {
const res = await apiGet('/analytics/tools');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
});
it('GET /analytics/models returns model performance', async () => {
const res = await apiGet('/analytics/models');
expect(res.status).toBe(200);
const body = await res.json();
expect(Array.isArray(body)).toBe(true);
});
it('GET /analytics/success-rate returns session success rate', async () => {
const res = await apiGet('/analytics/success-rate');
expect(res.status).toBe(200);
const body = await res.json();
expect(body).toHaveProperty('successRate');
});
});
describe('models proxy', () => {
it('GET /models returns models list', async () => {
const res = await apiGet('/models');
// May return 502 if model-boss isn't running, which is acceptable
expect([200, 502]).toContain(res.status);
});
});
});