test/auth-reserve-inherit.test.js
Rama master · réplica de lectura del repositorio original.
// V-17 (audit CORE-004) — the per-account on-demand reserve has a documented
// third state: `onDemandReserve: null` = "inherit the global runtime-config
// default" (setAccountSpendPolicy's own contract, and the dashboard's
// `reserveOverride: null` display). `Number(null) === 0` and `Number('') === 0`,
// so the old `Number.isFinite(Number(x))` guard silently converted inherit into
// an EXPLICIT zero reserve on resolve, on save and on load: a configured global
// floor of $25 became "spend the balance to the last cent", and every save
// materialized the corruption into accounts.json.
//
// Invariant pinned here: only an explicit finite number is a reserve. null /
// undefined / '' are "no override", and that meaning survives a serialize →
// deserialize round trip.
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
addAccountByKey, removeAccount, applyQuotaSnapshot, setAccountSpendPolicy,
getAccountSpendState, __serializeAccounts, __deserializeAccount,
} from '../src/auth.js';
const created = [];
const ENV_KEYS = ['WINDSURFAPI_ON_DEMAND_RESERVE_USD', 'WINDSURFAPI_SPEND_ON_DEMAND'];
const savedEnv = {};
function add(label) {
const account = addAccountByKey(
`v17-key-${Date.now()}-${created.length}-${Math.random().toString(36).slice(2)}`,
label,
);
created.push(account.id);
return account;
}
function persisted(account) {
const record = __serializeAccounts().find(a => a.apiKey === account.apiKey);
assert.ok(record, 'account is serialized');
return record;
}
afterEach(() => {
for (const k of ENV_KEYS) {
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
delete savedEnv[k];
}
while (created.length) removeAccount(created.pop());
});
describe('V-17 — on-demand reserve: null means inherit, never zero', () => {
it('reserve:null round-trips as inherit and is not coerced to zero', () => {
const acct = add('v17-roundtrip');
setAccountSpendPolicy(acct.id, { reserve: null });
assert.equal(acct.onDemandReserve, null, 'clear leaves the in-memory override absent');
const record = persisted(acct);
assert.equal(Object.hasOwn(record, 'onDemandReserve'), false,
'an inherited reserve is not materialized into accounts.json as an explicit 0');
assert.equal(__deserializeAccount(record).onDemandReserve, null,
'a restart must not turn inherit into an explicit zero override');
// Older/hand-edited files can still carry the null explicitly.
assert.equal(__deserializeAccount({ apiKey: 'k1', onDemandReserve: null }).onDemandReserve, null,
'persisted null stays inherit');
assert.equal(__deserializeAccount({ apiKey: 'k2', onDemandReserve: '' }).onDemandReserve, null,
'an empty string is absence, not zero');
assert.equal(__deserializeAccount({ apiKey: 'k3' }).onDemandReserve, null,
'an absent field is inherit');
});
it('an inherited reserve resolves to the configured global floor (not zero)', () => {
savedEnv.WINDSURFAPI_ON_DEMAND_RESERVE_USD = process.env.WINDSURFAPI_ON_DEMAND_RESERVE_USD;
process.env.WINDSURFAPI_ON_DEMAND_RESERVE_USD = '25';
const acct = add('v17-inherit-global');
setAccountSpendPolicy(acct.id, { reserve: null }); // the documented "clear" state
acct.credits = { balance: 10 }; // under the $25 global floor
const now = 20_000_000;
const state = getAccountSpendState(acct, now);
assert.equal(state.reserve, 25, 'null override inherits the global reserve of $25');
assert.equal(state.reserveOverride, null, 'the dashboard reports "inherit", not an override of 0');
applyQuotaSnapshot(acct, 0, now); // included quota dry, balance under the floor
assert.ok(acct.quotaResetAt > now,
'a balance under the inherited floor is dry → cooled (never burned to zero)');
});
it('an explicit zero reserve still means zero (control)', () => {
savedEnv.WINDSURFAPI_ON_DEMAND_RESERVE_USD = process.env.WINDSURFAPI_ON_DEMAND_RESERVE_USD;
process.env.WINDSURFAPI_ON_DEMAND_RESERVE_USD = '25';
const acct = add('v17-explicit-zero');
acct.credits = { balance: 10 };
setAccountSpendPolicy(acct.id, { reserve: 0 });
const state = getAccountSpendState(acct, 20_100_000);
assert.equal(state.reserve, 0, 'an explicit numeric zero beats the global floor');
assert.equal(state.reserveOverride, 0);
const record = persisted(acct);
assert.equal(record.onDemandReserve, 0, 'an explicit zero IS persisted');
assert.equal(__deserializeAccount(record).onDemandReserve, 0,
'an explicit zero survives the round trip');
applyQuotaSnapshot(acct, 0, 20_100_000);
assert.ok(!acct.quotaResetAt, 'reserve 0 + funded balance keeps serving (historical default)');
});
it('a real numeric override survives the round trip (control)', () => {
const acct = add('v17-numeric');
setAccountSpendPolicy(acct.id, { reserve: 12.5 });
const record = persisted(acct);
assert.equal(record.onDemandReserve, 12.5);
assert.equal(__deserializeAccount(record).onDemandReserve, 12.5);
assert.equal(__deserializeAccount({ apiKey: 'k4', onDemandReserve: '7.5' }).onDemandReserve, 7.5,
'a numeric string from the dashboard form still parses');
});
it('non-numeric input is rejected instead of being coerced to zero', () => {
const acct = add('v17-garbage');
setAccountSpendPolicy(acct.id, { reserve: null }); // documented inherit state
for (const bad of [false, true, 'false', 'abc', [], {}, NaN, Infinity]) {
setAccountSpendPolicy(acct.id, { reserve: bad });
assert.equal(acct.onDemandReserve, null,
`${JSON.stringify(bad)} must not become an explicit reserve (Number(false) === 0)`);
}
assert.equal(__deserializeAccount({ apiKey: 'k5', onDemandReserve: false }).onDemandReserve, null,
'a persisted boolean is not a reserve');
});
});