Ctrl K
ComunidadGuías…

test/dashboard-devin-panel.test.js

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

Archivo original ↗Preguntar a KiBot
// Unit contract for the DevIn panel builders (src/dashboard/devin-panel.js).
//
// The panel's whole claim is "no fabricated data": every section is either a
// real in-process fact or an explicit `available:false` + reason. These tests
// pin that claim at the builder boundary, including the two places it is easy
// to lose: a merged turn list that silently drops the ring total, and a
// "sessions" array that returns empty instead of saying why.

import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import {
  buildDevinPanel,
  buildQuotaSection,
  buildRuntimeSection,
  buildSessionsSection,
  buildSessionSources,
  buildTurnsSection,
  MAX_SCAN_ACCOUNTS,
  QUOTA_KNOB_KEYS,
  RUNTIME_SWITCH_KEYS,
  SOURCE_REASONS,
  TURN_ROW_CAP,
  TURN_WINDOW_MS,
} from '../src/dashboard/devin-panel.js';

const NOW = 1_700_000_000_000;

function page(items, summary = {}) {
  const totals = items.reduce((acc, row) => ({
    promptTokens: acc.promptTokens + row.promptTokens,
    completionTokens: acc.completionTokens + row.completionTokens,
    totalTokens: acc.totalTokens + row.totalTokens,
    credit: acc.credit + row.credit,
    acu: acc.acu + row.acu,
  }), { promptTokens: 0, completionTokens: 0, totalTokens: 0, credit: 0, acu: 0 });
  return {
    items,
    total: items.length,
    capacity: 100,
    summary: { requests: items.length, ...totals, lastTs: items.length ? items[0].ts : 0, ...summary },
  };
}

function row(ts, extra = {}) {
  return {
    ts,
    model: 'swe-2',
    promptTokens: 10,
    completionTokens: 5,
    totalTokens: 15,
    credit: 1,
    acu: 0.5,
    ...extra,
  };
}

describe('buildTurnsSection', () => {
  it('merges accounts newest-first and sums each ring summary', () => {
    const rings = {
      a1: page([row(NOW - 1_000), row(NOW - 60_000)]),
      a2: page([row(NOW - 30_000, { model: 'swe-2-max' })]),
    };
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }, { id: 'a2', ref: 'acct-22222222' }],
      readRequests: (id) => rings[id],
      now: NOW,
    });
    assert.equal(turns.available, true);
    assert.equal(turns.scanned, 2);
    assert.equal(turns.accountsWithTurns, 2);
    assert.equal(turns.retained, 3);
    assert.equal(turns.summary.totalTokens, 45);
    assert.equal(turns.summary.credit, 3);
    assert.equal(turns.summary.acu, 1.5);
    assert.deepEqual(turns.items.map((r) => r.ts), [NOW - 1_000, NOW - 30_000, NOW - 60_000]);
    assert.equal(turns.items[1].model, 'swe-2-max');
  });

  it('keeps failed attempts out of the billed-turn view', () => {
    const failed = row(NOW - 5_000, {
      billed: false, promptTokens: 0, completionTokens: 0, totalTokens: 0, credit: 0, acu: 0,
      outcome: 'rate_limited', status: 429,
    });
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      readRequests: () => ({
        items: [failed, row(NOW - 10_000)],
        total: 2,
        billed: 1,
        failed: 1,
        capacity: 100,
        summary: { requests: 1, promptTokens: 10, completionTokens: 5, totalTokens: 15, credit: 1, acu: 0.5, lastTs: NOW - 10_000 },
      }),
      now: NOW,
    });
    assert.equal(turns.retained, 1, 'the ring total that counts billed turns comes from summary.requests');
    assert.equal(turns.accountsWithTurns, 1);
    assert.equal(turns.window.turns, 1, 'a failed attempt is not a billed turn');
    assert.equal(turns.items.length, 1);
    assert.equal(turns.items[0].ts, NOW - 10_000);
  });

  it('counts the trailing window from the rows it actually read', () => {
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      readRequests: () => page([
        row(NOW - 10),
        row(NOW - TURN_WINDOW_MS + 1),
        row(NOW - TURN_WINDOW_MS - 1),
      ]),
      now: NOW,
    });
    assert.equal(turns.window.ms, TURN_WINDOW_MS);
    assert.equal(turns.window.turns, 2);
    assert.equal(turns.window.totalTokens, 30);
    // The retained total still covers every row, window or not.
    assert.equal(turns.retained, 3);
  });

  it('caps the rendered rows without lying about the retained total', () => {
    const many = Array.from({ length: 80 }, (_, i) => row(NOW - i * 1_000));
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      readRequests: () => page(many),
      now: NOW,
      rowCap: 5,
    });
    assert.equal(turns.items.length, 5);
    assert.equal(turns.shown, 5);
    assert.equal(turns.retained, 80);
  });

  it('reports the pool size and the cap instead of implying a full scan', () => {
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      totalAccounts: 600,
      readRequests: () => page([row(NOW - 1_000)]),
      now: NOW,
    });
    assert.equal(turns.scanned, 1);
    assert.equal(turns.totalAccounts, 600);
    assert.equal(turns.capped, true);
    const full = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      totalAccounts: 1,
      readRequests: () => page([row(NOW - 1_000)]),
      now: NOW,
    });
    assert.equal(full.capped, false);
  });

  it('survives a broken reader and a missing page without failing the panel', () => {
    const turns = buildTurnsSection({
      accounts: [{ id: 'boom', ref: 'acct-bad' }, { id: 'missing', ref: 'acct-null' }, { id: 'ok', ref: 'acct-ok' }],
      readRequests: (id) => {
        if (id === 'boom') throw new Error('ring exploded');
        if (id === 'missing') return null;
        return page([row(NOW - 5)]);
      },
      now: NOW,
    });
    assert.equal(turns.scanned, 3);
    assert.equal(turns.accountsWithTurns, 1);
    assert.equal(turns.items.length, 1);
    assert.equal(turns.items[0].accountRef, 'acct-ok');
  });

  it('drops rows without a finite timestamp and truncates hostile model text', () => {
    const turns = buildTurnsSection({
      accounts: [{ id: 'a1', ref: 'acct-11111111' }],
      readRequests: () => page([
        row(Number.NaN),
        { ts: NOW - 1, model: `m${'x'.repeat(400)}`, promptTokens: 1, completionTokens: 1, totalTokens: 2, credit: 0, acu: 0 },
      ]),
      now: NOW,
    });
    assert.equal(turns.items.length, 1);
    assert.equal(turns.items[0].model.length, 120);
  });

  it('never exposes the raw account id, only the hashed ref', () => {
    const turns = buildTurnsSection({
      accounts: [{ id: 'raw-account-id-1234', ref: 'acct-abcdef12' }],
      readRequests: () => page([row(NOW - 1)]),
      now: NOW,
    });
    const json = JSON.stringify(turns);
    assert.ok(!json.includes('raw-account-id-1234'));
    assert.equal(turns.items[0].accountRef, 'acct-abcdef12');
  });

  it('returns an available, empty section when there are no accounts', () => {
    const turns = buildTurnsSection({ accounts: [], readRequests: () => null, now: NOW });
    assert.equal(turns.available, true);
    assert.equal(turns.scanned, 0);
    assert.equal(turns.retained, 0);
    assert.deepEqual(turns.items, []);
    assert.equal(turns.window.turns, 0);
  });
});

describe('buildRuntimeSection', () => {
  it('whitelists switch keys and reports source + override', () => {
    const runtime = buildRuntimeSection({
      switches: {
        devinConnect: { value: true, override: true, source: 'config' },
        devinCliMode: { value: 'acp', override: null, source: 'env' },
        somethingElse: { value: 1, override: 1, source: 'config' },
      },
    });
    assert.deepEqual(runtime.switches.map((s) => s.key), [...RUNTIME_SWITCH_KEYS]);
    const connect = runtime.switches.find((s) => s.key === 'devinConnect');
    assert.deepEqual(connect, { key: 'devinConnect', value: true, source: 'config', overridden: true });
    const cli = runtime.switches.find((s) => s.key === 'devinCliMode');
    assert.deepEqual(cli, { key: 'devinCliMode', value: 'acp', source: 'env', overridden: false });
    assert.ok(!runtime.switches.some((s) => s.key === 'somethingElse'));
  });

  it('defaults every switch to null/env when the caller passes nothing', () => {
    const runtime = buildRuntimeSection();
    assert.equal(runtime.switches.length, RUNTIME_SWITCH_KEYS.length);
    assert.ok(runtime.switches.every((s) => s.value === null && s.source === 'env' && s.overridden === false));
    assert.equal(runtime.langServer.running, false);
    assert.equal(runtime.langServer.activeRequests, 0);
    assert.equal(runtime.detach.enabled, false);
    assert.equal(runtime.detach.counters, null);
  });

  it('sums active requests, counts ready instances and keeps admission counters', () => {
    const runtime = buildRuntimeSection({
      langServer: {
        running: true,
        pid: 4242,
        port: 42100,
        totalRssBytes: 1024,
        instances: [
          { ready: true, activeRequests: 2 },
          { ready: false, activeRequests: 1 },
        ],
        admissionStats: {
          startAttempts: 5,
          startSuccesses: 4,
          startFailures: 1,
          poolWaits: 2,
          memoryWaits: 0,
          poolExhausted: 0,
          memoryGuardBlocks: 0,
          evictions: 1,
          lastFailure: { kind: 'pool_capacity', errorType: 'ls_pool_exhausted', at: NOW - 1_000 },
          lastSuccess: { key: 'default' },
        },
      },
      detach: { enabled: true, entries: 2, bytes: 99, draining: false, admit: 3, serve: 1, re_detach: 0 },
    });
    assert.equal(runtime.langServer.instances, 2);
    assert.equal(runtime.langServer.ready, 1);
    assert.equal(runtime.langServer.activeRequests, 3);
    assert.equal(runtime.langServer.admission.startAttempts, 5);
    assert.equal(runtime.langServer.admission.lastFailure.errorType, 'ls_pool_exhausted');
    assert.equal(runtime.detach.entries, 2);
    assert.equal(runtime.detach.bytes, 99);
    assert.equal(runtime.detach.counters.admit, 3);
    assert.ok(!('entries' in runtime.detach.counters));
    assert.ok(!('bytes' in runtime.detach.counters));
    assert.ok(!('draining' in runtime.detach.counters));
  });

  it('reports the credential store as read-only presence flags', () => {
    const runtime = buildRuntimeSection({ credStoreEnabled: true, remoteCredStore: false });
    assert.deepEqual(runtime.credentials, { credStoreEnabled: true, remoteCredStore: false, writable: false });
  });
});

describe('session sources', () => {
  it('names exactly one available source and gives the others a reason', () => {
    const sources = buildSessionSources();
    const available = sources.filter((s) => s.available === true);
    assert.equal(available.length, 1);
    assert.equal(available[0].id, 'proxyTurns');
    const byId = Object.fromEntries(sources.map((s) => [s.id, s]));
    assert.equal(byId.cascadeTrajectories.available, false);
    assert.equal(byId.cascadeTrajectories.reason, SOURCE_REASONS.LS_ONLY);
    assert.ok(byId.cascadeTrajectories.rpcs.includes('LanguageServerService/GetAllCascadeTrajectories'));
    assert.equal(byId.acpSessions.available, false);
    assert.equal(byId.acpSessions.reason, SOURCE_REASONS.NOT_TRACKED);
  });

  it('marks the proxy turn ring as billed-only and process-local', () => {
    const proxy = buildSessionSources().find((s) => s.id === 'proxyTurns');
    assert.deepEqual(proxy.notes, [SOURCE_REASONS.BILLED_ONLY, SOURCE_REASONS.PROCESS_LOCAL]);
  });

  it('composes a sessions section that always carries the source table', () => {
    const section = buildSessionsSection();
    assert.ok(Array.isArray(section.sources));
    assert.equal(section.sources.length, 3);
    assert.equal(section.turns.available, true);
  });
});

describe('buildQuotaSection', () => {
  it('maps the drought decision without inventing absent numbers', () => {
    const quota = buildQuotaSection({
      drought: {
        drought: true,
        threshold: 20,
        activeAccounts: 3,
        knownAccounts: 3,
        quotaKnownAccounts: 2,
        droughtAccounts: 1,
        coverageMet: true,
        lowestWeeklyPercent: 12.5,
        lowestDailyPercent: 0,
        restrictEnabled: true,
        restrictionFailOpen: false,
        freeTierModels: ['swe-2'],
      },
    });
    assert.equal(quota.drought.active, true);
    assert.equal(quota.drought.threshold, 20);
    assert.equal(quota.drought.lowestWeeklyPercent, 12.5);
    assert.equal(quota.drought.lowestDailyPercent, 0);
    assert.deepEqual(quota.drought.freeTierModels, ['swe-2']);
    assert.equal(quota.drought.coverageMet, true);
  });

  it('reports nulls (not zeros) when the drought summary is absent', () => {
    const quota = buildQuotaSection();
    assert.equal(quota.drought.active, false);
    assert.equal(quota.drought.threshold, null);
    assert.equal(quota.drought.lowestWeeklyPercent, null);
    assert.equal(quota.drought.coverageMet, false);
    assert.deepEqual(quota.drought.freeTierModels, []);
  });

  it('distinguishes an env-following knob from a runtime-config override', () => {
    const quota = buildQuotaSection({
      tunables: { cooldownEnabled: true, cooldownMs: 1800000, dryThreshold: 0, spendOnDemand: true, onDemandReserveUsd: 0 },
      overrides: { cooldownEnabled: null, cooldownMs: 900000, dryThreshold: null, spendOnDemand: false, onDemandReserveUsd: null },
    });
    assert.deepEqual(quota.knobs.map((k) => k.key), [...QUOTA_KNOB_KEYS]);
    const cooldownMs = quota.knobs.find((k) => k.key === 'cooldownMs');
    assert.deepEqual(cooldownMs, { key: 'cooldownMs', value: 1800000, override: 900000, source: 'config', overridden: true });
    const dry = quota.knobs.find((k) => k.key === 'dryThreshold');
    assert.deepEqual(dry, { key: 'dryThreshold', value: 0, override: null, source: 'env', overridden: false });
    const spend = quota.knobs.find((k) => k.key === 'spendOnDemand');
    assert.equal(spend.value, true);
    assert.equal(spend.override, false);
    assert.equal(spend.source, 'config');
  });
});

describe('buildDevinPanel', () => {
  it('assembles the four top-level fields and stamps the generation time', () => {
    const panel = buildDevinPanel({ now: NOW });
    assert.equal(panel.ok, true);
    assert.equal(panel.generatedAt, NOW);
    assert.ok(panel.runtime);
    assert.ok(panel.sessions);
    assert.ok(panel.quota);
    assert.equal(panel.runtime.switches.length, RUNTIME_SWITCH_KEYS.length);
    assert.equal(panel.sessions.turns.available, true);
    assert.equal(panel.quota.knobs.length, QUOTA_KNOB_KEYS.length);
  });

  it('uses caller-supplied sections instead of rebuilding them', () => {
    const runtime = buildRuntimeSection({ uptimeSec: 42 });
    const panel = buildDevinPanel({ now: NOW, runtime, sessions: buildSessionsSection(), quota: buildQuotaSection() });
    assert.equal(panel.runtime.uptimeSec, 42);
  });

  it('caps the account scan constant at a bounded value', () => {
    assert.ok(Number.isInteger(MAX_SCAN_ACCOUNTS));
    assert.ok(MAX_SCAN_ACCOUNTS > 0 && MAX_SCAN_ACCOUNTS <= 1000);
    assert.ok(TURN_ROW_CAP > 0 && TURN_ROW_CAP <= 200);
  });
});