test/dashboard-devin-read.test.js
Rama master · réplica de lectura del repositorio original.
import { afterEach, describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import {
ReadError,
accountRef,
cacheKey,
peekCached,
readCached,
readDevinJson,
redactUpstreamText,
resolveOrgId,
__resetReadCache,
__setFetchImplForTest,
__setNowForTest,
} from '../src/dashboard/devin-read.js';
function jsonResponse(value, { status = 200, headers = {} } = {}) {
return {
ok: status >= 200 && status < 300,
status,
headers: { get: (name) => headers[String(name).toLowerCase()] ?? null },
text: async () => (typeof value === 'string' ? value : JSON.stringify(value)),
};
}
function errorResponse(status) {
return jsonResponse({ detail: 'nope' }, { status });
}
const originalOrg = process.env.DEVIN_BACKEND_ORG_ID;
afterEach(() => {
__setFetchImplForTest(null);
__setNowForTest(null);
__resetReadCache();
if (originalOrg === undefined) delete process.env.DEVIN_BACKEND_ORG_ID;
else process.env.DEVIN_BACKEND_ORG_ID = originalOrg;
});
describe('readDevinJson', () => {
it('is GET-only: no method/body parameter, redirect blocked, no Content-Type', async () => {
const calls = [];
const out = await readDevinJson({
token: 'tok',
path: '/org-abc123/billing/status',
orgId: 'org-abc123',
fetchImpl: async (url, init) => {
calls.push({ url, init });
return jsonResponse({ plan_slug: 'pro' });
},
});
assert.equal(out.plan_slug, 'pro');
assert.equal(calls.length, 1);
assert.equal(calls[0].init.method, 'GET');
assert.equal('body' in calls[0].init, false);
assert.equal(calls[0].init.redirect, 'error');
assert.equal(calls[0].init.headers.Authorization, 'Bearer tok');
assert.equal(calls[0].init.headers.Accept, 'application/json');
assert.match(calls[0].init.headers['User-Agent'], /^WindsurfAPI\//);
assert.equal(calls[0].init.headers['x-cog-org-id'], 'org-abc123');
assert.equal('Content-Type' in calls[0].init.headers, false);
assert.equal(calls[0].url, 'https://app.devin.ai/api/org-abc123/billing/status');
});
it('omits x-cog-org-id on global paths', async () => {
let seen;
await readDevinJson({
token: 'tok',
path: '/users/spending-state',
fetchImpl: async (url, init) => { seen = init; return jsonResponse({ state: 'active' }); },
});
assert.equal('x-cog-org-id' in seen.headers, false);
});
it('rejects absolute URLs, protocol-relative paths and header injection before fetch', async () => {
let called = 0;
const fetchImpl = async () => { called++; return jsonResponse({}); };
for (const path of ['https://evil.test/x', '//evil.test/x', '/x\r\nHost: evil', 'x/no-leading-slash']) {
await assert.rejects(
readDevinJson({ token: 'tok', path, fetchImpl }),
(err) => err instanceof ReadError && err.kind === 'bad_path',
`path ${JSON.stringify(path)} must be rejected`,
);
}
assert.equal(called, 0, 'no fetch may happen for a bad path');
});
it('maps upstream statuses to error kinds', async () => {
const cases = [
[401, 'unauthorized'],
[403, 'forbidden'],
[404, 'not_found'],
[429, 'rate_limited'],
[500, 'upstream'],
[302, 'redirect_blocked'],
];
for (const [status, kind] of cases) {
await assert.rejects(
readDevinJson({ token: 'tok', path: '/x', fetchImpl: async () => errorResponse(status) }),
(err) => err instanceof ReadError && err.kind === kind && err.status === status,
`status ${status}`,
);
}
});
it('maps timeout, malformed and too-large bodies', async () => {
await assert.rejects(
readDevinJson({
token: 'tok',
path: '/x',
fetchImpl: async () => { throw Object.assign(new Error('aborted'), { name: 'TimeoutError' }); },
}),
(err) => err.kind === 'timeout',
);
await assert.rejects(
readDevinJson({ token: 'tok', path: '/x', fetchImpl: async () => jsonResponse('<html>not json') }),
(err) => err.kind === 'malformed',
);
await assert.rejects(
readDevinJson({
token: 'tok',
path: '/x',
maxBytes: 8,
fetchImpl: async () => jsonResponse({ long: 'aaaaaaaaaaaaaaaa' }),
}),
(err) => err.kind === 'too_large',
);
});
it('keeps the timeout armed while a streamed body is read', async () => {
let signal;
await assert.rejects(
readDevinJson({
token: 'tok',
path: '/x',
timeoutMs: 20,
fetchImpl: async (_url, init) => {
signal = init.signal;
return new Response(new ReadableStream({
start(controller) {
const timer = setTimeout(() => {
controller.enqueue(new TextEncoder().encode('{}'));
controller.close();
}, 120);
signal.addEventListener('abort', () => {
clearTimeout(timer);
controller.error(Object.assign(new Error('aborted'), { name: 'AbortError' }));
}, { once: true });
},
}));
},
}),
(err) => err.kind === 'timeout',
);
assert.equal(signal.aborted, true);
});
it('aborts an oversized streamed body before reading all of it', async () => {
let chunks = 0;
await assert.rejects(
readDevinJson({
token: 'tok',
path: '/x',
maxBytes: 32,
fetchImpl: async () => new Response(new ReadableStream({
pull(controller) {
chunks++;
controller.enqueue(new Uint8Array(16).fill(97));
if (chunks === 8) controller.close();
},
}, { highWaterMark: 0 })),
}),
(err) => err.kind === 'too_large',
);
assert.ok(chunks <= 3, `read ${chunks} chunks before enforcing the cap`);
});
it('requires a token and a valid org header when one is supplied', async () => {
await assert.rejects(readDevinJson({ token: '', path: '/x' }), (err) => err.kind === 'no_account');
await assert.rejects(
readDevinJson({ token: 'tok', path: '/x', orgId: 'not-an-org' }),
(err) => err.kind === 'bad_org',
);
});
});
describe('resolveOrgId', () => {
it('uses GET /users/current-membership first, never post-auth', async () => {
const calls = [];
const org = await resolveOrgId({
token: 'tok',
fetchImpl: async (url, init) => {
calls.push({ url, method: init.method });
if (url.endsWith('/users/current-membership')) {
return jsonResponse({ org: { org_id: 'org-primary1' }, user: { email: 'x@example.test' } });
}
throw new Error(`unexpected ${url}`);
},
});
assert.equal(org, 'org-primary1');
assert.equal(calls.length, 1);
assert.equal(calls[0].method, 'GET');
assert.ok(calls.every((c) => !c.url.includes('/users/post-auth')));
});
it('falls back to GET /organizations and picks the primary org', async () => {
const calls = [];
const org = await resolveOrgId({
token: 'tok',
fetchImpl: async (url) => {
calls.push(url);
if (url.endsWith('/users/current-membership')) return errorResponse(404);
if (url.endsWith('/organizations')) {
return jsonResponse([{ org_id: 'org-a' }, { org_id: 'org-b', is_primary_org: true }]);
}
throw new Error(`unexpected ${url}`);
},
});
assert.equal(org, 'org-b');
assert.deepEqual(calls.map((u) => u.split('/api')[1]), ['/users/current-membership', '/organizations']);
});
it('uses the single org when no primary is flagged', async () => {
const org = await resolveOrgId({
token: 'tok',
fetchImpl: async (url) => {
if (url.endsWith('/users/current-membership')) return jsonResponse({});
if (url.endsWith('/organizations')) return jsonResponse([{ org_id: 'org-only1' }]);
throw new Error(`unexpected ${url}`);
},
});
assert.equal(org, 'org-only1');
});
it('refuses to guess between multiple orgs with no primary', async () => {
await assert.rejects(
resolveOrgId({
token: 'tok',
fetchImpl: async (url) => {
if (url.endsWith('/users/current-membership')) return jsonResponse({});
if (url.endsWith('/organizations')) return jsonResponse([{ org_id: 'org-a' }, { org_id: 'org-b' }]);
throw new Error(`unexpected ${url}`);
},
}),
(err) => err.kind === 'org_unresolved',
);
});
it('does not fall back when the token itself is rejected', async () => {
const calls = [];
await assert.rejects(
resolveOrgId({
token: 'tok',
fetchImpl: async (url) => { calls.push(url); return errorResponse(401); },
}),
(err) => err.kind === 'unauthorized',
);
assert.equal(calls.length, 1, 'a 401 must not be retried against /organizations');
});
it('honours DEVIN_BACKEND_ORG_ID without any fetch', async () => {
process.env.DEVIN_BACKEND_ORG_ID = 'org-fromenv';
let called = 0;
const org = await resolveOrgId({ token: 'tok', fetchImpl: async () => { called++; return jsonResponse({}); } });
assert.equal(org, 'org-fromenv');
assert.equal(called, 0);
});
});
describe('readCached', () => {
it('serves fresh hits, refetches after TTL, and supports force', async () => {
let now = 1_000_000;
__setNowForTest(() => now);
let loads = 0;
const load = async () => { loads++; return { n: loads }; };
const first = await readCached('k', 30_000, load);
assert.equal(first.cached, false);
assert.equal(first.value.n, 1);
now += 29_000;
const hit = await readCached('k', 30_000, load);
assert.equal(hit.cached, true);
assert.equal(hit.value.n, 1);
assert.equal(loads, 1);
now += 2_000;
const miss = await readCached('k', 30_000, load);
assert.equal(miss.cached, false);
assert.equal(miss.value.n, 2);
now += 1_000;
const forced = await readCached('k', 30_000, load, { force: true });
assert.equal(forced.cached, false);
assert.equal(loads, 3);
});
it('single-flights concurrent loads', async () => {
let loads = 0;
let release;
const gate = new Promise((resolve) => { release = resolve; });
const load = async () => { loads++; await gate; return { ok: 1 }; };
const a = readCached('single', 30_000, load);
const b = readCached('single', 30_000, load);
release();
const [ra, rb] = await Promise.all([a, b]);
assert.equal(loads, 1);
assert.equal(ra.value.ok, 1);
assert.equal(rb.value.ok, 1);
});
it('serves the last good value as stale on transient errors, but never on 403', async () => {
let now = 2_000_000;
__setNowForTest(() => now);
await readCached('stale', 30_000, async () => ({ plan: 'pro' }));
now += 31_000;
const stale = await readCached('stale', 30_000, async () => {
throw new ReadError('timeout', { status: null });
});
assert.equal(stale.ok, true);
assert.equal(stale.stale, true);
assert.equal(stale.value.plan, 'pro');
const forbidden = await readCached('stale', 30_000, async () => {
throw new ReadError('forbidden', { status: 403 });
});
assert.equal(forbidden.ok, false);
assert.equal(forbidden.error, 'forbidden');
assert.equal('value' in forbidden, false);
});
it('drops the stale value after the max-stale cap', async () => {
let now = 3_000_000;
__setNowForTest(() => now);
await readCached('old', 30_000, async () => ({ plan: 'pro' }));
now += 31 * 60_000;
const out = await readCached('old', 30_000, async () => {
throw new ReadError('upstream', { status: 502 });
});
assert.equal(out.ok, false);
assert.equal(out.error, 'upstream');
});
it('caps the cache at 200 entries, evicting the oldest insertion', async () => {
for (let i = 0; i < 201; i += 1) {
await readCached(`cap-${i}`, 30_000, async () => ({ i }));
}
assert.equal(peekCached('cap-0'), null, 'the oldest entry is evicted');
assert.equal(peekCached('cap-200').value.i, 200);
});
});
describe('cache keys, peek and redaction', () => {
it('never uses the raw token as a cache key and accountRef is a hash', () => {
const token = ['super', 'secret', 'token'].join('-');
const key = cacheKey({ accountId: 'acct-test', token, name: 'status' });
assert.equal(key.includes(token), false);
assert.match(key, /^acct-test:[0-9a-f]{12}:status$/);
assert.match(accountRef('acct-test'), /^acct-[0-9a-f]{8}$/);
});
it('peekCached is read-only and reports freshness', async () => {
let now = 4_000_000;
__setNowForTest(() => now);
assert.equal(peekCached('peek'), null);
await readCached('peek', 30_000, async () => ({ v: 1 }));
assert.equal(peekCached('peek').value.v, 1);
assert.equal(peekCached('peek').stale, false);
now += 31_000;
assert.equal(peekCached('peek').stale, true);
});
it('redacts credentials, org/user/acct ids and email from free text', () => {
const raw = 'contact org-test123 / user-test456 / acct_abcdef123 / user@example.test '
+ 'auth1_abcdefgh devin-session-token$abcdef';
const out = redactUpstreamText(raw);
for (const secret of ['org-test123', 'user-test456', 'acct_abcdef123', 'user@example.test', 'auth1_abcdefgh', 'devin-session-token$abcdef']) {
assert.equal(out.includes(secret), false, `must redact ${secret}`);
}
assert.ok(redactUpstreamText('x'.repeat(1000)).length <= 200);
});
});
describe('read-path write scan', () => {
const here = dirname(fileURLToPath(import.meta.url));
const modules = [
'devin-read.js',
'billing-health.js',
'model-reconcile.js',
'quota-burn-down.js',
'usage.js',
'catalog-diff.js',
'pool-events.js',
];
const FORBIDDEN = [
'probePostAuth',
'saveAccounts',
'setAccountStatus',
'refreshCredits',
'setRuntimeApiKey',
'setRuntimeDashboardPassword',
'writeJsonAtomic',
'writeFileSync',
"method: 'POST'",
'method: "POST"',
];
it('never contains a write call, a post-auth probe or a local persistence call', () => {
for (const name of modules) {
const source = readFileSync(join(here, '..', 'src', 'dashboard', name), 'utf8');
for (const token of FORBIDDEN) {
assert.equal(source.includes(token), false, `${name} must not contain ${token}`);
}
assert.equal(source.includes("'/users/post-auth'"), false, `${name} must not call post-auth`);
}
});
});