test/dashboard-devin-panel.test.js
Rama master · réplica de lectura del repositorio original.
// 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);
});
});