Testing
Which command proves which change, and the suites that exist to stay unbroken.
Run the narrowest command for what you changed. Never a root-level build unless the change is whole-workspace orchestration.
No test command needs a running database
The backend suites use a stateful in-memory client fake at
apps/backend/test/prisma.fake.ts. The whole suite runs with no external
services.
Which command
| Change | Command |
|---|---|
| Backend TypeScript | pnpm --filter @sthora/backend lint |
| Backend logic | pnpm --filter @sthora/backend test |
| Backend routes, bootstrap, config, or API docs | pnpm --filter @sthora/backend test:e2e |
| Prisma schema | db:generate, then build, then test:e2e |
| Shared contracts | pnpm --filter @sthora/shared build, then each consumer's tests |
| Dashboard | pnpm --filter @sthora/dashboard test, then build |
| Marketing site | pnpm --filter @sthora/website test, then build |
| These docs | pnpm --filter @sthora/docs test, then build |
| Frontend route files added or renamed | Regenerate the route tree, then build |
| Whole workspace | pnpm build |
Fix lint failures before running tests. Fix failing tests before widening scope. Report the exact commands you ran and their outcome.
The suites that exist to stay unbroken
Three files are security regression suites. A case is never deleted to make a change pass.
| File | Guards |
|---|---|
compose-sanitiser.spec.ts | One case per reject rule |
archive-entry-path.spec.ts | Zip-slip and path traversal |
proxy.service.spec.ts | The Traefik dashboard staying unexposed, and the socket staying read-only |
Two more are worth knowing about:
openssh-key-interop.spec.tschecks generated deploy keys againstssh-keygenitself.- The TOTP implementation is verified against RFC 6238's own published test vectors.
Test quality
- High signal over assertion count. A few strong regression tests beat many shallow ones.
- Every bug fix adds at least one test that fails before the fix and passes after it.
- Test observable behaviour, not implementation detail. A refactor that preserves behaviour must not require test edits.
- Name a test as a sentence describing the behaviour and its condition, not the method name.
- Tests are deterministic: no real network, no wall-clock dependence, no shared mutable state, no ordering assumptions.
Placement
| Kind | Location |
|---|---|
| Unit | Beside the code under test |
| End-to-end | The package's top-level test/ folder |
| Component or route (frontend) | Beside the component or route file |
Frontend specifics
Query by accessible role and text, the way a user finds things — never by class name or internal component structure. Mock network calls at the transport boundary, not by stubbing the app's own data-fetching hooks. Keep at least one smoke test per app that renders its entry route, so a broken shell fails fast.
This site's own tests
apps/docs asserts that every page in content/docs/ carries a title and a description, that
every page reaches the sitemap, and that the Markdown URL encoding round-trips. A page with no
description is invisible until a search result renders empty, and a page missing from the
sitemap is invisible to a crawler — neither shows up by looking at the page.