Ctrl K
ComunidadGuías…

test/devin-connect-relogin.test.js

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

Archivo original ↗Preguntar a KiBot
// reLoginAccount — auto-recovery for dead DEVIN_CONNECT session tokens.
//
// The session token is an opaque server-side session_id with no expiry/refresh,
// so when it dies the ONLY recovery is a fresh email/password Auth1 login. This
// covers the gating (env flag + cred store), the happy path (token swap +
// status reset), the throttle/de-dupe, and the no-credential / failure paths.
//
// Network + the encrypted store are injected via __setReloginDeps so nothing
// here touches a real endpoint or a real key.

import { afterEach, beforeEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  addAccountByKey, removeAccount, reLoginAccount,
  __setReloginDeps, __resetReloginState, __reloginGateState,
} from '../src/auth.js';

const EMAIL = 'relogin-test@example.com';
let acct;

function setEnv(on) {
  if (on) process.env.DEVIN_CONNECT_AUTO_RELOGIN = '1';
  else delete process.env.DEVIN_CONNECT_AUTO_RELOGIN;
}

beforeEach(() => {
  __resetReloginState();
  acct = addAccountByKey('devin-session-token$DEAD', EMAIL);
  acct.email = EMAIL;
  acct.method = 'email';
});

afterEach(() => {
  __setReloginDeps(null);
  __resetReloginState();
  setEnv(false);
  if (acct) removeAccount(acct.id);
});

describe('reLoginAccount — gating', () => {
  it('is a no-op when DEVIN_CONNECT_AUTO_RELOGIN is unset', async () => {
    setEnv(false);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => { loginCalls++; return { apiKey: 'devin-session-token$NEW' }; },
    });
    const res = await reLoginAccount(acct.id);
    assert.equal(res, false);
    assert.equal(loginCalls, 0, 'must not attempt login when flag off');
  });

  it('is a no-op when the credential store is disabled', async () => {
    setEnv(true);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => false,
      getCredential: () => 'pw',
      windsurfLogin: async () => { loginCalls++; return { apiKey: 'x' }; },
    });
    assert.equal(await reLoginAccount(acct.id), false);
    assert.equal(loginCalls, 0);
  });

  it('is a no-op when no stored credential exists for the email', async () => {
    setEnv(true);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => null,
      windsurfLogin: async () => { loginCalls++; return { apiKey: 'x' }; },
    });
    assert.equal(await reLoginAccount(acct.id), false);
    assert.equal(loginCalls, 0);
  });

  it('returns false for an unknown account id', async () => {
    setEnv(true);
    __setReloginDeps({ isCredStoreEnabled: () => true, getCredential: () => 'pw', windsurfLogin: async () => ({ apiKey: 'x' }) });
    assert.equal(await reLoginAccount('no-such-id'), false);
  });
});

describe('reLoginAccount — happy path', () => {
  it('swaps in the fresh token and resets account health', async () => {
    setEnv(true);
    acct.status = 'error';
    acct.errorCount = 5;
    acct._errorAt = Date.now();
    let pwSeen, emailSeen;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: (e) => { emailSeen = e; return 'the-password'; },
      windsurfLogin: async (email, pw) => { pwSeen = pw; return { apiKey: 'devin-session-token$FRESH', refreshToken: 'rt1' }; },
    });
    const res = await reLoginAccount(acct.id);
    assert.equal(res, 'devin-session-token$FRESH');
    assert.equal(acct.apiKey, 'devin-session-token$FRESH');
    assert.equal(acct.status, 'active');
    assert.equal(acct.errorCount, 0);
    assert.equal(acct.refreshToken, 'rt1');
    assert.equal(emailSeen, EMAIL);
    assert.equal(pwSeen, 'the-password');
  });

  it('returns false and keeps the old token when login throws', async () => {
    setEnv(true);
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => { throw new Error('upstream 503'); },
    });
    assert.equal(await reLoginAccount(acct.id), false);
    assert.equal(acct.apiKey, 'devin-session-token$DEAD', 'token unchanged on failure');
  });

  it('returns false when login yields no apiKey', async () => {
    setEnv(true);
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => ({ apiKey: '' }),
    });
    assert.equal(await reLoginAccount(acct.id), false);
  });

  it('treats an unusable credential (decrypt throw) as a failure, not a crash', async () => {
    setEnv(true);
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => { throw new Error('Unsupported state or unable to authenticate data'); },
      windsurfLogin: async () => ({ apiKey: 'x' }),
    });
    assert.equal(await reLoginAccount(acct.id), false);
  });
});

describe('reLoginAccount — throttle + de-dupe', () => {
  it('does not re-login twice within the cooldown', async () => {
    setEnv(true);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => { loginCalls++; return { apiKey: 'devin-session-token$N' + loginCalls }; },
    });
    await reLoginAccount(acct.id);
    await reLoginAccount(acct.id);
    assert.equal(loginCalls, 1, 'second attempt within cooldown is skipped');
  });

  it('force=true bypasses the cooldown', async () => {
    setEnv(true);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => { loginCalls++; return { apiKey: 'devin-session-token$N' + loginCalls }; },
    });
    await reLoginAccount(acct.id);
    await reLoginAccount(acct.id, { force: true });
    assert.equal(loginCalls, 2);
  });

  it('coalesces concurrent callers onto a single login', async () => {
    setEnv(true);
    let loginCalls = 0;
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => {
        loginCalls++;
        await new Promise(r => setTimeout(r, 20));
        return { apiKey: 'devin-session-token$CONC' };
      },
    });
    const [a, b, c] = await Promise.all([
      reLoginAccount(acct.id), reLoginAccount(acct.id), reLoginAccount(acct.id),
    ]);
    assert.equal(loginCalls, 1, 'concurrent callers share one login');
    assert.equal(a, 'devin-session-token$CONC');
    assert.equal(b, 'devin-session-token$CONC');
    assert.equal(c, 'devin-session-token$CONC');
  });
});

describe('reLoginAccount — global concurrency gate (C4)', () => {
  // A mass token-death event: many DIFFERENT accounts re-login at once. The
  // per-account coalescing doesn't help across accounts, so without a global
  // cap all of them would hammer the upstream login in parallel.
  it('caps simultaneous logins fleet-wide and queues the rest', async () => {
    setEnv(true);
    process.env.DEVIN_CONNECT_RELOGIN_MAX_CONCURRENT = '2';
    __resetReloginState(); // pick up the new cap

    const ids = [];
    for (let i = 0; i < 5; i++) {
      const a = addAccountByKey(`devin-session-token$DEAD${i}`, `gate-${i}@example.com`);
      a.email = `gate-${i}@example.com`;
      a.method = 'email';
      ids.push(a.id);
    }

    let active = 0;
    let peak = 0;
    const release = [];
    __setReloginDeps({
      isCredStoreEnabled: () => true,
      getCredential: () => 'pw',
      windsurfLogin: async () => {
        active++;
        peak = Math.max(peak, active);
        // Block until the test lets this login finish, so all 5 pile up against
        // the gate simultaneously and we can observe the cap.
        await new Promise(r => release.push(r));
        active--;
        return { apiKey: 'devin-session-token$NEW' };
      },
    });

    const all = Promise.all(ids.map(id => reLoginAccount(id)));
    // Let the gate admit its first batch.
    await new Promise(r => setTimeout(r, 20));
    assert.equal(active, 2, 'only MAX_CONCURRENT logins run at once');
    const gate = __reloginGateState();
    assert.equal(gate.active, 2);
    assert.equal(gate.waiting, 3, 'the other 3 are queued, not stampeding');

    // Drain: release logins one at a time; the queue should feed through.
    while (release.length) {
      release.shift()();
      await new Promise(r => setTimeout(r, 10));
    }
    const results = await all;
    assert.equal(results.filter(r => r === 'devin-session-token$NEW').length, 5, 'all 5 eventually re-login');
    assert.ok(peak <= 2, `peak concurrency ${peak} must not exceed the cap`);

    for (const id of ids) removeAccount(id);
    delete process.env.DEVIN_CONNECT_RELOGIN_MAX_CONCURRENT;
    __resetReloginState();
  });
});