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
114 lines
4.7 KiB
JavaScript
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 })
|
|
})
|