const assert = require('node:assert/strict') const { spawn } = require('node:child_process') const net = require('node:net') const { test } = require('node:test') const HOST = '127.0.0.1' const TOKEN = 'server-smoke-token' test('render server health, auth, and validation routes work', async (t) => { const port = await freePort() const server = spawn(process.execPath, ['server.js'], { cwd: __dirname, env: { ...process.env, HOST, PORT: String(port), RENDER_TOKEN: TOKEN, RENDER_MAX_BODY: '1mb', RENDER_RATE_MAX: '100', PUPPETEER_EXECUTABLE_PATH: '/not-used-by-smoke-test', }, stdio: ['ignore', 'pipe', 'pipe'], }) let output = '' server.stdout.on('data', (chunk) => { output += chunk.toString() }) server.stderr.on('data', (chunk) => { output += chunk.toString() }) t.after(async () => { if (server.exitCode === null) { server.kill('SIGTERM') await waitForExit(server) } }) await waitForServer(server, port, () => output) const base = `http://${HOST}:${port}` const health = await fetch(`${base}/health`) assert.equal(health.status, 200) assert.deepEqual(await health.json(), { ok: true }) const missingToken = await postRender(base, null, invalidRenderBody()) assert.equal(missingToken.status, 401) assert.equal((await missingToken.json()).error, 'invalid render token') const badToken = await postRender(base, 'wrong-token', invalidRenderBody()) assert.equal(badToken.status, 401) assert.equal((await badToken.json()).error, 'invalid render token') const invalidRender = await postRender(base, TOKEN, invalidRenderBody()) assert.equal(invalidRender.status, 400) assert.match((await invalidRender.json()).error, /canvas must be/) const missingJobEvents = await fetch(`${base}/render/does-not-exist/events?token=bad`) assert.equal(missingJobEvents.status, 404) const missingJobFile = await fetch(`${base}/render/does-not-exist/file?token=bad`) assert.equal(missingJobFile.status, 404) const missingJobDelete = await fetch(`${base}/render/does-not-exist?token=bad`, { method: 'DELETE' }) assert.equal(missingJobDelete.status, 404) }) function invalidRenderBody() { return { scene: { canvas: { width: 0, height: 100 }, duration: 1, fps: 30, layers: [], assets: [], }, options: { fps: 30 }, } } function postRender(base, token, body) { const headers = { 'Content-Type': 'application/json' } if (token) headers.Authorization = `Bearer ${token}` return fetch(`${base}/render`, { method: 'POST', headers, body: JSON.stringify(body), }) } function freePort() { return new Promise((resolve, reject) => { const server = net.createServer() server.once('error', reject) server.listen(0, HOST, () => { const address = server.address() server.close(() => { if (!address || typeof address === 'string') reject(new Error('could not allocate test port')) else resolve(address.port) }) }) }) } async function waitForServer(server, port, output) { const deadline = Date.now() + 5000 let lastError = null while (Date.now() < deadline) { if (server.exitCode !== null) { throw new Error(`server exited early with code ${server.exitCode}\n${output()}`) } try { const res = await fetch(`http://${HOST}:${port}/health`) if (res.ok) return } catch (err) { lastError = err } await delay(50) } throw new Error(`server did not start: ${lastError?.message || 'timeout'}\n${output()}`) } function waitForExit(child) { return new Promise((resolve) => { child.once('exit', () => resolve()) setTimeout(() => { if (child.exitCode === null) child.kill('SIGKILL') resolve() }, 1000).unref() }) } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)) }