Repository Public
1import { afterAll, beforeAll, describe, expect, it } from 'vitest';2import type { ClusterOverview, LocalModel, SystemStatus } from '@cr/shared';3import { ClusterOverview as ClusterOverviewSchema, SystemStatus as SystemStatusSchema, UserSettings } from '@cr/shared';4import { runJanitor } from '../janitor.js';5import { Client, createHarness, setupAdmin, type Harness } from '../testkit/harness.js';6import { mapAgentd } from './cluster/mapping.js';78const IPV4 = /\d+\.\d+\.\d+\.\d+/;9const nowSec = (): number => Math.floor(Date.now() / 1000);1011/** Shape observed on the real agentd `/api/cluster`, with every sensitive field populated. */12function agentdFixture(): Record<string, unknown> {13 const t = nowSec();14 return {15 ts: t,16 nodes: [17 { name: 'M5M36', hostname: 'M5M36.maclustr.io', chip: 'Apple M5 Max', model: 'Mac Studio', tier: 'A', generation: 5, cpuCores: 18, memoryMB: 36864, gpuCores: 40, remote: false, user: 'simon-pierreboucher', platform: 'darwin', kind: 'mac', site: 'lan', role: 'node', hardware: 'Mac16,9', publicIP: '142.170.25.2', serial: 'LY5Q6FD2PL', lanIP: '192.168.2.104' },18 { name: 'M3U96a', hostname: 'M3U96a.maclustr.io', chip: 'Apple M3 Ultra', model: 'Mac Studio', cpuCores: 32, memoryMB: 98304, gpuCores: 80, remote: false, user: 'simon-pierreboucher', platform: 'darwin', kind: 'mac', role: 'node', publicIP: '142.170.25.2' },19 { name: 'm4ma', chip: 'Apple M4', model: 'Mac mini', cpuCores: 10, memoryMB: 24576, platform: 'darwin', kind: 'mac', role: '10.67.0.9 relay', user: 'x' },20 { name: 'M2U64', chip: 'Apple M2 Ultra', model: 'Mac Studio', cpuCores: 24, memoryMB: 65536, platform: 'darwin', kind: 'mac' },21 { name: 'iphone-1', model: 'iPhone 17 Pro Max', kind: 'mobile', platform: 'ios', cpuCores: 6 },22 ],23 metrics: {24 M5M36: { name: 'M5M36', status: 'online', ts: t - 5, cpu: 12.34, memUsedMB: 9000.4, memTotalMB: 36864, load: [1.234, 1, 0.5], diskFreeGB: 400.26, diskTotalGB: 994, ip: '192.168.2.104' },25 M3U96a: { name: 'M3U96a', status: 'online', ts: t - 300, cpu: 50, memUsedMB: 1, memTotalMB: 98304, load: [9, 9, 9], disk: { free: 10, total: 100 } },26 m4ma: { name: 'm4ma', status: 'offline', ts: t - 10, cpu: null, load: [] },27 'iphone-1': { name: 'iphone-1', status: 'online', ts: t - 2, cpu: 3, diskUsedGB: 100, diskTotalGB: 256 },28 },29 lanMap: { M5M36: '192.168.2.104', M3U96a: '192.168.2.50' },30 appsByNode: { M5M36: { apps: ['claude-remote', { name: 'uqo-eval', port: 8080, url: 'http://192.168.2.104:8080' }, '10.0.0.1'] }, M3U96a: { apps: ['findata'] } },31 servers: [{ name: 'BHS64', hostname: 'ns5044661.ip-51-161-112.net', publicIP: '51.161.112.61', cpuCores: 16, memoryMB: 65536, platform: 'linux', user: 'ubuntu', role: 'tunnel gateway' }],32 aggregates: { cores: 401 },33 };34}3536let h: Harness;37let admin: Client;38let payload: unknown = agentdFixture();39let agentdFails = false;40const fetchCalls: { url: string; init?: RequestInit }[] = [];41let registryUp = true;4243const fakeFetch = (async (input: string | URL | Request, init?: RequestInit): Promise<Response> => {44 const url = String(input);45 fetchCalls.push({ url, init });46 const json = (body: unknown, status = 200): Response => new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json' } });47 if (url === 'http://model-a.test:8001/v1/models') return json({ data: [] });48 if (url === 'http://model-a.test:8001/v1/chat/completions') return json({ choices: [{ message: { content: 'ok' } }], usage: { completion_tokens: 8 } });49 if (url === 'http://model-b.test:8002/v1/models') throw new TypeError('fetch failed');50 if (url === 'http://model-b.test:8002/v1/chat/completions') throw new TypeError('fetch failed');51 if (url === 'http://model-c.test:8003/v1/models') return json({}, 500);52 if (url === 'http://model-c.test:8003/v1/chat/completions') return json({ choices: [] });53 if (url.startsWith('https://skills.test/api/v1/artifacts?')) {54 if (!registryUp) throw new TypeError('fetch failed');55 return json({ items: [56 { name: '@research/paper-review', namespace: 'research', slug: 'paper-review', type: 'skill', summary: '<b>review</b>', latestVersion: '1.2.0', license: 'MIT', url: 'https://skills.test/a/research/paper-review', publisher: { namespace: 'research', displayName: 'Research' }, sourceVerified: true },57 { summary: 'no name: skipped' },58 { name: '@x/y', summary: 's', url: 'javascript:alert(1)' },59 ] });60 }61 return json({ error: 'unexpected' }, 404);62}) as typeof fetch;6364beforeAll(async () => {65 h = await createHarness({66 env: {67 CR_EXEC_HOSTS: 'M3U96a',68 CR_NODE_ALIAS: 'M5M36',69 CR_SKILLS_REGISTRY_URL: 'https://skills.test',70 CR_LOCAL_MODELS: JSON.stringify([71 { id: 'model-a', name: 'Model A', host: 'M3U96a', engine: 'mlx', endpoint: 'http://model-a.test:8001' },72 { id: 'model-b', name: 'Model B', host: 'M3U96b', engine: null, endpoint: 'http://model-b.test:8002' },73 { id: 'model-c', name: 'Model C', host: 'M2U64', engine: 'llama.cpp', endpoint: 'http://model-c.test:8003/' },74 ]),75 },76 tweak: (c) => (c.uploadLimitBytes = 64 * 1024),77 deps: {78 fetch: fakeFetch,79 skillsRegistry: undefined,80 clusterSource: {81 fetch: async () => {82 if (agentdFails) throw new Error('down');83 return payload;84 },85 },86 },87 });88 ({ client: admin } = await setupAdmin(h));89});90afterAll(async () => {91 await h.close();92});9394describe('cluster (agentd)', () => {95 it('maps the payload to ClusterOverview; stale metrics are UNKNOWN, never ONLINE', async () => {96 await h.server.ctx.cluster.poll();97 const res = await admin.get('/cluster/nodes');98 const o = ClusterOverviewSchema.parse(res.json());99 expect(o.source).toBe('agentd');100 const by = Object.fromEntries(o.nodes.map((n) => [n.id, n]));101 expect(Object.keys(by).sort()).toEqual(['BHS64', 'M2U64', 'M3U96a', 'M5M36', 'iphone-1', 'm4ma']);102 expect(by.M5M36).toMatchObject({103 health: 'ONLINE', stale: false, isHost: true, kind: 'mac', chip: 'Apple M5 Max', model: 'Mac Studio', cpuCores: 18, gpuCores: 40, memoryMb: 36864,104 cpuPct: 12.3, memUsedMb: 9000, diskFreeGb: 400.3, diskTotalGb: 994, load1: 1.23, apps: ['claude-remote', 'uqo-eval'], execAllowed: false, os: 'darwin',105 });106 expect(by.M3U96a).toMatchObject({ health: 'UNKNOWN', stale: true, execAllowed: true, isHost: false, diskFreeGb: 10, diskTotalGb: 100 }); // 300 s old107 expect(by.M3U96a?.metricsAt).not.toBeNull();108 expect(by.m4ma).toMatchObject({ health: 'OFFLINE', stale: false, cpuPct: null, load1: null, diskFreeGb: null, role: null }); // role carried an address ⇒ dropped109 expect(by.M2U64).toMatchObject({ health: 'UNKNOWN', stale: true, metricsAt: null, cpuPct: null }); // never reported110 expect(by['iphone-1']).toMatchObject({ kind: 'mobile', health: 'ONLINE', diskFreeGb: 156, diskTotalGb: 256 });111 expect(by.BHS64).toMatchObject({ kind: 'server', health: 'UNKNOWN', stale: true });112 expect(o.health).toBe('DEGRADED');113 expect(o.totals).toEqual({ nodes: 6, online: 2, cores: 18 + 32 + 10 + 24 + 6 + 16, memoryGb: (36864 + 98304 + 24576 + 65536 + 65536) / 1024 });114 expect(o.edges).toEqual([115 { from: 'M5M36', to: 'M3U96a', kind: 'lan' }, { from: 'M5M36', to: 'm4ma', kind: 'lan' }, { from: 'M5M36', to: 'M2U64', kind: 'lan' },116 { from: 'M5M36', to: 'BHS64', kind: 'tunnel' },117 ]);118 });119 it('never leaks IP addresses, usernames, hostnames or serials', async () => {120 for (const url of ['/cluster/nodes', '/cluster/nodes/M5M36', '/cluster/metrics', '/system/status']) {121 const body = (await admin.get(url)).body;122 expect([url, IPV4.test(body)]).toEqual([url, false]);123 for (const secret of ['simon-pierreboucher', 'ubuntu', 'LY5Q6FD2PL', 'maclustr.io', 'ip-51-161', 'publicIP', 'lanMap', 'lanIP', 'Mac16,9']) {124 expect([url, secret, body.includes(secret)]).toEqual([url, secret, false]);125 }126 }127 });128 it('persists inventory and one metrics row per fresh node', async () => {129 const db = h.server.ctx.db;130 const nodes = await db.q<{ id: string; exec_allowed: boolean }>('SELECT id, exec_allowed FROM cluster_nodes');131 expect(nodes.map((n) => n.id).sort()).toEqual(['BHS64', 'M2U64', 'M3U96a', 'M5M36', 'iphone-1', 'm4ma']);132 expect(nodes.find((n) => n.id === 'M3U96a')?.exec_allowed).toBe(true);133 const metrics = await db.q<{ node_id: string; online: boolean }>('SELECT node_id, online FROM node_metrics');134 expect(metrics.map((m) => `${m.node_id}:${m.online}`).sort()).toEqual(['M5M36:true', 'iphone-1:true', 'm4ma:false']);135 await h.server.ctx.cluster.poll(); // again within the minute: downsampled, no new rows136 expect((await db.q('SELECT 1 FROM node_metrics')).length).toBe(3);137 const detail = (await admin.get('/cluster/nodes/M5M36')).json<{ node: { id: string }; history: unknown[] }>();138 expect(detail.node.id).toBe('M5M36');139 expect(detail.history).toHaveLength(1);140 expect((await admin.get('/cluster/nodes/nope')).statusCode).toBe(404);141 expect(Object.keys((await admin.get('/cluster/metrics?minutes=5')).json<{ series: object }>().series).sort()).toEqual(['M5M36', 'iphone-1', 'm4ma']);142 });143 it('when agentd stops answering, the last snapshot ages into UNKNOWN', async () => {144 agentdFails = true;145 await h.server.ctx.cluster.poll();146 expect((await admin.get('/cluster/nodes')).json<ClusterOverview>().nodes.find((n) => n.id === 'M5M36')?.health).toBe('ONLINE'); // still within 90 s147 h.clock.offsetMs += 120_000;148 await h.server.ctx.cluster.poll();149 const o = (await admin.get('/cluster/nodes')).json<ClusterOverview>();150 expect(o.health).toBe('UNKNOWN');151 expect(o.nodes.every((n) => n.health === 'UNKNOWN' && n.stale)).toBe(true);152 expect(o.totals.online).toBe(0);153 expect(o.collectedAt).not.toBeNull();154 const status = (await admin.get('/system/status')).json<SystemStatus>();155 expect(status.components.find((c) => c.component === 'agentd')).toMatchObject({ health: 'UNKNOWN', detail: 'unreachable' });156 agentdFails = false;157 h.clock.offsetMs -= 120_000;158 await h.server.ctx.cluster.poll();159 });160 it('mapAgentd tolerates garbage', () => {161 const opts = { nowMs: Date.now(), fetchedAtMs: Date.now(), execHosts: [], hostAlias: null };162 for (const junk of [null, 42, 'x', [], {}, { nodes: 'no', metrics: [], servers: 7 }, { nodes: [null, 1, {}, { name: '10.0.0.1' }, { name: 5 }] }]) {163 const o = mapAgentd(junk, opts);164 expect(o.nodes).toEqual([]);165 expect(o.health).toBe('UNKNOWN');166 expect(o.edges).toEqual([]);167 }168 const ms = mapAgentd({ nodes: [{ name: 'a' }], metrics: { a: { status: 'online', ts: Date.now() } } }, opts);169 expect(ms.nodes[0]).toMatchObject({ health: 'ONLINE', kind: 'other' }); // epoch ms tolerated170 });171});172173describe('cluster without agentd', () => {174 it('is limited to the host measured by the runtime, with cluster health UNKNOWN', async () => {175 const h2 = await createHarness();176 try {177 const { client } = await setupAdmin(h2);178 const o = ClusterOverviewSchema.parse((await client.get('/cluster/nodes')).json());179 expect(o).toMatchObject({ source: 'none', health: 'UNKNOWN', edges: [] });180 expect(o.nodes).toHaveLength(1);181 expect(o.nodes[0]).toMatchObject({ id: 'fakehost', isHost: true, health: 'ONLINE', cpuPct: 12.5, memUsedMb: 1000, diskFreeGb: 50, execAllowed: false });182 expect((await client.get('/cluster/models')).json()).toEqual([]); // nothing configured ⇒ nothing invented183 expect((await client.get('/skills/registry/search?q=x')).json()).toEqual({ connected: false, results: [] });184 const status = (await client.get('/system/status')).json<SystemStatus>();185 expect(status.components.find((c) => c.component === 'agentd')).toMatchObject({ health: 'UNKNOWN', detail: 'not configured' });186 await h2.rt.stop();187 const down = ClusterOverviewSchema.parse((await client.get('/cluster/nodes')).json());188 expect(down).toMatchObject({ source: 'none', health: 'UNKNOWN', nodes: [], collectedAt: null });189 await h2.rt.start();190 } finally {191 await h2.close();192 }193 });194});195196describe('local models', () => {197 it('health comes from a real probe of /v1/models; internal URLs are not sent to the browser', async () => {198 const res = await admin.get('/cluster/models');199 const models = res.json<LocalModel[]>();200 expect(models.map((m) => [m.id, m.health, m.endpoint])).toEqual([201 ['model-a', 'ONLINE', 'M3U96a:8001'], ['model-b', 'OFFLINE', 'M3U96b:8002'], ['model-c', 'DEGRADED', 'M2U64:8003'],202 ]);203 expect(models.every((m) => m.latencyMs === null && m.tokensPerSec === null && m.checkedAt !== null)).toBe(true);204 expect(res.body).not.toContain('.test');205 });206 it('test endpoint does a real small completion; tokens/s only when usage is reported', async () => {207 const a = (await admin.post('/cluster/models/model-a/test')).json<{ ok: boolean; latencyMs: number | null; tokensPerSec: number | null; error: string | null }>();208 expect(a.ok).toBe(true);209 expect(a.latencyMs).not.toBeNull();210 expect(a.error).toBeNull();211 const call = fetchCalls.find((c) => c.url.endsWith(':8001/v1/chat/completions'));212 expect(JSON.parse(String(call?.init?.body))).toMatchObject({ model: 'model-a', max_tokens: 32 });213 const b = (await admin.post('/cluster/models/model-b/test')).json();214 expect(b).toMatchObject({ ok: false, latencyMs: null, tokensPerSec: null, error: 'unreachable' });215 const c = (await admin.post('/cluster/models/model-c/test')).json();216 expect(c).toMatchObject({ ok: true, tokensPerSec: null }); // no usage ⇒ not measurable217 expect((await admin.post('/cluster/models/nope/test')).statusCode).toBe(404);218 });219});220221describe('skills / MCP', () => {222 it('proxies listings, caches them, and audits operations without secrets', async () => {223 h.rt.skills = [{ name: 'review', description: 'd', source: 'user', kind: 'skill', version: null, location: '~/.claude/skills/review', enabled: true, updateAvailable: null }];224 h.rt.mcp = [{ name: 'notion', scope: 'user', transport: 'http', target: 'https://mcp.example', envKeys: ['NOTION_TOKEN'], status: 'connected', lastError: null, checkedAt: null }];225 expect((await admin.get('/skills')).json()).toEqual(h.rt.skills);226 expect((await admin.get('/mcp')).json()).toEqual(h.rt.mcp);227 const db = h.server.ctx.db;228 expect((await db.q('SELECT name FROM installed_skills')).length).toBe(1);229 expect(await db.one('SELECT name, env_keys FROM mcp_servers')).toEqual({ name: 'notion', env_keys: ['NOTION_TOKEN'] });230 const add = await admin.post('/mcp', { name: 'gh', transport: 'stdio', target: 'npx', args: ['-y', 'x'], env: { GH_TOKEN: 'ghp_SUPERSECRET' }, headers: { Authorization: 'Bearer HDRSECRET' } });231 expect(add.statusCode).toBe(201);232 const sent = h.rt.calls.find((c) => c.url === '/mcp/add')?.body as { env: Record<string, string> };233 expect(sent.env.GH_TOKEN).toBe('ghp_SUPERSECRET'); // forwarded once to the runtime…234 await new Promise((r) => setTimeout(r, 50));235 const trail = JSON.stringify(await db.q('SELECT * FROM audit_events')) + JSON.stringify(await db.q('SELECT * FROM mcp_servers')) + h.logs.join('');236 expect(trail).not.toContain('ghp_SUPERSECRET'); // …and stored / logged nowhere237 expect(trail).not.toContain('HDRSECRET');238 expect(await db.one("SELECT detail FROM audit_events WHERE action = 'mcp.add'")).toEqual({ detail: { transport: 'stdio', scope: 'user', envKeys: ['GH_TOKEN'], headerKeys: ['Authorization'] } });239 expect((await admin.post('/mcp', { name: 'bad name!', transport: 'stdio', target: 'x' })).statusCode).toBe(400);240 expect((await admin.post('/mcp', { name: 'p', transport: 'stdio', target: 'x', scope: 'project' })).statusCode).toBe(400);241 expect((await admin.req('DELETE', '/mcp/gh')).statusCode).toBe(200);242 expect((await admin.post('/mcp/gh/test')).json()).toMatchObject({ name: 'gh', status: 'connected' });243 expect((await admin.post('/skills/op', { op: 'disable', name: 'review', kind: 'skill' })).statusCode).toBe(200);244 expect((await admin.post('/skills/op', { op: 'rm -rf', name: 'x' })).statusCode).toBe(400);245 h.rt.skills = [{ nope: true }];246 expect((await admin.get('/skills')).statusCode).toBe(502);247 });248 it('registry: read-only search through the adapter; unreachable ⇒ connected:false; install ⇒ 501, nothing fetched', async () => {249 const res = (await admin.get('/skills/registry/search?q=paper%20review')).json<{ connected: boolean; results: Record<string, unknown>[] }>();250 expect(res.connected).toBe(true);251 expect(res.results).toHaveLength(2);252 expect(res.results[0]).toEqual({ name: '@research/paper-review', type: 'skill', summary: '<b>review</b>', latestVersion: '1.2.0', license: 'MIT', url: 'https://skills.test/a/research/paper-review', publisher: 'Research', verified: true });253 expect(res.results[1]?.url).toBeNull(); // javascript: URL dropped254 const call = fetchCalls.find((c) => c.url.startsWith('https://skills.test/')) as { url: string; init?: RequestInit };255 expect(call.url).toBe('https://skills.test/api/v1/artifacts?q=paper+review&sort=relevance&per_page=20');256 expect((call.init?.method ?? 'GET')).toBe('GET');257 expect(JSON.stringify(call.init?.headers ?? {}).toLowerCase()).not.toContain('authorization');258 registryUp = false;259 expect((await admin.get('/skills/registry/search?q=x')).json()).toEqual({ connected: false, results: [] });260 registryUp = true;261 const before = fetchCalls.length;262 const rtBefore = h.rt.calls.length;263 const install = await admin.post('/skills/op', { op: 'install-registry', name: '@research/paper-review' });264 expect(install.statusCode).toBe(501);265 expect(fetchCalls.length).toBe(before);266 expect(h.rt.calls.length).toBe(rtBefore);267 });268});269270describe('system, notifications, settings, janitor', () => {271 it('status reports real checks with timestamps', async () => {272 const s = SystemStatusSchema.parse((await admin.get('/system/status')).json());273 const by = Object.fromEntries(s.components.map((c) => [c.component, c.health]));274 expect(by).toEqual({ database: 'ONLINE', runtime: 'ONLINE', tmux: 'ONLINE', claude: 'ONLINE', agentd: 'ONLINE' });275 expect(s.components.every((c) => !Number.isNaN(Date.parse(c.checkedAt)))).toBe(true);276 expect(s.host).toMatchObject({ name: 'fakehost', cpuPct: 12.5, memTotalMb: 4000, diskFreeGb: 50 });277 expect(s.claude).toEqual({ version: '9.9.9', authenticated: true });278 expect(s.counts).toEqual({ liveSessions: 0, wsClients: 0, runningTasks: 0, queuedTasks: 0 });279 });280 it('audit is paginated newest-first', async () => {281 const page1 = (await admin.get('/system/audit?limit=3')).json<{ id: string; action: string }[]>();282 expect(page1).toHaveLength(3);283 expect(Number(page1[0]?.id)).toBeGreaterThan(Number(page1[2]?.id));284 const page2 = (await admin.get(`/system/audit?limit=3&before=${page1[2]?.id}`)).json<{ id: string }[]>();285 expect(Number(page2[0]?.id)).toBeLessThan(Number(page1[2]?.id));286 expect((await admin.get('/system/audit?before=1%3BDROP')).statusCode).toBe(400);287 expect((await admin.get("/system/audit?action=auth.setup'%20OR%201=1")).json()).toEqual([]);288 });289 it('settings: defaults merged, validated, persisted', async () => {290 const defaults = (await admin.get('/settings')).json();291 expect(defaults).toEqual(UserSettings.parse({}));292 expect(defaults.notifications).toEqual({ inApp: true, webPush: false, detail: 'generic' });293 expect((await admin.req('PUT', '/settings', { ...defaults, terminalFontSize: 99 })).statusCode).toBe(400);294 expect((await admin.req('PUT', '/settings', { ...defaults, theme: 'neon' })).statusCode).toBe(400);295 const saved = await admin.req('PUT', '/settings', { ...defaults, terminalFontSize: 16, terminalFont: '<i>x</i>' });296 expect(saved.statusCode).toBe(200);297 expect((await admin.get('/settings')).json()).toMatchObject({ terminalFontSize: 16, terminalFont: '<i>x</i>', scrollback: 5000 });298 await h.server.ctx.db.q("UPDATE application_settings SET settings = '{\"terminalFontSize\": \"huge\"}'");299 expect((await admin.get('/settings')).json()).toEqual(defaults); // corrupt stored value ⇒ defaults300 });301 it('notifications: in-app only when enabled, list and mark read', async () => {302 const uid = (await h.server.ctx.db.one<{ id: string }>('SELECT id FROM users'))?.id as string;303 const n = h.server.ctx.notifier;304 await n.notify(uid, 'system', 'One', 'detail-one', null);305 await n.notify(uid, 'system', 'Two', 'detail-two', '/x');306 const list = (await admin.get('/notifications')).json<{ id: string; title: string; body: string; readAt: string | null }[]>();307 expect(list.map((x) => [x.title, x.body, x.readAt])).toEqual([['Two', '', null], ['One', '', null]]);308 expect((await admin.post('/notifications/read', { ids: [list[1]?.id] })).json()).toEqual({ ok: true, updated: 1 });309 expect((await admin.get('/notifications?unread=1')).json()).toHaveLength(1);310 expect((await admin.post('/notifications/read', { all: true })).json().updated).toBe(1);311 const s = (await admin.get('/settings')).json();312 await admin.req('PUT', '/settings', { ...s, notifications: { inApp: false, webPush: false, detail: 'generic' } });313 await n.notify(uid, 'system', 'Three', null, null);314 expect((await admin.get('/notifications')).json()).toHaveLength(2);315 });316 it('upload size cap is enforced by the server itself', async () => {317 const p = (await admin.post('/projects', { name: 'Up', mode: 'create' })).json<{ id: string }>();318 const ok = await admin.req('POST', `/projects/${p.id}/upload?path=small.bin`, undefined, { raw: Buffer.alloc(1024), headers: { 'content-type': 'application/octet-stream' } });319 expect(ok.statusCode).toBe(201);320 const big = await admin.req('POST', `/projects/${p.id}/upload?path=big.bin`, undefined, { raw: Buffer.alloc(64 * 1024 + 1), headers: { 'content-type': 'application/octet-stream' } });321 expect(big.statusCode).toBe(413);322 expect(h.rt.files.has(`${h.workspace}/up/big.bin`)).toBe(false);323 });324 it('janitor applies the retention windows and nothing more', async () => {325 const db = h.server.ctx.db;326 const uid = (await db.one<{ id: string }>('SELECT id FROM users'))?.id as string;327 const sess = await db.one<{ id: string }>("INSERT INTO terminal_sessions (owner_id, kind, title, state, cwd_abs) VALUES ($1, 'shell', 'j', 'STOPPED', '/x') RETURNING id", [uid]);328 await db.q(`INSERT INTO terminal_events (session_id, kind, at) VALUES ($1, 'old', now() - interval '91 days'), ($1, 'new', now() - interval '89 days')`, [sess?.id as string]);329 await db.q(`INSERT INTO login_attempts (key, ok, at) VALUES ('ip:j', false, now() - interval '31 days'), ('ip:j', false, now() - interval '29 days')`);330 await db.q(`INSERT INTO audit_events (actor, action, at) VALUES ('j', 'old', now() - interval '401 days'), ('j', 'new', now() - interval '399 days')`);331 await db.q(`INSERT INTO notifications (user_id, kind, title, created_at) VALUES ($1, 'system', 'old', now() - interval '91 days')`, [uid]);332 await db.q(`INSERT INTO node_metrics (node_id, at, online) VALUES ('M5M36', now() - interval '15 days', true), ('M5M36', now() - interval '13 days', true)`);333 await db.q(`INSERT INTO auth_sessions (user_id, token_hash, csrf_token, expires_at) VALUES ($1, 'old', 'c', now() - interval '8 days'), ($1, 'recent', 'c', now() - interval '6 days')`, [uid]);334 await db.q(`INSERT INTO preauth_challenges (token_hash, challenge, expires_at) VALUES ('a', 'c', now() - interval '1 minute'), ('b', 'c', now() + interval '1 minute')`);335 const live = (await db.one<{ n: number }>('SELECT count(*)::int AS n FROM auth_sessions WHERE revoked_at IS NULL AND expires_at > now()'))?.n;336 const deleted = await runJanitor(db);337 expect(deleted).toEqual({ terminal_events: 1, node_metrics: 1, login_attempts: 1, audit_events: 1, notifications: 1, auth_sessions: 1, preauth_challenges: 1 });338 expect((await db.one<{ n: number }>('SELECT count(*)::int AS n FROM auth_sessions WHERE revoked_at IS NULL AND expires_at > now()'))?.n).toBe(live);339 expect((await runJanitor(db)).terminal_events).toBe(0);340 expect((await admin.get('/sessions')).statusCode).toBe(200); // the current session survived341 });342});343