api-routes.test.js
5.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* Integration Tests — API Routes
*
* Spins up Express with arena routes and tests key endpoints.
* Uses temp SQLite DB to avoid side effects.
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import express from 'express';
import http from 'http';
import fs from 'fs';
const TEST_DB_PATH = '/tmp/test-integration-skills.sqlite';
const TEST_SKILLS_DIR = '/tmp/test-int-skills';
const TEST_DATA_DIR = '/tmp/test-int-data';
let app, server, baseUrl;
function request(method, urlStr, body = null) {
return new Promise((resolve, reject) => {
const url = new URL(urlStr, baseUrl);
const opts = { method, hostname: url.hostname, port: url.port, path: url.pathname + url.search, headers: { 'Content-Type': 'application/json' } };
const req = http.request(opts, (res) => {
let data = '';
res.on('data', (d) => data += d);
res.on('end', () => {
try { resolve({ status: res.statusCode, data: JSON.parse(data) }); }
catch { resolve({ status: res.statusCode, data }); }
});
});
req.on('error', reject);
if (body) req.write(JSON.stringify(body));
req.end();
});
}
describe('API Integration Tests', () => {
beforeAll(async () => {
[TEST_DB_PATH, TEST_DB_PATH + '-wal', TEST_DB_PATH + '-shm'].forEach(f => { try { fs.unlinkSync(f); } catch {} });
fs.mkdirSync(TEST_SKILLS_DIR, { recursive: true });
fs.mkdirSync(TEST_DATA_DIR, { recursive: true });
process.env.SKILLS_DB_PATH = TEST_DB_PATH;
process.env.BENCHMARK_DB_PATH = '/tmp/test-integration-bench.sqlite';
process.env.PORT = '0';
process.env.SKILLS_CODE_DIR = TEST_SKILLS_DIR;
process.env.SKILLS_DATA_DIR = TEST_DATA_DIR;
process.env.JWT_SECRET = 'test-secret-key-for-integration';
process.env.API_KEYS = 'test-api-key-123';
// Clear module cache for clean state
Object.keys(require.cache).filter(k =>
k.includes('skill-db') || k.includes('benchmark-db') || k.includes('/config')
).forEach(k => delete require.cache[k]);
app = express();
app.use(express.json());
try {
const arenaRoutes = require('../../routes/arena');
app.use('/api/arena', arenaRoutes);
} catch (e) { console.warn('Skipping arena routes:', e.message); }
server = http.createServer(app);
await new Promise((resolve) => {
server.listen(0, () => {
baseUrl = `http://127.0.0.1:${server.address().port}`;
resolve();
});
});
});
afterAll(async () => {
if (server) await new Promise(r => server.close(r));
[TEST_DB_PATH, TEST_DB_PATH + '-wal', TEST_DB_PATH + '-shm',
'/tmp/test-integration-bench.sqlite'].forEach(f => { try { fs.unlinkSync(f); } catch {} });
try { fs.rmSync(TEST_SKILLS_DIR, { recursive: true }); } catch {}
try { fs.rmSync(TEST_DATA_DIR, { recursive: true }); } catch {}
});
describe('Arena Routes', () => {
it('GET /api/arena/db-stats should return stats', async () => {
const res = await request('GET', '/api/arena/db-stats');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('total');
});
it('GET /api/arena/leaderboard should return paginated results', async () => {
const res = await request('GET', '/api/arena/leaderboard?limit=10&page=1');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('skills');
expect(res.data).toHaveProperty('total');
expect(Array.isArray(res.data.skills)).toBe(true);
});
it('GET /api/arena/taxonomy should return taxonomy data', async () => {
const res = await request('GET', '/api/arena/taxonomy');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('roles');
expect(res.data).toHaveProperty('scenes');
expect(res.data).toHaveProperty('platforms');
});
it('GET /api/arena/stats should return arena stats', async () => {
const res = await request('GET', '/api/arena/stats');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('totalSkills');
});
it('GET /api/arena/scenes should return scene listing', async () => {
const res = await request('GET', '/api/arena/scenes');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('scenes');
});
it('GET /api/arena/skill/nonexistent returns 404', async () => {
const res = await request('GET', '/api/arena/skill/definitely_not_a_real_skill_xyz');
expect(res.status).toBe(404);
});
it('POST /api/arena/submit with valid data should register a skill', async () => {
const res = await request('POST', '/api/arena/submit', {
skillName: 'integration_test_skill', description: 'Test skill for integration',
domain: 'test', scope: 'unit',
});
// Expect 200 (created) or 409 (already exists)
expect([200, 201, 409]).toContain(res.status);
});
it('GET /api/arena/leaderboard returns non-empty list', async () => {
const res = await request('GET', '/api/arena/leaderboard?limit=5');
expect(res.status).toBe(200);
expect(res.data.skills.length).toBeGreaterThanOrEqual(0);
});
it('GET /api/arena/skill/:name/favorite query works', async () => {
const res = await request('GET', '/api/arena/skill/integration_test_skill/favorite?userId=test-user-1');
expect(res.status).toBe(200);
expect(res.data).toHaveProperty('favorited');
expect(res.data).toHaveProperty('count');
});
it('POST /api/arena/skill/:name/favorite should toggle favorite', async () => {
const res = await request('POST', '/api/arena/skill/integration_test_skill/favorite', { userId: 'test-user-1' });
expect(res.status).toBe(200);
});
});
});