Skip to content
Looms
Documentation
On this page

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.

ts
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 })    }  })}
bash
bun test durable-review.test.ts

Each 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.