Ctrl K
ComunidadGuías…

test/account-tier-inference.test.js

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

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