💰 fix: Lazy-Initialize Balance Record at Check Time for Overrides (#12474)

* fix: Lazy-initialize balance record when missing at check time

When balance is configured via admin panel DB overrides, users with
existing sessions never pass through the login middleware that creates
their balance record. This causes checkBalanceRecord to find no record
and return balance: 0, blocking the user.

Add optional balanceConfig and upsertBalanceFields deps to
CheckBalanceDeps. When no balance record exists but startBalance is
configured, lazily create the record instead of returning canSpend: false.

Pass the new deps from BaseClient, chatV1, and chatV2 callers.

* test: Add checkBalance lazy initialization tests

Cover lazy balance init scenarios: successful init with startBalance,
insufficient startBalance, missing config fallback, undefined
startBalance, missing upsertBalanceFields dep, and startBalance of 0.

* fix: Address review findings for lazy balance initialization

- Use canonical BalanceConfig and IBalanceUpdate types from
  @librechat/data-schemas instead of inline type definitions
- Include auto-refill fields (autoRefillEnabled, refillIntervalValue,
  refillIntervalUnit, refillAmount, lastRefill) during lazy init,
  mirroring the login middleware's buildUpdateFields logic
- Add try/catch around upsertBalanceFields with graceful fallback to
  canSpend: false on DB errors
- Read balance from DB return value instead of raw startBalance constant
- Fix misleading test names to describe observable throw behavior
- Add tests: upsertBalanceFields rejection, auto-refill field inclusion,
  DB-returned balance value, and logViolation assertions

* fix: Address second review pass findings

- Fix import ordering: package type imports before local type imports
- Remove misleading comment on DB-fallback test, rename for clarity
- Add logViolation assertion to insufficient-balance lazy-init test
- Add test for partial auto-refill config (autoRefillEnabled without
  required dependent fields)

* refactor: Replace createMockReqRes factory with describe-scoped consts

Replace zero-argument factory with plain const declarations using
direct type casts instead of double-cast through unknown.

* fix: Sort local type imports longest-first, add missing logViolation assertion

- Reorder local type imports in spec file per AGENTS.md (longest to
  shortest within sub-group)
- Add logViolation assertion to startBalance: 0 test for consistent
  violation payload coverage across all throw paths
This commit is contained in:
Danny Avila 2026-03-30 22:51:07 -04:00 committed by GitHub
parent 4f37e8adb9
commit fd01dfc083
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 323 additions and 3 deletions

View file

@ -40,6 +40,7 @@ const { sendResponse } = require('~/server/middleware/error');
const {
createAutoRefillTransaction,
findBalanceByUser,
upsertBalanceFields,
getTransactions,
getMultiplier,
getConvo,
@ -296,7 +297,14 @@ const chatV1 = async (req, res) => {
amount: promptTokens,
},
},
{ findBalanceByUser, getMultiplier, createAutoRefillTransaction, logViolation },
{
findBalanceByUser,
getMultiplier,
createAutoRefillTransaction,
logViolation,
balanceConfig,
upsertBalanceFields,
},
);
};

View file

@ -37,6 +37,7 @@ const {
getMultiplier,
getTransactions,
findBalanceByUser,
upsertBalanceFields,
createAutoRefillTransaction,
} = require('~/models');
const { logViolation, getLogStores } = require('~/cache');
@ -169,7 +170,14 @@ const chatV2 = async (req, res) => {
amount: promptTokens,
},
},
{ findBalanceByUser, getMultiplier, createAutoRefillTransaction, logViolation },
{
findBalanceByUser,
getMultiplier,
createAutoRefillTransaction,
logViolation,
balanceConfig,
upsertBalanceFields,
},
);
};