test/account-tier-inference.test.js
Rama master · réplica de lectura del repositorio original.
// Tier inference must never be a demotion engine.
//
// `updateCapability` runs on every chat outcome and, when no authoritative
// GetUserStatus has landed, rewrites account.tier from the Cascade canaries. Two
// measured defects on a Pro account whose only working selectors were swe-2:
//
// 1. one successful `gemini-2.5-flash` canary flipped tier 'pro' → 'free'
// (refreshCredits sets tier from planName but does NOT set
// userStatusLastFetched, so the guard did not fire). `isConnectSelectorAllowedForAccount`
// then blocks every paid selector — i.e. the canary removed the account's only
// working route.
// 2. every canary failing (a daily-dry account: all paid selectors return
// QUOTA_EXHAUSTED) flipped tier → 'expired', which blocks everything.
//
// The rules pinned here: a plan name is positive evidence and a FLOOR; a
// non-premium success is not evidence of a downgrade; all-canaries-failed is
// "unknown", never "expired"; and unknown is never treated as free.
import { afterEach, beforeEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
__deserializeAccount,
__resetModelCatalogState,
__serializeAccounts,
__setModelCatalogDeps,
addAccountByKey,
getAccountInternal,
isConnectSelectorAllowedForAccount,
removeAccount,
tierFromPlanName,
updateCapability,
} from '../src/auth.js';
const created = [];
function seed({ tier = 'unknown', planTier = null } = {}) {
const a = addAccountByKey(`sk-tier-${Date.now()}-${Math.random().toString(36).slice(2)}`, 'tier');
created.push(a.id);
const acct = getAccountInternal(a.id);
acct.status = 'active';
acct.tier = tier;
acct.planTier = planTier;
return acct;
}
beforeEach(() => {
__setModelCatalogDeps({
disableConnectSync: true,
getCascadeModelConfigs: async () => ({ configs: [] }),
});
});
afterEach(() => {
for (const id of created.splice(0)) {
try { removeAccount(id); } catch {}
}
__resetModelCatalogState();
__setModelCatalogDeps(null);
});
describe('tierFromPlanName — plan name is evidence, silence is not', () => {
it('maps paid plans and trials to pro', () => {
for (const name of ['Pro', 'PRO', 'Teams', 'Enterprise', 'TRIAL', 'Individual', 'Premium', 'paid']) {
assert.equal(tierFromPlanName(name), 'pro', name);
}
});
it('maps an explicit free plan to free', () => {
assert.equal(tierFromPlanName('Free'), 'free');
assert.equal(tierFromPlanName('free plan'), 'free');
});
it('returns null for a missing or unrecognised plan name (never free)', () => {
for (const name of ['', null, undefined, 'Mystery Plan']) {
assert.equal(tierFromPlanName(name), null, String(name));
}
});
});
describe('inferTier via updateCapability — conservative demotion', () => {
it('does NOT demote a plan-pro account on a gemini canary success', () => {
const acct = seed({ tier: 'pro', planTier: 'pro' });
updateCapability(acct.apiKey, 'gemini-2.5-flash', true, 'success');
assert.equal(acct.tier, 'pro',
'a non-premium model succeeding is not evidence the PLAN changed');
assert.equal(isConnectSelectorAllowedForAccount(acct, 'swe-2-medium'), true,
'the account must keep its only working paid selectors');
});
it('does NOT mark a plan-pro account expired when every canary fails', () => {
const acct = seed({ tier: 'pro', planTier: 'pro' });
updateCapability(acct.apiKey, 'claude-opus-4.6', false, 'model_error');
updateCapability(acct.apiKey, 'gemini-2.5-flash', false, 'model_error');
updateCapability(acct.apiKey, 'gpt-4o-mini', false, 'model_error');
assert.equal(acct.tier, 'pro',
'a daily-dry account fails every paid canary with QUOTA_EXHAUSTED; that is not expiry');
});
it('reports unknown — not expired, not free — when canaries fail with no plan floor', () => {
const acct = seed({ tier: 'unknown', planTier: null });
updateCapability(acct.apiKey, 'gemini-2.5-flash', false, 'model_error');
assert.equal(acct.tier, 'unknown');
assert.equal(isConnectSelectorAllowedForAccount(acct, 'claude-opus-4-8-medium'), true,
'unknown stays optimistic for paid selectors (self-healing), which "free" would not');
});
it('still infers free from a free canary when no plan says otherwise', () => {
const acct = seed({ tier: 'unknown', planTier: null });
updateCapability(acct.apiKey, 'gemini-2.5-flash', true, 'success');
assert.equal(acct.tier, 'free');
assert.equal(isConnectSelectorAllowedForAccount(acct, 'claude-opus-4-8-medium'), false);
});
it('lets a paid canary promote above a free plan floor', () => {
const acct = seed({ tier: 'free', planTier: 'free' });
updateCapability(acct.apiKey, 'claude-opus-4.6', true, 'success');
assert.equal(acct.tier, 'pro',
'positive evidence of a paid model outranks a stale free plan name');
});
it('a free plan floor does not block the free inference', () => {
const acct = seed({ tier: 'free', planTier: 'free' });
updateCapability(acct.apiKey, 'gemini-2.5-flash', true, 'success');
assert.equal(acct.tier, 'free');
});
});
describe('planTier persistence — the floor survives a restart', () => {
it('round-trips a plan-derived floor', () => {
const acct = seed({ tier: 'pro', planTier: 'pro' });
const record = __serializeAccounts().find((row) => row.id === acct.id);
assert.equal(record.planTier, 'pro');
const restored = __deserializeAccount(record);
assert.equal(restored.planTier, 'pro');
assert.equal(restored.tier, 'pro');
});
it('loads an older accounts.json without a floor as null, not free', () => {
const restored = __deserializeAccount({ apiKey: 'legacy-tier-record', email: 'x@y.com' });
assert.equal(restored.planTier, null,
'unknown is not free — a missing floor must not become a downgrade');
assert.equal(restored.tier, 'unknown');
});
});