import test from 'node:test'; import assert from 'node:assert/strict'; import { loadConfig } from '../server/config.mjs'; import { openStore } from '../server/store.mjs'; import { nextState } from '../server/probe.mjs'; import { makeSnapshot } from '../server/snapshot.mjs'; import { widgetOptions, widgetSummary } from '../server/widget.mjs'; import { createStatusServer } from '../server/index.mjs'; import { readFileSync } from 'node:fs'; import { runInNewContext } from 'node:vm'; import { widgetCopy } from '../server/widget.mjs'; const config = loadConfig({ RIDEBUS_API_KEY: 'private-api-key', STATUS_BOARD_TEST_COOKIE: 'private-cookie', STATUS_FAILURE_THRESHOLD: '1' }); const website = config.services.find(service => service.id === 'website'); const api = config.services.find(service => service.id === 'api'); const good = { ok: true, latencyMs: 100, httpCode: 200, reason: null }; const bad = { ok: false, latencyMs: null, httpCode: 503, reason: 'http' }; const now = Date.now(); function record(store, service, result, time = now) { store.record(service, result, nextState(store.state(service), result, config), time); } test('widget uses only selected services and never publishes credentials or transport data', () => { const store = openStore(':memory:'); try { record(store, website, good); record(store, config.services.find(service => service.id === 'board'), bad); const result = widgetSummary(store, config, ['website'], now); assert.equal(result.overall, 'operational'); assert.deepEqual(result.services, [{ id: 'website', status: 'operational' }]); assert.doesNotMatch(JSON.stringify(result), /private-api-key|private-cookie|signature|baseUrl|stopId|latencyMs|httpCode/); assert.equal(widgetSummary(store, config, ['board'], now).overall, 'down'); } finally { store.close(); } }); test('widget cannot keep a stale successful check green and advertises its next expiry', () => { const store = openStore(':memory:'); try { record(store, website, good); const deadline = now + config.staleAfterSeconds * 1000; assert.equal(widgetSummary(store, config, ['website'], deadline).overall, 'operational'); assert.equal(widgetSummary(store, config, ['website'], deadline).expiresAt, deadline + 1); assert.equal(widgetSummary(store, config, ['website'], deadline + 1).overall, 'unknown'); assert.equal(widgetSummary(store, config, ['website'], now).expiresAt, now + 90000); } finally { store.close(); } }); test('journey failure and maintenance in the widget agree with the full status page', () => { const store = openStore(':memory:'); try { record(store, api, good); store.recordScenario(api, { ...bad, steps: [], mode: 'http' }, nextState(null, bad, config), now); assert.equal(widgetSummary(store, config, ['api'], now).overall, 'degraded'); assert.equal(widgetSummary(store, config, ['api'], now).services[0].status, makeSnapshot(store, config, 7, now).services.find(service => service.id === 'api').status); store.createMaintenance({ serviceIds: ['api'], titleRu: 'Работы', titleEn: 'Work', messageRu: 'Текст', messageEn: 'Text', startsAt: now + 1000, endsAt: now + 5000 }, now); assert.equal(widgetSummary(store, config, ['api'], now).expiresAt, now + 1000); assert.equal(widgetSummary(store, config, ['api'], now + 1000).overall, 'maintenance'); assert.equal(widgetSummary(store, config, ['api'], now + 1000).expiresAt, now + 5000); assert.equal(widgetSummary(store, config, ['api'], now + 5000).overall, 'degraded'); } finally { store.close(); } }); test('widget option validation rejects unknown services, duplicates, demo and unsafe presentation', () => { assert.deepEqual(widgetOptions(new URLSearchParams('services=website,api&lang=en&theme=dark'), config.services, { embed: true }), { serviceIds: ['website', 'api'], locale: 'en', theme: 'dark' }); for (const query of ['services=missing', 'services=', 'services=api,api', 'services=api&services=website', 'demo=1', 'lang=fr', 'theme=