SPB Git

Repository Public

misc.test.ts 23.9 KB 343 lines typescript

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
Files main
Menu

Theme