// ── The servers model ───────────────────────────────────────────────────── // // No database and no express: the model takes rows and produces the shapes the // three tiers answer with, which is the whole reason the SQL lives in a separate // file from the logic. // // Two things here are worth more than the rest: **a token never leaves this // module**, and **a stale row cannot claim a server is up**. const test = require('node:test') const assert = require('node:assert') const { fakeCtx } = require('./_fakes') function withCore(ctx = fakeCtx()) { require('../core')._reset() require('../core').init(ctx) return ctx } const NOW = Date.parse('2026-09-15T12:00:00Z') const serverRow = (over = {}) => ({ id: 'main', name: 'Main · Vanilla', sidecarBaseUrl: 'http://10.0.0.5:8090', sidecarTokenEnc: 'enc:s3cret', protocol: 1, enabled: 1, sortOrder: 0, ...over, }) const stateRow = (over = {}) => ({ serverId: 'main', reachable: 1, online: 1, players: 42, maxPlayers: 100, hostname: 'Runic Gateway · Main', level: 'Procedural Map', seed: 1234, worldSize: 4000, bootId: 'boot-20260915T194502Z', protocol: 1, updatedAt: new Date(NOW - 10_000).toISOString(), ...over, }) test('a fresh row reports what the server said', () => { withCore() const servers = require('../model/servers/servers.model') const shaped = servers.shapePublic(serverRow(), stateRow(), NOW) assert.strictEqual(shaped.online, true) assert.strictEqual(shaped.players, 42) assert.strictEqual(shaped.stale, false) assert.strictEqual(shaped.worldSize, 4000) }) test('a stale row is reported offline, with no player count', () => { withCore() const servers = require('../model/servers/servers.model') // The row says what was true when it was written and nothing has written it // since. Reporting its player count would put a number on a page that is // simply the last number anyone saw, with no way for a reader to tell. const old = stateRow({ updatedAt: new Date(NOW - servers.STALE_AFTER_MS - 1000).toISOString() }) const shaped = servers.shapePublic(serverRow(), old, NOW) assert.strictEqual(shaped.stale, true) assert.strictEqual(shaped.online, false) assert.strictEqual(shaped.players, 0) }) test('a server with no state row at all is stale rather than absent', () => { withCore() const servers = require('../model/servers/servers.model') // A configured server nothing has polled yet. It belongs on the page — an // operator added it on purpose — and it must not claim to be online. const shaped = servers.shapePublic(serverRow(), undefined, NOW) assert.strictEqual(shaped.id, 'main') assert.strictEqual(shaped.stale, true) assert.strictEqual(shaped.online, false) assert.strictEqual(shaped.updatedAt, null) }) test('the public shape carries nothing about the sidecar', () => { withCore() const servers = require('../model/servers/servers.model') const shaped = servers.shapePublic(serverRow(), stateRow(), NOW) // Asserted over the WHOLE object rather than by naming the two fields that // would be worst: the failure this guards against is a field added later, by // someone who did not read this file, and an allowlist is the only assertion // that catches one. assert.deepStrictEqual(Object.keys(shaped).sort(), [ 'hostname', 'id', 'level', 'maxPlayers', 'name', 'online', 'players', 'seed', 'stale', 'updatedAt', 'worldSize', ]) }) test('the admin shape reports whether a token is stored, never the token', () => { withCore() const servers = require('../model/servers/servers.model') // `listForAdmin` reads the database, so the shape is asserted through the piece // that does not: the rule is that `hasToken` is a boolean and no key anywhere // in the object holds the ciphertext or the plaintext. const row = serverRow() const shaped = { ...servers.shapePublic(row, stateRow(), NOW), sidecarBaseUrl: row.sidecarBaseUrl, hasToken: Boolean(row.sidecarTokenEnc), } assert.strictEqual(shaped.hasToken, true) const serialised = JSON.stringify(shaped) assert.ok(!serialised.includes('s3cret'), 'the plaintext token reached a response shape') assert.ok(!serialised.includes('enc:'), 'the stored ciphertext reached a response shape') }) test('a token round-trips through the box, and an empty one means “leave it alone”', () => { withCore() const servers = require('../model/servers/servers.model') const enc = servers.encryptToken('s3cret') assert.notStrictEqual(enc, 's3cret') assert.strictEqual(servers.withToken(serverRow({ sidecarTokenEnc: enc })).token, 's3cret') // All three spellings of "the operator did not type a new token". The admin // form can only ever show a blank field, so it posts one on every save that did // not intend to change the credential — and writing that through would erase // the token every time somebody renamed a server. assert.strictEqual(servers.encryptToken(''), null) assert.strictEqual(servers.encryptToken(null), null) assert.strictEqual(servers.encryptToken(undefined), null) }) test('a token that will not decrypt reports the server unconfigured rather than throwing', () => { const ctx = withCore() const servers = require('../model/servers/servers.model') // The usual cause is a `SECRET_ENC_KEY` that changed. One server's unreadable // credential must not be able to fail the poll for the other five, and it must // not fail `onBoot` — which would make the whole module `startup_failed`. const shaped = servers.withToken(serverRow({ sidecarTokenEnc: 'not-encrypted-by-this-box' })) assert.strictEqual(shaped.token, null) assert.strictEqual(shaped.baseUrl, 'http://10.0.0.5:8090') const errors = ctx.logs.flatMap((l) => l.log.error.calls) assert.strictEqual(errors.length, 1, 'the failure was swallowed without a word') }) test('a server with no token stored reads as having none', () => { withCore() const servers = require('../model/servers/servers.model') assert.strictEqual(servers.withToken(serverRow({ sidecarTokenEnc: null })).token, null) })