import assert from 'node:assert/strict' import { mkdir, mkdtemp, writeFile } from 'node:fs/promises' import { tmpdir } from 'node:os' import { join } from 'node:path' import { test } from 'node:test' import { fileURLToPath } from 'node:url' import { type RunningService, startService } from './main.ts' const here = fileURLToPath(new URL('.', import.meta.url)) async function startTestService(): Promise<{ service: RunningService base: (path: string) => string internal: (path: string) => string }> { const dataDir = await mkdtemp(join(tmpdir(), 'will-svc-')) const webDist = join(dataDir, 'web-dist') const docsDist = join(dataDir, 'docs-dist') await mkdir(webDist, { recursive: true }) await mkdir(docsDist, { recursive: true }) await writeFile(join(webDist, 'index.html'), 'will') await writeFile(join(docsDist, 'manifest.json'), JSON.stringify({ pages: ['architecture'] })) await writeFile(join(docsDist, 'architecture.html'), '

architecture

') // ephemeral ports const publicPort = 0 const service = await startService({ dataDir, publicAddr: `127.0.0.1:${publicPort}`, internalAddr: '127.0.0.1:0', webDist, docsDist, mediaMaxBytes: 1000 << 10, // 0/0 pins storageState to 'ok' for every non-negative freePct; tests // that need warn/stop patch the service meta thresholds explicitly. storageWarnPct: 0, storageStopPct: 0, workerUrl: join(here, 'worker.ts'), }) const pubAddr = service.publicServer.address() const intAddr = service.internalServer.address() if ( pubAddr === null || typeof pubAddr !== 'object' || intAddr === null || typeof intAddr !== 'object' ) { throw new Error('no listen address') } return { service, base: (p: string) => `http://127.0.0.1:${pubAddr.port}${p}`, internal: (p: string) => `http://127.0.0.1:${intAddr.port}${p}`, } } test('healthz ok, index served, docs manifest served', async () => { const { service, base } = await startTestService() try { const health = await fetch(base('/healthz')) assert.equal(health.status, 200) assert.equal(((await health.json()) as { ok: boolean }).ok, true) const index = await fetch(base('/')) assert.match(await index.text(), /will<\/title>/) const docs = await fetch(base('/api/docs')) assert.deepEqual(((await docs.json()) as { pages: string[] }).pages, ['architecture']) const doc = await fetch(base('/docs/architecture')) assert.match(await doc.text(), /architecture/) } finally { await service.close() } }) test('path traversal is rejected', async () => { const { service, base } = await startTestService() try { const res = await fetch(base('/docs/..%2F..%2Fetc%2Fpasswd')) assert.equal(res.status, 404) const res2 = await fetch(base('/assets/../../etc/passwd')) assert.ok(res2.status === 403 || res2.status === 404) } finally { await service.close() } }) test('audit ingest -> timeline -> SSE replay with Last-Event-ID', async () => { const { service, base, internal } = await startTestService() try { for (let i = 1; i <= 3; i++) { const res = await fetch(internal('/v1/audit/ingest'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ events: [{ id: `e${i}`, ts: new Date().toISOString(), kind: 'tool', payload: { i } }], }), }) assert.equal(res.status, 200) const body = (await res.json()) as { accepted: number } assert.equal(body.accepted, 1) } const page = await fetch(base('/api/timeline?limit=10')) const timeline = (await page.json()) as { rows: { id: string; seq: number }[] } assert.equal(timeline.rows.length, 3) // SSE replay from seq 1 misses nothing after seq 1 const controller = new AbortController() const stream = await fetch(base('/api/stream?lastEventId=1'), { signal: controller.signal, headers: { accept: 'text/event-stream' }, }) assert.equal(stream.status, 200) const reader = stream.body?.getReader() if (!reader) throw new Error('no stream body') const decoder = new TextDecoder() let buf = '' const ids: number[] = [] while (ids.length < 2) { const { value, done } = await reader.read() if (done) break buf += decoder.decode(value, { stream: true }) for (const m of buf.matchAll(/^id: (\d+)$/gm)) ids.push(Number(m[1])) buf = buf.split('\n\n').pop() ?? '' } controller.abort() assert.deepEqual(ids, [2, 3]) // live notification: ingest while stream open const liveController = new AbortController() const live = await fetch(base('/api/stream'), { signal: liveController.signal }) const liveReader = live.body?.getReader() if (!liveReader) throw new Error('no live stream body') await fetch(internal('/v1/audit/ingest'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ events: [{ id: 'e4', ts: new Date().toISOString(), kind: 'tool', payload: {} }], }), }) let liveBuf = '' let sawE4 = false for (let i = 0; i < 20 && !sawE4; i++) { const { value, done } = await liveReader.read() if (done) break liveBuf += decoder.decode(value, { stream: true }) if (liveBuf.includes('e4')) sawE4 = true } liveController.abort() controller.abort() assert.ok(sawE4, 'live SSE delivery after ingest') } finally { await service.close() } }) test('media upload, meta, public download round-trip', async () => { const { service, base, internal } = await startTestService() try { const bytes = Buffer.from('media-bytes-here') const up = (await fetch(internal('/v1/media'), { method: 'POST', headers: { 'content-type': 'application/octet-stream', 'x-will-media-type': 'image/png', 'x-will-media-name': 'x.png', }, body: new Uint8Array(bytes), })) as Response assert.equal(up.status, 201) const meta = (await up.json()) as { digest: string; size: number } assert.equal(meta.size, bytes.length) const metaRes = await fetch(internal(`/v1/media/${meta.digest}/meta`)) assert.equal(((await metaRes.json()) as { originalName: string }).originalName, 'x.png') const dl = await fetch(base(`/api/media/${meta.digest}`)) assert.equal(dl.status, 200) assert.equal(dl.headers.get('content-type'), 'image/png') assert.deepEqual(Buffer.from(await dl.arrayBuffer()), bytes) const missing = await fetch(base(`/api/media/${'0'.repeat(64)}`)) assert.equal(missing.status, 404) } finally { await service.close() } }) test('chat ingest, hide via internal API, tombstone filters public chat', async () => { const { service, base, internal } = await startTestService() try { const msg = { id: 'tg-1', chatId: -100, messageId: 9, direction: 'in', ts: new Date().toISOString(), senderId: '7', senderName: 'friend', text: 'hello will', triggerReason: 'mention', } const ingest = await fetch(internal('/v1/chat/ingest'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ messages: [msg] }), }) assert.equal(ingest.status, 200) const before = await fetch(base('/api/chat')) const rows1 = (await before.json()) as { rows: { hidden: boolean }[] } assert.equal(rows1.rows.length, 1) assert.equal(rows1.rows[0]?.hidden, false) const hide = await fetch(internal('/v1/chat/hide'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ messageId: 9, requesterId: '7', authority: 'sender', ts: new Date().toISOString(), }), }) assert.equal(hide.status, 200) const after = await fetch(base('/api/chat')) const rows2 = (await after.json()) as { rows: { hidden: boolean }[] } assert.equal(rows2.rows[0]?.hidden, true) } finally { await service.close() } }) test('status reports schema, counts and storage; recall works over HTTP', async () => { const { service, base, internal } = await startTestService() try { await fetch(internal('/v1/audit/ingest'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ events: [{ id: 'x', ts: new Date().toISOString(), kind: 'boot', payload: {} }], }), }) const status = await fetch(base('/api/status')) const body = (await status.json()) as { schemaVersion: number counts: { audit: number } storageState: string } assert.equal(body.schemaVersion, 1) assert.equal(body.counts.audit, 1) assert.equal(body.storageState, 'ok') const recall = await fetch(internal('/v1/recall'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ kind: 'audit', q: 'boot', limit: 5 }), }) const rbody = (await recall.json()) as { rows: unknown[] } assert.equal(rbody.rows.length, 1) } finally { await service.close() } }) test('usage view aggregates turn_usage rows into daily cost', async () => { const { service, base, internal } = await startTestService() try { const ingest = (events: unknown[]) => fetch(internal('/v1/audit/ingest'), { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ events }), }) const today = new Date().toISOString().slice(0, 11) const res = await ingest([ { id: 'u1', ts: `${today}00:00:00Z`, kind: 'turn_usage', payload: { provider: 'zai', model: 'glm-5.3-flash', input: 2_000_000, output: 100_000, cacheRead: 1_000_000, cacheWrite: 0, responses: 3, }, }, { id: 'u2', ts: `${today}09:00:00Z`, kind: 'turn_usage', payload: { provider: 'other', model: 'mystery', input: 5, responses: 1 }, }, { id: 'u3', ts: new Date().toISOString(), kind: 'agent_end', payload: {} }, ]) assert.equal(res.status, 200) const page = await fetch(base('/api/usage')) assert.equal(page.status, 200) const report = (await page.json()) as { days: { date: string; input: number; responses: number; costUsd: number | null }[] totals: { input: number; costUsd: number | null; responses: number } projection: { todayUsd: number | null; projectedEomUsd: number | null } } assert.equal(report.days.length, 1) assert.equal(report.days[0]?.input, 2_000_005) assert.equal(report.days[0]?.responses, 4) // one unpriced model among the day's rows => cost withheld for that day assert.equal(report.days[0]?.costUsd, null) assert.equal(report.projection.projectedEomUsd, null) } finally { await service.close() } })