import assert from 'node:assert/strict'; import { spawn } from 'node:child_process'; import { get } from 'node:http'; import { mkdtemp, rm } from 'node:fs/promises'; import { tmpdir } from 'node:os'; import { join } from 'node:path'; import { fileURLToPath } from 'node:url'; const directory = await mkdtemp(join(tmpdir(), 'competition-api-')); const port = 19000 + Math.floor(Math.random() * 30000); const base = `http://127.0.0.1:${port}/api/competition`; const server = spawn(process.execPath, ['server/competition-api.mjs'], { cwd: fileURLToPath(new URL('..', import.meta.url)), env: { ...process.env, COMPETITION_ADMIN_ALLOWED_IPS: '', COMPETITION_DATA_PATH: join(directory, 'state.json'), COMPETITION_PORT: String(port), COMPETITION_ALLOWED_ORIGINS: 'https://www.yuedongsports.com' }, stdio: 'ignore', }); try { let ready = false; for (let attempt = 0; attempt < 40; attempt++) { try { ready = (await fetch(`${base}/ping`)).ok; if (ready) break; } catch {} await new Promise((resolve) => setTimeout(resolve, 100)); } assert.ok(ready, 'API did not start'); assert.equal((await fetch(`${base}/admin`)).status, 200); const deniedStatus = await new Promise((resolve, reject) => { get(`${base}/admin`, { localAddress: '127.0.0.2' }, (response) => { response.resume(); resolve(response.statusCode); }).on('error', reject); }); assert.equal(deniedStatus, 403, 'an unlisted IP reached the admin endpoint'); const crossOriginState = await fetch(`${base}/state`, { headers: { Origin: 'https://www.yuedongsports.com' } }); assert.equal(crossOriginState.headers.get('access-control-allow-origin'), 'https://www.yuedongsports.com'); const streamController = new AbortController(); const stream = await fetch(`${base}/stream`, { signal: streamController.signal }); const reader = stream.body.getReader(); await reader.read(); const draft = JSON.parse(await (await fetch(`${base}/state`)).text()); draft.competition.nameZh = '测试赛事'; draft.schedule.items = [{ id: 'M1', time: '09:00', nameZh: '单摇', nameEn: 'Speed', group: 'A', gender: 'MALE', venueZh: '主场', venueEn: 'Hall A' }]; assert.equal((await fetch(`${base}/admin`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft) })).status, 200); const update = await Promise.race([reader.read(), new Promise((_, reject) => setTimeout(() => reject(new Error('SSE update timed out')), 2000))]); assert.match(new TextDecoder().decode(update.value), /event: update/); streamController.abort(); assert.equal((await (await fetch(`${base}/state`)).json()).competition.nameZh, '', 'draft leaked to public'); draft.published = true; assert.equal((await fetch(`${base}/admin`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(draft) })).status, 200); assert.equal((await (await fetch(`${base}/state`)).json()).competition.nameZh, '测试赛事'); assert.equal((await (await fetch(`${base}/admin`)).json()).schedule.items.length, 1); console.log('Competition API smoke test passed'); } finally { server.kill(); await rm(directory, { recursive: true, force: true }); }