Skip to content
open-ismsDocs

Search documentation

Find a page by title or topic

Testing

Unit tests, end-to-end tests, and the checks CI runs on every push.

Four workflows, and everything they run is available locally.

Locally, before a push#

bun run typecheck            # strict, must be clean
bun run test:unit            # unit tests under lib, server, schema
bun run build                # the full Next.js build

Migration guards, which are fast and catch the mistakes that are expensive later:

bun run check:migrations              # journals match their .sql files
bun run check:migration-immutability  # nothing shipped has been edited
bun run check:migration-safety        # no one-step constraints on existing tables

The end-to-end suite#

Playwright against a real stack: Postgres and MinIO in Docker, migrated with the same runtime-migrate.mjs the image runs, seeded, built and served. No secrets anywhere; the whole stack is throwaway localhost dummies.

bun run test:l0     # schema-drift and coverage checks, seconds, no browser
bun run e2e         # the browser suite
bun run e2e:ui      # the same, with the Playwright UI
bun run e2e:image   # against the published image rather than a local build

test:l0 exists to fail fast. It catches schema drift and coverage gaps in seconds, before anything spends minutes starting browsers.

What CI runs#

WorkflowTriggerWhat it proves
CIevery pushtypecheck, unit tests, build, migration guards, shellcheck, and that the framework reference table still matches the data
CI / migration-failureevery pusha failing migration leaves the database untouched, by actually running one
CI / composeevery pushthe self-host compose file and the Caddy config are valid
E2Epull requeststhe browser suite against a real stack
Self-host stackpull requests, and weeklyinstall, update, rollback and a full backup-restore drill against real containers
Releasemanualsee Releases

The weekly run of the self-host workflow is not redundant. Base images move underneath us: Postgres, node, MinIO, the updater. A scheduled run catches a stack that broke without anyone touching the repository.

The drills#

Three of those jobs are drills rather than tests, in the sense that they perform the failure instead of asserting a property of the code:

  • Migration failure. Runs a migration that fails, then checks the database is exactly as it was.
  • Lifecycle. Installs from scratch, updates to a second version through the updater API, then rolls back to the pinned previous version and checks it serves against the upgraded schema. It also asserts no service publishes a port beyond loopback and that the updater publishes none at all.
  • Backup drill. Writes a database row and an object, takes a backup, destroys every volume, restores from the archive alone, and asserts both halves came back. A backup nobody has restored is a belief, not a control, and this project sells backup management as a NIS 2 measure, so the bar is higher here than "the job ran without error".

Writing tests#

Unit tests live next to what they test and run under bun test. End-to-end specs live in e2e/.

The useful question for a new test is which of the two it is. If it can be answered by calling a function with arguments, it is a unit test and belongs where the function is. If it only means something with a browser, a session and a database, it is an end-to-end spec, and it is worth writing it against behaviour a user would notice rather than against an implementation detail that will move.