/** * 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_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); }); }); });