test/auth-terminal-states.test.js
Rama master · réplica de lectura del repositorio original.
// A16 — operator terminal states are authoritative.
//
// `disabled` and `banned` are operator decisions, not health verdicts. A request
// acquired before the operator flipped the switch can report its outcome after
// it (late success/error), and background recovery (auto re-login, liveness
// probe, token refresh) can finish after the flip. None of those may promote the
// credential back into rotation: only an explicit operator reset may leave a
// terminal state. These tests pin the transition table and the post-await
// identity/status revalidation that makes the flip durable.
import { afterEach, beforeEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
__resetReauthChecks,
__resetReloginState,
__setReloginDeps,
addAccountByKey,
getAccountInternal,
probeAndRecoverConnectAccount,
reLoginAccount,
removeAccount,
reportBanSignal,
reportError,
reportSuccess,
requestReauthCheck,
} from '../src/auth.js';
let seq = 0;
const created = new Set();
function newAccount(status = 'active', label = 'terminal') {
const account = addAccountByKey(
`devin-session-token$terminal-${++seq}-${Date.now()}`,
`${label}-${seq}@example.com`,
);
account.email = `${label}-${seq}@example.com`;
account.method = 'email';
if (status !== 'active') account.status = status;
created.add(account.id);
return account;
}
function setAutoRelogin(on) {
if (on) process.env.DEVIN_CONNECT_AUTO_RELOGIN = '1';
else delete process.env.DEVIN_CONNECT_AUTO_RELOGIN;
}
beforeEach(() => {
__resetReloginState();
__resetReauthChecks();
});
afterEach(() => {
for (const id of created) removeAccount(id);
created.clear();
__setReloginDeps(null);
__resetReloginState();
__resetReauthChecks();
setAutoRelogin(false);
});
describe('late outcomes cannot override operator terminal states', () => {
for (const status of ['disabled', 'banned']) {
it(`late success leaves a ${status} account ${status}`, () => {
const account = newAccount(status, `late-success-${status}`);
account.errorCount = 3;
account._errorAt = Date.now();
if (status === 'banned') {
account.bannedAt = 123456;
account.bannedReason = 'Account suspended';
}
reportSuccess(account.apiKey);
const live = getAccountInternal(account.id);
assert.equal(live.status, status, 'a late success is not an operator reset');
if (status === 'banned') {
assert.equal(live.bannedAt, 123456, 'ban evidence survives');
assert.equal(live.bannedReason, 'Account suspended');
}
});
it(`late errors cannot walk a ${status} account into self-healing 'error'`, () => {
const account = newAccount(status, `late-error-${status}`);
for (let i = 0; i < 6; i++) reportError(account.apiKey);
const live = getAccountInternal(account.id);
assert.equal(live.status, status);
assert.equal(live.errorCount, 0, 'a terminal account accumulates no error budget');
});
it(`ban signals cannot replace an operator ${status} with 'banned'`, () => {
const account = newAccount(status, `ban-signal-${status}`);
reportBanSignal(account.apiKey, 'Account suspended');
reportBanSignal(account.apiKey, 'Invalid API key');
assert.equal(getAccountInternal(account.id).status, status);
});
}
it('CONTROL: success still promotes a self-healing error account to active', () => {
const account = newAccount('error', 'error-control');
account.errorCount = 4;
account.erroredAt = Date.now();
reportSuccess(account.apiKey);
assert.equal(getAccountInternal(account.id).status, 'active');
assert.equal(getAccountInternal(account.id).errorCount, 0);
});
it('CONTROL: a real error streak still disables an active account', () => {
const peer = newAccount('active', 'error-peer');
assert.ok(peer);
const account = newAccount('active', 'error-control-active');
for (let i = 0; i < 3; i++) reportError(account.apiKey);
assert.equal(getAccountInternal(account.id).status, 'error');
});
});
describe('automatic re-login cannot resurrect a terminal account', () => {
for (const status of ['disabled', 'banned']) {
it(`does not even attempt a login for a ${status} account`, async () => {
setAutoRelogin(true);
let loginCalls = 0;
const account = newAccount(status, `relogin-start-${status}`);
const originalKey = account.apiKey;
__setReloginDeps({
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
loginCalls++;
return { apiKey: 'devin-session-token$FRESH' };
},
});
assert.equal(await reLoginAccount(account.id), false);
assert.equal(loginCalls, 0, 'terminal accounts are out of the recovery path');
assert.equal(getAccountInternal(account.id).status, status);
assert.equal(getAccountInternal(account.id).apiKey, originalKey);
});
}
it('discards a login that finished after the operator disabled the account', async () => {
setAutoRelogin(true);
const account = newAccount('active', 'relogin-mid-disable');
const originalKey = account.apiKey;
__setReloginDeps({
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
account.status = 'disabled';
return { apiKey: 'devin-session-token$FRESH' };
},
});
assert.equal(await reLoginAccount(account.id), false);
const live = getAccountInternal(account.id);
assert.equal(live.status, 'disabled', 'status revalidated after the awaited login');
assert.equal(live.apiKey, originalKey, 'the discarded login must not install a token');
});
it('discards a login that finished after the account was removed', async () => {
setAutoRelogin(true);
const account = newAccount('active', 'relogin-mid-remove');
__setReloginDeps({
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
removeAccount(account.id);
return { apiKey: 'devin-session-token$FRESH' };
},
});
assert.equal(await reLoginAccount(account.id), false);
assert.equal(getAccountInternal(account.id), null, 'a stale result cannot recreate the row');
});
it('discards a login that finished after the operator installed a different key', async () => {
setAutoRelogin(true);
const account = newAccount('active', 'relogin-mid-key');
__setReloginDeps({
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
account.apiKey = 'devin-session-token$OPERATOR';
return { apiKey: 'devin-session-token$FRESH' };
},
});
assert.equal(await reLoginAccount(account.id), false);
assert.equal(getAccountInternal(account.id).apiKey, 'devin-session-token$OPERATOR');
});
});
describe('liveness probe cannot resurrect a terminal account', () => {
it('does not probe a disabled or banned account', async () => {
for (const status of ['disabled', 'banned']) {
const account = newAccount(status, `probe-start-${status}`);
let probeCalls = 0;
__setReloginDeps({
checkSessionLiveness: async () => {
probeCalls++;
return { alive: true };
},
});
const res = await probeAndRecoverConnectAccount(account.id);
assert.equal(probeCalls, 0);
assert.equal(res.alive, false);
assert.equal(getAccountInternal(account.id).status, status);
}
});
it('discards an ALIVE verdict that arrives after the operator disabled the account', async () => {
const account = newAccount('error', 'probe-mid-disable-alive');
account.errorCount = 4;
__setReloginDeps({
checkSessionLiveness: async () => {
account.status = 'disabled';
return { alive: true };
},
});
const res = await probeAndRecoverConnectAccount(account.id);
assert.equal(res.alive, false);
assert.equal(getAccountInternal(account.id).status, 'disabled');
assert.equal(getAccountInternal(account.id).errorCount, 4);
});
it('discards a DEAD verdict after the operator disabled the account (no error, no re-login)', async () => {
setAutoRelogin(true);
const account = newAccount('active', 'probe-mid-disable-dead');
let loginCalls = 0;
__setReloginDeps({
checkSessionLiveness: async () => {
account.status = 'disabled';
return { alive: false, code: 'UNAUTHORIZED' };
},
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
loginCalls++;
return { apiKey: 'devin-session-token$FRESH' };
},
});
const res = await probeAndRecoverConnectAccount(account.id);
assert.equal(res.alive, false);
assert.equal(loginCalls, 0);
assert.equal(getAccountInternal(account.id).status, 'disabled');
assert.equal(getAccountInternal(account.id).errorCount, 0);
});
it('discards a verdict for an account removed while the probe was in flight', async () => {
const account = newAccount('active', 'probe-mid-remove');
__setReloginDeps({
checkSessionLiveness: async () => {
removeAccount(account.id);
return { alive: true };
},
});
const res = await probeAndRecoverConnectAccount(account.id);
assert.equal(res.alive, false);
assert.equal(getAccountInternal(account.id), null);
});
it('discards a verdict for a token the operator replaced mid-probe', async () => {
const account = newAccount('error', 'probe-mid-key');
account.errorCount = 2;
__setReloginDeps({
checkSessionLiveness: async () => {
account.apiKey = 'devin-session-token$OPERATOR';
return { alive: true };
},
});
const res = await probeAndRecoverConnectAccount(account.id);
assert.equal(res.alive, false);
assert.equal(getAccountInternal(account.id).status, 'error');
assert.equal(getAccountInternal(account.id).apiKey, 'devin-session-token$OPERATOR');
});
it('does not stamp a failed re-login verdict onto a row re-added with the same id', async () => {
setAutoRelogin(true);
const account = newAccount('active', 'probe-aba');
const id = account.id;
__setReloginDeps({
checkSessionLiveness: async () => ({ alive: false, code: 'UNAUTHORIZED' }),
isCredStoreEnabled: () => true,
getCredential: () => 'pw',
windsurfLogin: async () => {
// Delete + re-add under the same id while the recovery RPC is pending.
removeAccount(id);
const replacement = addAccountByKey('devin-session-token$replacement', 'replacement@example.com');
replacement.id = id;
throw new Error('login failed after ABA');
},
});
const res = await probeAndRecoverConnectAccount(id);
assert.equal(res.alive, false);
assert.equal(res.recovered, false);
const live = getAccountInternal(id);
assert.ok(live, 'the replacement row exists');
assert.equal(live.status, 'active', 'the old probe must not mark the new row reauth');
});
});
describe('reauth probe verdicts cannot be applied to a changed row', () => {
it('discards an ALIVE verdict after a mid-probe operator disable', async () => {
const account = newAccount('active', 'reauth-probe-disable');
__setReloginDeps({
checkSessionLiveness: async () => {
account.status = 'disabled';
return { alive: true };
},
});
const res = await requestReauthCheck(account.id, { force: true });
assert.equal(res.checked, false);
assert.equal(res.reason, 'NOT_ELIGIBLE');
assert.equal(getAccountInternal(account.id).status, 'disabled');
});
it('discards an UNAUTHORIZED verdict for a row removed mid-probe', async () => {
const account = newAccount('active', 'reauth-probe-remove');
__setReloginDeps({
checkSessionLiveness: async () => {
removeAccount(account.id);
return { alive: false, code: 'UNAUTHORIZED' };
},
});
const res = await requestReauthCheck(account.id, { force: true });
assert.equal(res.checked, false);
assert.equal(res.reason, 'ACCOUNT_CHANGED');
assert.equal(getAccountInternal(account.id), null);
});
});