repositories / will
will
owned by admin
src/service/ops.int.test.ts
Rawimport assert from 'node:assert/strict'
import { mkdtemp, readdir, readFile } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { test } from 'node:test'
import { migrate, openDatabase } from '../shared/db.ts'
import type { AuditEvent, ChatMessageIn, DeploymentEventMirror } from '../shared/events.ts'
import { fixedClock } from '../shared/time.ts'
import { createOps } from './ops.ts'
async function freshOps(clockIso = '2026-09-12T12:00:00Z') {
const dir = await mkdtemp(join(tmpdir(), 'will-ops-'))
const db = openDatabase(join(dir, 'will.db'))
migrate(db)
const clock = fixedClock(clockIso)
const ops = createOps(
db,
{ dataDir: dir, archiveDir: join(dir, 'archive'), mediaDir: join(dir, 'media') },
clock,
)
return { ops, dir, db }
}
function audit(id: string, ts: string, kind = 'tool'): AuditEvent {
return {
id,
ts,
kind,
payload: { note: `payload-${id}` },
provenance: { revision: 'r1', worktreeHead: 'abc', worktreeDirty: false },
}
}
test('audit ingest is idempotent by event id and assigns seqs', async () => {
const { ops } = await freshOps()
const r1 = ops.ingestAudit([
audit('a', '2026-09-12T10:00:00Z'),
audit('b', '2026-09-12T11:00:00Z'),
])
assert.equal(r1.accepted, 2)
const r2 = ops.ingestAudit([audit('a', '2026-09-12T10:00:00Z')])
assert.equal(r2.accepted, 0)
assert.equal(r2.duplicates, 1)
assert.equal(ops.lastSeq(), 2)
const since = ops.eventsSince(0, 10)
assert.equal(since.length, 2)
assert.equal((since[0]?.payload as { note: string }).note, 'payload-a')
})
test('recall searches beyond the newest page in hot data and archives', async () => {
const { ops } = await freshOps()
const recent = Array.from({ length: 20 }, (_, i) =>
audit(`noise-${i}`, `2026-09-${String(i + 1).padStart(2, '0')}T00:00:00Z`),
)
ops.ingestAudit([
audit('cold-needle', '2026-01-01T00:00:00Z', 'forgotten-audit'),
audit('hot-needle', '2026-08-01T00:00:00Z', 'remembered-audit'),
...recent,
])
ops.ingestChat([
{
id: 'chat-cold-needle',
chatId: -100,
messageId: 1,
direction: 'in',
ts: '2024-01-01T00:00:00Z',
senderName: 'old friend',
text: 'remember this phrase',
},
...recent.map((_, i) => ({
id: `chat-noise-${i}`,
chatId: -100,
messageId: i + 2,
direction: 'in' as const,
ts: `2026-08-${String(i + 1).padStart(2, '0')}T00:00:00Z`,
text: 'routine chatter',
})),
])
const hot = await ops.recall({ kind: 'audit', q: 'remembered-audit', limit: 2 })
assert.deepEqual(
(hot.rows as { id: string }[]).map((r) => r.id),
['hot-needle'],
)
await ops.archiveCycle()
const coldAudit = await ops.recall({ kind: 'audit', q: 'forgotten-audit', limit: 2 })
assert.deepEqual(
(coldAudit.rows as { id: string }[]).map((r) => r.id),
['cold-needle'],
)
assert.equal(coldAudit.cold, true)
const coldChat = await ops.recall({ kind: 'chat', q: 'remember this phrase', limit: 2 })
assert.deepEqual(
(coldChat.rows as { id: string }[]).map((r) => r.id),
['chat-cold-needle'],
)
assert.equal(coldChat.cold, true)
})
test('recall orders overlapping archives and late hot rows globally', async () => {
const { ops } = await freshOps()
ops.ingestAudit([
audit('jan-01', '2026-01-01T00:00:00Z', 'shared-needle'),
audit('jan-30', '2026-01-30T00:00:00Z', 'shared-needle'),
])
await ops.archiveCycle()
ops.ingestAudit([audit('jan-15', '2026-01-15T00:00:00Z', 'shared-needle')])
await ops.archiveCycle()
ops.ingestAudit([audit('late-jan-02', '2026-01-02T00:00:00Z', 'shared-needle')])
const result = await ops.recall({ kind: 'audit', q: 'shared-needle', limit: 1 })
assert.equal((result.rows[0] as { id: string }).id, 'jan-30')
assert.equal(result.cold, true)
})
test('recall has identical Unicode case matching before and after archival', async () => {
const { ops } = await freshOps()
ops.ingestAudit([
audit('kind-unicode', '2026-01-01T00:00:00Z', 'ÄPFEL'),
audit('ÖL', '2026-01-02T00:00:00Z'),
])
ops.ingestChat([
{
id: 'chat-unicode',
chatId: -100,
messageId: 1,
direction: 'in',
ts: '2024-01-01T00:00:00Z',
senderName: 'ÄNNE',
text: 'GRÜSSE',
},
])
for (const query of [
{ kind: 'audit' as const, q: 'äpfel', id: 'kind-unicode' },
{ kind: 'audit' as const, q: 'öl', id: 'ÖL' },
{ kind: 'chat' as const, q: 'änne', id: 'chat-unicode' },
{ kind: 'chat' as const, q: 'grüsse', id: 'chat-unicode' },
]) {
const hot = await ops.recall({ kind: query.kind, q: query.q, limit: 2 })
assert.equal((hot.rows[0] as { id: string }).id, query.id)
}
await ops.archiveCycle()
for (const query of [
{ kind: 'audit' as const, q: 'äpfel', id: 'kind-unicode' },
{ kind: 'audit' as const, q: 'öl', id: 'ÖL' },
{ kind: 'chat' as const, q: 'änne', id: 'chat-unicode' },
{ kind: 'chat' as const, q: 'grüsse', id: 'chat-unicode' },
]) {
const cold = await ops.recall({ kind: query.kind, q: query.q, limit: 2 })
assert.equal((cold.rows[0] as { id: string }).id, query.id)
assert.equal(cold.cold, true)
}
})
test('recall tie ordering is limit-prefix consistent before and after archival', async () => {
const { ops } = await freshOps()
const ts = '2026-01-01T00:00:00Z'
ops.ingestAudit([audit('z', ts, 'tie'), audit('a', ts, 'tie')])
ops.ingestChat([
{ id: 'z', chatId: -100, messageId: 1, direction: 'in', ts, text: 'tie' },
{ id: 'a', chatId: -100, messageId: 2, direction: 'in', ts, text: 'tie' },
])
const assertOrder = async (kind: 'audit' | 'chat') => {
const one = await ops.recall({ kind, q: 'tie', limit: 1 })
const two = await ops.recall({ kind, q: 'tie', limit: 2 })
assert.deepEqual(
(one.rows as { id: string }[]).map((r) => r.id),
['z'],
)
assert.deepEqual(
(two.rows as { id: string }[]).map((r) => r.id),
['z', 'a'],
)
}
await assertOrder('audit')
await assertOrder('chat')
await ops.archiveCycle()
await assertOrder('audit')
await assertOrder('chat')
})
test('chat ingest is idempotent and hide tombstones', async () => {
const { ops } = await freshOps()
const msg: ChatMessageIn = {
id: 'tg-1',
chatId: -100,
messageId: 5,
direction: 'in',
ts: '2026-09-12T10:00:00Z',
updateId: 1,
senderId: '7',
text: 'hello',
}
const r1 = ops.ingestChat([msg])
assert.equal(r1.accepted, 1)
const r2 = ops.ingestChat([msg])
assert.equal(r2.duplicates, 1)
const hidden = ops.hideMessage({
messageId: 5,
requesterId: '7',
authority: 'sender',
ts: '2026-09-12T10:05:00Z',
})
assert.equal(hidden.alreadyHidden, false)
assert.equal(
ops.hideMessage({
messageId: 5,
requesterId: '1',
authority: 'oliver',
ts: '2026-09-12T10:06:00Z',
}).alreadyHidden,
true,
)
const page = await ops.chatPage({ limit: 10 })
assert.equal(page.rows.length, 1)
assert.equal(page.rows[0]?.hidden, true)
})
test('deployment events mirror idempotently', async () => {
const { ops } = await freshOps()
const e: DeploymentEventMirror = {
id: 'd1',
ts: '2026-09-12T10:00:00Z',
kind: 'promote',
revision: 'rev9',
detail: { ok: true },
}
assert.equal(ops.ingestDeploy([e]).accepted, 1)
assert.equal(ops.ingestDeploy([e]).duplicates, 1)
const recall = await ops.recall({ kind: 'deploy', limit: 10 })
assert.equal(recall.rows.length, 1)
})
test('media store is content-addressed and idempotent', async () => {
const { ops, dir } = await freshOps()
const bytes = Buffer.from('hello media')
const meta1 = await ops.putMedia(bytes, { type: 'text/plain', originalName: 'a.txt' })
const meta2 = await ops.putMedia(bytes, { type: 'text/plain', originalName: 'b.txt' })
assert.equal(meta1.digest, meta2.digest)
assert.equal(meta1.size, bytes.length)
const found = ops.getMediaMeta(meta1.digest)
assert.ok(found)
assert.equal(found.originalName, 'a.txt')
const files = await readdir(
join(dir, 'media', meta1.digest.slice(0, 2), meta1.digest.slice(2, 4)),
)
assert.deepEqual(files, [meta1.digest])
})
test('analysis cache upserts by (digest, capability, model, schemaVersion)', async () => {
const { ops } = await freshOps()
const key = {
digest: 'd'.repeat(64),
capability: 'zai_vision',
model: 'glm-5.3-flash',
schemaVersion: 1,
}
assert.equal(ops.getAnalysis(key), undefined)
ops.putAnalysis({ ...key, result: { text: 'a cat' } })
ops.putAnalysis({ ...key, result: { text: 'a cat v2' } })
const got = ops.getAnalysis(key)
assert.deepEqual(got?.result, { text: 'a cat v2' })
const analyses = await ops.recall({ kind: 'analysis', digest: key.digest })
assert.equal(analyses.rows.length, 1)
})
test('archive cycle moves old audit rows exactly once and cold reads serve them', async () => {
const { ops, dir } = await freshOps()
// clock now = 2026-09-12; insert rows older than 90 days and one fresh row
ops.ingestAudit([
audit('old1', '2026-01-05T00:00:00Z'),
audit('old2', '2026-01-20T00:00:00Z'),
audit('old3', '2026-03-01T00:00:00Z'),
audit('new1', '2026-09-01T00:00:00Z'),
])
const result = await ops.archiveCycle()
assert.equal(result.exportedAudit, 3)
assert.equal(result.segments, 2) // 2026-01 and 2026-03
// hot table only has the fresh row
assert.equal(ops.counts().audit, 1)
assert.equal(ops.eventsSince(0, 100).length, 1)
// timeline cold fallback pages in archived rows (newest first)
const page = await ops.timeline({ limit: 10 })
assert.equal(page.rows.length, 4)
assert.equal(page.cold, true)
assert.equal(page.rows[0]?.id, 'new1')
assert.equal(page.rows[3]?.id, 'old1')
assert.equal(page.rows[3]?.archived, true)
// sidecar indexes exist and are versioned
const indexFiles: string[] = []
for (const month of ['2026-01', '2026-03']) {
for (const f of await readdir(join(dir, 'archive', 'audit', month))) {
if (f.endsWith('.index.json')) indexFiles.push(f)
}
}
assert.equal(indexFiles.length, 2)
const sidecar = JSON.parse(
await readFile(join(dir, 'archive', 'audit', '2026-01', indexFiles[0] as string), 'utf8'),
)
assert.equal(sidecar.version, 1)
assert.equal(sidecar.kind, 'audit')
assert.equal(sidecar.count, 2)
// second cycle exports nothing new (move exactly once)
const second = await ops.archiveCycle()
assert.equal(second.exportedAudit, 0)
})
test('crash between publish and delete does not duplicate rows (exactly once)', async () => {
const { ops } = await freshOps()
ops.ingestAudit([audit('old1', '2026-01-05T00:00:00Z')])
// First cycle archives AND deletes. Simulate crash-after-publish by inserting
// the same row back (as a crashed cycle would have left it) before cycle 2.
await ops.archiveCycle()
ops.ingestAudit([audit('old1', '2026-01-05T00:00:00Z')])
const second = await ops.archiveCycle()
assert.equal(second.exportedAudit, 0)
assert.equal(ops.counts().audit, 0)
const page = await ops.timeline({ limit: 10 })
assert.equal(page.rows.length, 1)
})
test('archive cycle moves chat after one year and tombstones keep filtering', async () => {
const { ops } = await freshOps()
ops.ingestChat([
{
id: 'tg-old',
chatId: -100,
messageId: 1,
direction: 'in',
ts: '2024-09-01T00:00:00Z',
text: 'ancient',
},
{
id: 'tg-new',
chatId: -100,
messageId: 2,
direction: 'in',
ts: '2026-08-01T00:00:00Z',
text: 'recent',
},
])
ops.hideMessage({
messageId: 1,
requesterId: '1',
authority: 'oliver',
ts: '2026-09-12T00:00:00Z',
})
const result = await ops.archiveCycle()
assert.equal(result.exportedChat, 1)
const page = await ops.chatPage({ limit: 10 })
assert.equal(page.rows.length, 2)
// hidden ancient message stays hidden even after archival
const hiddenRow = page.rows.find((r) => r.id === 'tg-old')
assert.equal(hiddenRow?.hidden, true)
const recall = await ops.recall({ kind: 'chat', limit: 10 })
assert.equal(recall.rows.length, 1) // hidden rows are excluded from recall
})
test('quick check reports ok and jobs are recorded', async () => {
const { ops } = await freshOps()
assert.equal(await ops.quickCheck(), 'ok')
// Default thresholds depend on the host filesystem's free space; pin them
// so storageState is deterministic regardless of the environment.
ops.setStorageThresholds(0, 0)
const status = await ops.status()
assert.equal(status.schemaVersion, 1)
assert.ok(status.storage.freePct >= 0)
assert.equal(status.storageState, 'ok')
assert.ok(status.lastQuickCheck)
// Any non-negative freePct sits below huge thresholds: warn, then stop.
ops.setStorageThresholds(1000, 0)
assert.equal((await ops.status()).storageState, 'warn')
ops.setStorageThresholds(1000, 1000)
assert.equal((await ops.status()).storageState, 'stop')
})
test('vacuum runs without error', async () => {
const { ops } = await freshOps()
await ops.vacuum()
await ops.reconcile()
})