repositories / will
will
owned by admin
src/service/main.int.test.ts
Rawimport 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'), '<!doctype html><title>will</title>')
await writeFile(join(docsDist, 'manifest.json'), JSON.stringify({ pages: ['architecture'] }))
await writeFile(join(docsDist, 'architecture.html'), '<h1>architecture</h1>')
// 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(), /<title>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()
}
})