Files
Module-Rust/server/test/liveStatus.test.js
wtclaude 3aa142bf06
All checks were successful
PR Checks / client-build (pull_request) Successful in 24s
PR Checks / server-tests (pull_request) Successful in 32s
PR Checks / frozen-manifest (pull_request) Successful in 44s
fix(rust): the last fixes' small ones (W3 W6 W7/W11 W8 W10 W13-W18)
Stage 2 of PLAN_LAST_FIXES (docs#366).

- W7/W11: an "is it loaded" refusal is confirmed live before it is given.
  New liveStatus.js asks the sidecar once (boot.refreshOne, as a poll tick
  would), at most once per server per 5 s and waiting at most 3 s, then
  reads the stored status again. Used by Economics (step and staff page),
  Backpacks, Dangerous Treasures, Guarded Crate, RaidableBases, ZoneDomes,
  TruePVE and RunicNPC.
- W3: a world call answered `already-out` (the bridge half comes in stage
  3) succeeds as called 0, saying a Travelling Vendor is already out.
- W6: "A locked crate dropped by a Chinook is being hacked."
- W8: a RaidableBases profile's audit row names RaidableBases, not "@data".
- W10: the backpack sizes picker lists each size once, naming its servers.
- W13: a RaidableBases name both servers describe differently shows each
  server's own description.
- W14: each RaidableBases note after the first starts its own sentence.
- W15: a staff deposit refused at Economics' limit answers 409.
- W16: a payment's failedNote explains only the reasons that happened.
- W17: the sync state counts player grants that did not land
  (waitingToLand), and the Permissions page says "in sync · 1 waits for
  their player to join" instead of plain "in sync".
- W18: Guarded Crate's kept note drops the "30 minutes" it could not know.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
2026-10-21 07:42:24 -05:00

114 lines
4.7 KiB
JavaScript

// ── Asking a server how it is now, before saying no (W7, W11) ──────────────
//
// Each test is one way the feature could look right and be wrong:
//
// a "no" from the stored status is asked again, and the fresh answer is the one acted on
// a "yes" is never asked again: the plugin refuses at the run, as it always has
// a "no" the sidecar cannot change is still a "no"
// ten steps against one server in one dry run ask it once
// a step refused for Backpacks just after a restart passes once the server says it is back
const test = require('node:test')
const assert = require('node:assert')
const { fakeCtx } = require('./_fakes')
require('../core')._reset()
require('../core').init(fakeCtx({ db: { query: () => Promise.resolve([]), pool: {} } }))
const boot = require('../boot')
const live = require('../liveStatus')
const MAIN = { id: 'main', name: 'Main', baseUrl: 'http://main:1', token: 't' }
/** `boot.refreshOne` replaced by `onAsk`, counted. */
function stubAsk(t, onAsk = () => {}) {
const saved = boot.refreshOne
const asked = []
live._reset()
boot.refreshOne = async (server) => {
asked.push(server.id)
await onAsk(server)
}
t.after(() => {
boot.refreshOne = saved
live._reset()
})
return asked
}
test('a refusal from the stored status is asked again, and the fresh answer is the one acted on', async (t) => {
let stored = { loaded: false }
const asked = stubAsk(t, () => {
stored = { loaded: true }
})
const answer = await live.confirmed(MAIN, async () => stored, (r) => !r.loaded)
assert.deepStrictEqual(answer, { loaded: true })
assert.deepStrictEqual(asked, ['main'])
})
test('a pass is never asked again', async (t) => {
const asked = stubAsk(t)
const answer = await live.confirmed(MAIN, async () => ({ loaded: true }), (r) => !r.loaded)
assert.deepStrictEqual(answer, { loaded: true })
assert.deepStrictEqual(asked, [])
})
test('a refusal the fresh answer agrees with is still a refusal, and a failed ask leaves the stored one', async (t) => {
stubAsk(t)
assert.strictEqual(await live.confirmed(MAIN, async () => 'Backpacks is not installed', Boolean), 'Backpacks is not installed')
stubAsk(t, () => {
throw new Error('boom')
})
assert.strictEqual(await live.refresh(MAIN), false)
assert.strictEqual(await live.confirmed(MAIN, async () => 'still no', Boolean), 'still no')
})
test('many refusals against one server at once ask it once, and again only after a while', async (t) => {
let release
const gate = new Promise((resolve) => {
release = resolve
})
const asked = stubAsk(t, () => gate)
const all = Promise.all(Array.from({ length: 10 }, () => live.confirmed(MAIN, async () => 'no', Boolean)))
release()
await all
assert.deepStrictEqual(asked, ['main'], 'one ask in flight serves every step')
await live.confirmed(MAIN, async () => 'no', Boolean)
assert.deepStrictEqual(asked, ['main'], `an ask within ${live.FRESH_MS} ms is as fresh as asking again`)
await live.confirmed({ ...MAIN, id: 'pve' }, async () => 'no', Boolean)
assert.deepStrictEqual(asked, ['main', 'pve'], 'another server is its own')
})
test('a step waits at most ASK_MS for the ask, and a late ask leaves the stored answer', async (t) => {
t.mock.timers.enable({ apis: ['setTimeout'] })
stubAsk(t, () => new Promise(() => {}))
const answer = live.confirmed(MAIN, async () => 'not installed', Boolean)
// Let the first read and the ask start before the clock moves.
for (let i = 0; i < 5; i++) await Promise.resolve()
t.mock.timers.tick(live.ASK_MS)
assert.strictEqual(await answer, 'not installed')
})
test('a bigger backpack refused on a stale "not installed" passes once the server says Backpacks is back (W7)', async (t) => {
const rewards = require('../eventRewards')
const serversDb = require('../model/servers/servers.db')
const backpacksDb = require('../model/backpacks/backpacks.db')
const saved = { getServer: serversDb.getServer, reportFor: backpacksDb.reportFor }
t.after(() => Object.assign(serversDb, { getServer: saved.getServer }) && Object.assign(backpacksDb, { reportFor: saved.reportFor }))
serversDb.getServer = async (id) => (id === 'main' ? { id: 'main', name: 'Main', enabled: 1, sidecarBaseUrl: 'http://main:1' } : null)
let report = null
backpacksDb.reportFor = async () => ({ serverId: 'main', backpacks: report })
const asked = stubAsk(t, () => {
report = JSON.stringify({ loaded: true, sizes: [6, 48] })
})
const step = rewards.ACTIONS.find((a) => a.id === 'rust.backpack.size')
const result = await step.perform({ runId: 7, stepId: 1, idempotencyKey: '7:1', params: { server: 'main', size: 48, recipients: 'linked' }, verify: true })
assert.deepStrictEqual(asked, ['main'])
assert.deepStrictEqual(result, { ok: true })
})