Testing
Rakkr leans on a broad, mostly hardware-free test suite plus a set of Linux-only
hardware/loopback smokes. Everything is wired into mise tasks; the full list is
in the tasks reference.
Unit & integration tests
pnpm --filter @rakkr/api test # controller APIpnpm --filter @rakkr/web test # web console helpers/componentspnpm --filter @rakkr/shared check # shared contracts type-checkpnpm --filter @rakkr/db check # db package type-checkmise run node:test-db # concurrency/race DB tests (needs Postgres)The API test runner sets RAKKR_API_NO_LISTEN=1 and, by default, removes
DATABASE_URL so tests run against the in-memory/JSON fallback stores. This is
why the controller is designed to work without a database — see the
data model.
Database-backed tests
DB tests come in two flavours, chosen by what they need to exercise:
- Persistence / round-trip tests (real SQL semantics: constraints, bigint
widths, JSONB, transactions, per-column persistence) run against an in-process
PGlite (WASM Postgres) database, so they need no running server and are
part of the default
node:testsuite. A test callscreatePgliteDatabase()from@rakkr/db, which spins up a fresh instance, applies the Drizzle migrations, and hands back apglite://…url thatcreateDatabase(and thus every store andLocalAuthService) resolves to that instance. SetDATABASE_URLto the url (or pass it directly), and close the handle in anafter/finally. - Concurrency / race tests (row-lock and atomic compare-and-set contention —
double-claim, last-writer-wins, FK races) need genuinely concurrent Postgres
connections. PGlite is single-connection and serializes transactions, so it
cannot reproduce them — a race test on PGlite would pass even with the
production lock removed, giving false confidence. These keep the
RAKKR_API_TEST_DATABASE_URLskip guard and run viamise run node:test-db, which provisions a throwaway Postgres, migrates it, runs the tagged files (listed inpackages/db/scripts/run-db-integration-tests.mjs), and drops it. SetRAKKR_API_TEST_DATABASE_URLto point at a reachable Postgres.
Rust tests, Clippy, and Miri run via:
mise run rust:check rust:clippy rust:miriMiri checks the agent for undefined behavior; use Miri-compatible patterns where existing tests rely on it (the SQLite health store, for example, is unavailable under Miri).
The fake-controller smoke
The most important hardware-free integration test is the fake-controller
smoke (scripts/agent-fake-controller-smoke*.mjs), part of mise run check:
mise run agent:fake-controller-smokeIt drives the real recorder agent against a stub controller and exercises the full job lifecycle — claim-next, heartbeat/status polling, capture start/runtime/ too-small failures, channel-map application, controller-terminal handoff, rendered MP3/VBR output, recorder-cache retention and delete-after-upload, idle sweeps, upload-failure handling, controller stop requests, and the various failure/recovery health events — without any audio hardware.
Audio fixtures
A checked golden fixture, fixtures/audio/rakkr-golden-dialogue-clean.wav, is a
clean 48 kHz stereo multi-speaker speech file. The loopback smokes replay it
through ALSA loopback and derive deterministic fault lanes (clipping, low
volume, duplicated-channel correlation, noisy speech) to validate the agent’s
quality scoring and health-event behavior. See
fixtures/audio/README.md.
Linux hardware & loopback smokes
These require the matching Linux device or snd-aloop setup and are not
portable Windows gates — run them only where the hardware/loopback exists:
mise run agent:loopback-smokemise run agent:loopback-meter-smokemise run agent:loopback-render-smokemise run agent:loopback-fixture-smokemise run agent:loopback-job-smokemise run agent:alsa-capture-smokemise run agent:alsa-meter-smokemise run agent:alsa-job-smokeNode-lifecycle smokes (via the Ansible runner) need the Compose test rig or a physical target:
docker compose up -d --build ansible-runner recorder-test-rigmise run ansible:runner-smokemise run ansible:x32-smoke # physical X32 rig; see Node lifecycle guideBaseline verifiers
A large class of “tests” are the baseline verifiers — scripts that assert the
documented invariants still match the source. They run as part of mise run check
and are explained in Baselines & verification.
What to run when
| You changed… | Run at least |
|---|---|
| Controller API routes/stores | node:check, node:test, the relevant baseline verifier |
| Web console | node:test, node:lint, node:format-check |
| Shared contracts | pnpm --filter @rakkr/shared check, then dependents |
| DB schema | db:generate, db:verify |
| Recorder agent (sync/jobs/health/cache/capture) | rust:check, rust:clippy, rust:miri, agent:fake-controller-smoke |
| An invariant with a baseline | the matching *:check verifier + its baseline doc |
When you can’t run the full gate, state exactly what you ran and why the rest was skipped (especially hardware-only smokes).