Ctrl K
ComunidadGuías…

test/auth-reserve-inherit.test.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
// 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');
  });
});