100 lines
5.5 KiB
JavaScript
100 lines
5.5 KiB
JavaScript
import test from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { createHash } from 'node:crypto';
|
|
import { loadConfig } from '../server/config.mjs';
|
|
import { createScenarioRunner } from '../server/scenarios.mjs';
|
|
|
|
const cookie = 'ridebus_board_device=test-device-secret';
|
|
const publicSettings = { cityId: 1, defaultStopId: 1, configured: true };
|
|
const managedSettings = { cityId: 2, defaultStopId: 28, configured: true };
|
|
|
|
function fixture({ device = { enabled: true, state: 'paired', config: managedSettings }, deviceStatus = 200, env = { STATUS_BOARD_TEST_COOKIE: cookie } } = {}) {
|
|
const config = loadConfig(env);
|
|
const service = config.services.find(item => item.id === 'board');
|
|
const requests = [], renders = [];
|
|
const runner = createScenarioRunner(config, {
|
|
fetchImpl: async (url, options) => {
|
|
assert.equal(options.method || 'GET', 'GET');
|
|
const { pathname, search } = new URL(url);
|
|
requests.push({ path: pathname + search, cookie: options.headers.Cookie });
|
|
if (pathname === '/api/config') return Response.json(publicSettings);
|
|
if (pathname === '/api/device/status') return Response.json(device, { status: deviceStatus });
|
|
if (pathname === '/api/stops') return Response.json([{ id: 1, name: 'Default stop' }, { id: 28, name: 'Assigned stop' }, { id: 99, name: 'Override stop' }]);
|
|
if (pathname === '/api/board') {
|
|
const stopId = Number(new URL(url).searchParams.get('stopId'));
|
|
if (device.enabled && env.STATUS_BOARD_TEST_COOKIE && stopId !== managedSettings.defaultStopId) return Response.json({ error: 'settings_changed' }, { status: 409 });
|
|
return Response.json({ mode: 'live', stop: { id: stopId }, departures: [], partial: false });
|
|
}
|
|
throw new Error('Unexpected request');
|
|
},
|
|
browserChecks: { run: async (_spec, target) => { renders.push({ ...target }); return { ok: true, latencyMs: 20, httpCode: 200 }; }, close: async () => {} },
|
|
});
|
|
return { runner, service, requests, renders };
|
|
}
|
|
|
|
test('paired board uses its assigned city and stop instead of public defaults or env override', async () => {
|
|
const item = fixture({ env: { STATUS_BOARD_TEST_COOKIE: cookie, STATUS_TEST_CITY_ID: '1', STATUS_BOARD_TEST_STOP_ID: '99' } });
|
|
try {
|
|
const result = await item.runner.run(item.service);
|
|
assert.equal(result.ok, true);
|
|
assert.deepEqual(item.renders, [{ cityId: 2, stopId: 28 }]);
|
|
assert.deepEqual(item.requests.map(value => value.path), ['/api/config', '/api/device/status', '/api/stops', '/api/board?stopId=28']);
|
|
assert.ok(item.requests.every(value => value.cookie === cookie));
|
|
assert.deepEqual(result.steps.map(value => value.id), ['config', 'device', 'stops', 'departures', 'render']);
|
|
assert.doesNotMatch(JSON.stringify(result), /test-device-secret|Assigned stop/);
|
|
} finally { await item.runner.close(); }
|
|
});
|
|
|
|
test('standalone board keeps its explicit test stop and needs no device endpoint', async () => {
|
|
const item = fixture({ env: { STATUS_BOARD_TEST_STOP_ID: '99' } });
|
|
try {
|
|
assert.equal((await item.runner.run(item.service)).ok, true);
|
|
assert.deepEqual(item.renders, [{ cityId: 1, stopId: 99 }]);
|
|
assert.ok(item.requests.every(value => value.path !== '/api/device/status' && value.cookie === undefined));
|
|
} finally { await item.runner.close(); }
|
|
});
|
|
|
|
test('cookie on a board with remote settings disabled retains standalone configuration', async () => {
|
|
const item = fixture({ device: { enabled: false } });
|
|
try {
|
|
assert.equal((await item.runner.run(item.service)).ok, true);
|
|
assert.deepEqual(item.renders, [{ cityId: 1, stopId: 1 }]);
|
|
} finally { await item.runner.close(); }
|
|
});
|
|
|
|
test('unpaired, pending and expired test sessions are missing access rather than outages', async () => {
|
|
for (const state of ['unpaired', 'pending', 'expired']) {
|
|
const item = fixture({ device: { enabled: true, state } });
|
|
try {
|
|
const result = await item.runner.run(item.service);
|
|
assert.equal(result.ok, null);
|
|
assert.equal(result.reason, 'auth_required');
|
|
assert.equal(result.steps.at(-1).id, 'device');
|
|
assert.equal(result.steps.at(-1).status, 'failed');
|
|
assert.equal(item.renders.length, 0);
|
|
assert.equal(item.requests.length, 2);
|
|
} finally { await item.runner.close(); }
|
|
}
|
|
});
|
|
|
|
test('revoked device access is unknown while malformed paired configuration remains a failure', async () => {
|
|
for (const options of [{ device: { error: 'device_revoked' }, deviceStatus: 410 }, { device: { enabled: true, state: 'paired', config: { ...managedSettings, defaultStopId: null } } }]) {
|
|
const item = fixture(options);
|
|
try {
|
|
const result = await item.runner.run(item.service);
|
|
assert.equal(result.ok, options.deviceStatus === 410 ? null : false);
|
|
assert.equal(result.reason, options.deviceStatus === 410 ? 'auth_required' : 'content');
|
|
assert.equal(item.renders.length, 0);
|
|
} finally { await item.runner.close(); }
|
|
}
|
|
});
|
|
|
|
test('only board scenario history is separated from the old public-default probe', () => {
|
|
for (const service of loadConfig({ STATUS_BOARD_TEST_COOKIE: cookie }).services.filter(item => item.scenario)) {
|
|
const { signature: _signature, ...scenario } = service.scenario;
|
|
const previousSignature = createHash('sha256').update(JSON.stringify({ ...scenario, apiKey: Boolean(scenario.apiKey), cookie: Boolean(scenario.cookie) })).digest('hex').slice(0, 24);
|
|
if (service.id === 'board') assert.notEqual(service.scenario.signature, previousSignature);
|
|
else assert.equal(service.scenario.signature, previousSignature);
|
|
}
|
|
});
|