import test from 'node:test'; import assert from 'node:assert/strict'; import { createApp } from '../server/index.mjs'; import { ApiError } from '../server/board.mjs'; test('board HTTP API validates inputs, rejects writes, and hides upstream errors', async t => { const client = { config: () => ({ configured: true, cityId: 1 }), stops: async () => [{ id: 1, name: 'Швейная фабрика' }], timetable: async id => ({ stop: { id }, routes: [] }), coordinates: async () => ({ latitude: 55, longitude: 28 }), board: async id => { if (id === 2) throw new Error('secret-api-key and upstream response'); if (id === 3) throw new ApiError(404, 'stop_not_found'); return { stop: { id }, departures: [] }; } }; const weatherClient = { weather: async (cityId, stopId) => ({ cityId, stopId, temperature: 12 }) }; const server = createApp(client, weatherClient).listen(0, '127.0.0.1'); await new Promise((resolve, reject) => { server.once('listening', resolve); server.once('error', reject); }); t.after(() => new Promise(resolve => server.close(resolve))); const base = `http://127.0.0.1:${server.address().port}`; await t.test('successful GET returns no-store and server time', async () => { const response = await fetch(base + '/api/board?stopId=1'); assert.equal(response.status, 200); assert.equal(response.headers.get('cache-control'), 'no-store'); const body = await response.json(); assert.equal(body.stop.id, 1); assert.ok(Number.isFinite(Date.parse(body.serverTime))); }); await t.test('invalid and missing IDs return 400', async () => { for (const endpoint of ['board', 'timetable', 'weather']) for (const query of ['', '?stopId=0', '?stopId=-1', '?stopId=abc', '?stopId=1&stopId=2']) assert.equal((await fetch(base + '/api/' + endpoint + query)).status, 400); }); await t.test('full timetable and weather use only the selected platform and no-store', async () => { const timetable = await fetch(base + '/api/timetable?stopId=2'); assert.equal(timetable.headers.get('cache-control'), 'no-store'); assert.equal((await timetable.json()).stop.id, 2); assert.deepEqual(await (await fetch(base + '/api/weather?stopId=2')).json(), { cityId: 1, stopId: 2, temperature: 12 }); }); await t.test('writes are never forwarded', async () => { const response = await fetch(base + '/api/board?stopId=1', { method: 'POST' }); assert.equal(response.status, 405); assert.equal(response.headers.get('allow'), 'GET'); }); await t.test('raw upstream errors and credentials never reach the browser', async () => { const response = await fetch(base + '/api/board?stopId=2'); assert.equal(response.status, 502); assert.deepEqual(await response.json(), { error: 'data_unavailable' }); }); await t.test('not-found and unknown endpoints keep their status', async () => { assert.equal((await fetch(base + '/api/board?stopId=3')).status, 404); assert.equal((await fetch(base + '/api/unrestricted-proxy')).status, 404); }); await t.test('config contains only public settings', async () => { assert.deepEqual(await (await fetch(base + '/api/config')).json(), { configured: true, cityId: 1 }); assert.equal((await fetch(base + '/healthz')).status, 200); }); });