Test execution and recovery
Test the application's observable behavior: whether a rejected action occurs, whether a pending wait survives restart, and whether replay changes anything outside the runtime.
Use deterministic agents#
The quickstart's writer uses runTurn and a typed tool without a model API. This makes approval and recovery tests reproducible. Keep model quality evaluations separate from runtime correctness tests.
Exercise both decision branches#
Start the release workflow, find its pending approval, and send approve or reject. Assert on the publication result, not just workflow completion. Rejection is a valid completed business outcome. Use the complete executable test below in the same directory as the two quickstart files.
import { expect, test } from 'bun:test'import { mkdtemp, rm } from 'node:fs/promises'import { tmpdir } from 'node:os'import { join } from 'node:path'import { z } from 'zod'const started = z.object({ runId: z.string(), status: z.string() })for (const choice of ['approve', 'reject'] as const) { test(`a new process can ${choice} a persisted review`, async () => { const dir = await mkdtemp(join(tmpdir(), 'looms-review-')) async function run(args: string[]) { const child = Bun.spawn( [process.execPath, join(import.meta.dir, 'durable-review.ts'), ...args], { env: { ...process.env, LOOMS_DB: join(dir, 'review.sqlite') }, stdout: 'pipe', stderr: 'pipe', }, ) const output = await new Response(child.stdout).text() const errors = await new Response(child.stderr).text() expect(await child.exited, errors).toBe(0) return output } try { const first = await run(['start']) const { runId } = started.parse(JSON.parse(first.split('\n')[0]!)) const before = await run(['inspect', runId]) expect(before).toContain('"status": "pending"') const after = await run([choice, runId]) expect(after).toContain('"status": "completed"') expect(after).toContain(`"published": ${choice === 'approve'}`) const recovered = await run(['inspect', runId]) expect(recovered).toContain(`"published": ${choice === 'approve'}`) expect(recovered).toContain('agent.tool.result') } finally { await rm(dir, { recursive: true, force: true }) } })}bun test durable-review.test.tsEach command runs in a different process against the same temporary SQLite file. The test verifies a pending review survives exit, either decision resumes it, and the final result survives another restart.
Module and replay helpers#
Install @swirls/looms/testing for createTestRuntime, assertReplayDeterministic, and moduleConformance. createTestRuntime supplies an in-memory store and a run helper for Effect-based runtime operations. assertReplayDeterministic compares repeated folds of a log; it does not prove external idempotency or compatibility with future code.
Before deploying#
- Fault-inject an interrupted effect with a provider-supported idempotency key.
- Send duplicate and conflicting webhook/approval requests through your gateway.
- Load a snapshot and history produced by the previous application version.
- Test authorization for another tenant's run and event stream.
- Disconnect and reconnect the UI while execution continues.
- Verify your backup restoration and downstream index backfill.