test/connect-removed-account.test.js
Rama master · réplica de lectura del repositorio original.
// 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');
});
});