Ctrl K
ComunidadGuías…

test/dashboard-devin-panel-routes.test.js

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

Archivo original ↗Preguntar a KiBot
// Route contract for the DevIn panel:
//   GET /devin/panel
//
// The panel inherits the dashboard auth gate (no new exemption), reads only
// in-process state (no upstream call), and must never put a credential or an
// email in the body. It also pins the shared switch-row helper that
// /runtime-env-status and the panel now both use.

import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { config } from '../src/config.js';
import {
  addAccountByKey,
  configureBindHost,
  recordAccountRequest,
  removeAccount,
  __resetAccountRequestsForTest,
  _resetLockoutForTests,
} from '../src/auth.js';
import { accountRef } from '../src/log-safety.js';
import { handleDashboardApi } from '../src/dashboard/api.js';
import { _resetRuntimeConfigForTests } from '../src/runtime-config.js';

const originalDashboardPassword = config.dashboardPassword;
const originalApiKey = config.apiKey;
const created = new Set();

function fakeRes() {
  return {
    statusCode: 0,
    headers: {},
    body: '',
    writeHead(status, headers) { this.statusCode = status; this.headers = headers || {}; },
    end(chunk) { this.body += chunk ? String(chunk) : ''; },
    json() { return this.body ? JSON.parse(this.body) : null; },
  };
}

function req(path, headers = {}) {
  return { url: `/dashboard/api${path}`, headers, socket: { remoteAddress: '127.0.0.1' } };
}

function authed(path, extra = {}) {
  return req(path, { 'x-dashboard-password': 'dash-secret', ...extra });
}

function addAccount(label) {
  const key = `panel-key-${label}-${Date.now()}-${Math.random().toString(16).slice(2)}`;
  const account = addAccountByKey(key, label);
  created.add(account.id);
  return { account, key };
}

function gateOn() {
  _resetRuntimeConfigForTests();
  config.dashboardPassword = 'dash-secret';
  config.apiKey = '';
  configureBindHost('0.0.0.0');
}

afterEach(() => {
  for (const id of created) removeAccount(id);
  created.clear();
  __resetAccountRequestsForTest();
  _resetRuntimeConfigForTests();
  _resetLockoutForTests();
  configureBindHost('0.0.0.0');
  config.dashboardPassword = originalDashboardPassword;
  config.apiKey = originalApiKey;
});

describe('GET /devin/panel', () => {
  it('fails closed without dashboard auth', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('GET', '/devin/panel', {}, req('/devin/panel'), res);
    assert.equal(res.statusCode, 401);
  });

  it('returns the four sections with ok:true', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('GET', '/devin/panel', {}, authed('/devin/panel'), res);
    assert.equal(res.statusCode, 200);
    assert.equal(res.headers['Cache-Control'], 'no-store');
    const body = res.json();
    assert.equal(body.ok, true);
    assert.equal(typeof body.generatedAt, 'number');
    assert.ok(Array.isArray(body.runtime.switches));
    assert.equal(body.runtime.switches.length, 6);
    assert.ok(body.runtime.langServer && typeof body.runtime.langServer.running === 'boolean');
    assert.ok(body.runtime.detach && typeof body.runtime.detach.enabled === 'boolean');
    assert.ok(Array.isArray(body.sessions.sources));
    assert.equal(body.sessions.turns.available, true);
    assert.equal(typeof body.sessions.turns.totalAccounts, 'number');
    assert.equal(typeof body.sessions.turns.capped, 'boolean');
    assert.ok(Array.isArray(body.quota.knobs));
    assert.equal(body.quota.knobs.length, 5);
    assert.ok(body.quota.drought && typeof body.quota.drought.active === 'boolean');
  });

  it('merges a recorded billed turn under the hashed account ref, never the key or email', async () => {
    gateOn();
    const { account, key } = addAccount('panel-turn@example.test');
    recordAccountRequest(account, {
      model: 'swe-2',
      usage: { prompt_tokens: 7, completion_tokens: 3, total_tokens: 10 },
      creditCost: 1.5,
      acuCost: 0.25,
      now: Date.now(),
    });

    const res = fakeRes();
    await handleDashboardApi('GET', '/devin/panel', {}, authed('/devin/panel'), res);
    assert.equal(res.statusCode, 200);
    const body = res.json();
    const turns = body.sessions.turns;
    assert.equal(turns.retained, 1);
    assert.equal(turns.items.length, 1);
    const row = turns.items[0];
    assert.equal(row.accountRef, accountRef(account.id));
    assert.equal(row.model, 'swe-2');
    assert.equal(row.totalTokens, 10);
    assert.equal(row.credit, 1.5);
    assert.equal(row.acu, 0.25);
    assert.ok(!res.body.includes(key), 'the raw account key must not reach the panel body');
    assert.ok(!res.body.includes('panel-turn@example.test'), 'the account email must not reach the panel body');
  });

  it('serves the snapshot without touching the network', async () => {
    gateOn();
    const original = globalThis.fetch;
    let calls = 0;
    globalThis.fetch = (...args) => { calls++; return original(...args); };
    try {
      const res = fakeRes();
      await handleDashboardApi('GET', '/devin/panel', {}, authed('/devin/panel'), res);
      assert.equal(res.statusCode, 200);
    } finally {
      globalThis.fetch = original;
    }
    assert.equal(calls, 0, 'opening the panel must not call upstream');
  });

  it('declares the unreadable sources instead of returning empty lists', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('GET', '/devin/panel', {}, authed('/devin/panel'), res);
    const sources = res.json().sessions.sources;
    const byId = Object.fromEntries(sources.map((s) => [s.id, s]));
    assert.equal(byId.proxyTurns.available, true);
    assert.equal(byId.cascadeTrajectories.available, false);
    assert.equal(byId.cascadeTrajectories.reason, 'ls_only');
    assert.equal(byId.acpSessions.available, false);
    assert.equal(byId.acpSessions.reason, 'not_tracked');
  });

  it('reports the effective switch values and their source', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('GET', '/devin/panel', {}, authed('/devin/panel'), res);
    const switches = res.json().runtime.switches;
    for (const row of switches) {
      assert.equal(typeof row.key, 'string');
      assert.ok('value' in row);
      assert.ok(row.source === 'env' || row.source === 'config');
      assert.equal(typeof row.overridden, 'boolean');
    }
    const cred = res.json().runtime.credentials;
    assert.equal(cred.writable, false);
  });

  it('does not answer for a non-GET method', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('POST', '/devin/panel', {}, authed('/devin/panel'), res);
    assert.notEqual(res.statusCode, 200);
  });
});

describe('GET /runtime-env-status (shared switch rows)', () => {
  it('still returns flat effective values plus per-switch detail', async () => {
    gateOn();
    const res = fakeRes();
    await handleDashboardApi('GET', '/runtime-env-status', {}, authed('/runtime-env-status'), res);
    assert.equal(res.statusCode, 200);
    const body = res.json();
    assert.equal(body.devinConnect, body.switches.devinConnect.value);
    assert.equal(body.devinCliMode, body.switches.devinCliMode.value);
    for (const key of ['devinConnect', 'devinOnly', 'devinCliMode', 'allowClientTools', 'loginHostFallback', 'autoRelogin']) {
      assert.ok(body.switches[key], `${key} must be present`);
      assert.ok('value' in body.switches[key]);
      assert.ok('override' in body.switches[key]);
      assert.ok('source' in body.switches[key]);
    }
  });

  it('reports config after an override and env after clearing it', async () => {
    gateOn();
    const put = fakeRes();
    await handleDashboardApi('PUT', '/runtime-env-status', { devinConnect: true }, authed('/runtime-env-status'), put);
    assert.equal(put.statusCode, 200);
    assert.equal(put.json().switches.devinConnect.source, 'config');
    assert.equal(put.json().switches.devinConnect.value, true);

    const clear = fakeRes();
    await handleDashboardApi('PUT', '/runtime-env-status', { devinConnect: null }, authed('/runtime-env-status'), clear);
    assert.equal(clear.statusCode, 200);
    assert.equal(clear.json().switches.devinConnect.source, 'env');
    assert.equal(clear.json().switches.devinConnect.override, null);
  });
});