Ctrl K
ComunidadGuías…

test/connect-removed-account.test.js

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

Archivo original ↗Preguntar a KiBot
// A removed account's late verdict must not land on whoever holds its key now.
//
// finalizeConnectAccount resolves the account's live credential through its
// immutable id, but `currentApiKeyForId(id, fallback)` answers with the
// acquire-time snapshot when the id is gone. A request can outlive its account
// (the dashboard deletes/replaces it while the stream is in flight) and the
// replacement can legitimately be created with the SAME synthetic key — so the
// fallback re-keyed the verdict onto the replacement. A late UNAUTHORIZED then
// charged the new account's error budget; a late success cleared it.
//
// The outcome belongs to the removed identity: it must be dropped, not
// re-targeted. These are behaviour tests against the real finalizer.

import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { addAccountByKey, removeAccount, getAccountInternal } from '../src/auth.js';
import { finalizeConnectAccount } from '../src/handlers/chat.js';

const createdIds = [];

function seed(label, apiKey) {
  const key = apiKey || `devin-session-token$removed-${label}-${Math.random().toString(36).slice(2)}`;
  const acct = addAccountByKey(key, label);
  createdIds.push(acct.id);
  return acct;
}

function syntheticError(code) {
  return Object.assign(new Error(`synthetic ${code}`), { code });
}

afterEach(() => {
  while (createdIds.length) removeAccount(createdIds.pop());
});

describe('late verdict for a removed account', () => {
  it('late_outcome_for_removed_account_does_not_penalize_recreated_account', () => {
    const a = seed('removed-a');
    const key = a.apiKey;
    // What the handler holds while the request is in flight: an acquire-time
    // value snapshot ({ id, apiKey }), not the pool object.
    const snapshot = { id: a.id, apiKey: key };
    getAccountInternal(a.id)._inflight = 1; // mimic the acquire

    assert.equal(removeAccount(a.id), true, 'precondition: A was removed mid-request');

    const b = seed('recreated-b', key);
    assert.notEqual(b.id, a.id, 'precondition: B is a different identity');
    assert.equal(b.apiKey, key, 'precondition: B was created with the same synthetic key');

    finalizeConnectAccount(snapshot, {
      model: 'swe-1-6-slow',
      startTime: Date.now() - 5,
      err: syntheticError('UNAUTHORIZED'),
    });

    const live = getAccountInternal(b.id);
    assert.equal(live.errorCount || 0, 0, "A's late 401 must not charge B's error budget");
    assert.equal(live.status, 'active', 'B stays in rotation');
    assert.equal(live.rateLimitedUntil || 0, 0, 'no cooldown written onto B');
    assert.equal(live.quotaResetAt || 0, 0, 'no quota cooldown written onto B');
    assert.equal(live.internalErrorStreak || 0, 0, 'no internal-error streak written onto B');
    assert.equal(getAccountInternal(a.id), null, 'A stays gone');
  });

  it('control: the same late 401 still charges an account that was not removed', () => {
    const a = seed('kept-a');
    const snapshot = { id: a.id, apiKey: a.apiKey };
    getAccountInternal(a.id)._inflight = 1;

    finalizeConnectAccount(snapshot, {
      model: 'swe-1-6-slow',
      startTime: Date.now() - 5,
      err: syntheticError('UNAUTHORIZED'),
    });

    assert.equal(getAccountInternal(a.id).errorCount, 1,
      'the guard must be scoped to removed identities, not suppress auth faults');
    assert.equal(getAccountInternal(a.id)._inflight, 0, 'the slot is still released');
  });
});