56 lines
3.1 KiB
JavaScript
56 lines
3.1 KiB
JavaScript
|
|
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 });
|
||
|
|
}
|