Salta ai contenuti

0005. Test on real boundaries and gate every pull request

Questi contenuti non sono ancora disponibili nella tua lingua.

  • Status: accepted, revised after the structure review (round 1); its mention of Graphile’s tables is superseded by 0019
  • Date: 2026-10-26

A PMS moves money, guests and availability on public channels: the costly failures are overbooking, data shown to the wrong tenant, duplicated charges and lost reservations. The analysis (chapter 18) and the research report docs/analysis/reviews/testing-quality-research.md define the strategy; this ADR fixes what the repository enforces from the first commit.

Levels and tools

Level Tool Rule
Unit Vitest 5 (*.unit.test.ts, next to the code) No I/O; small fakes for ports, never mocks of repositories inside pure rules.
Property fast-check 4 (*.property.test.ts) Domain algorithms are compared with an independent, simpler model. 1,000 runs per property in pull requests, 10,000 at night; the seed is printed and every counterexample becomes a regular test.
Database Vitest with PostgreSQL 17 through Testcontainers Migrations run for real. Tests connect with the application role (NOSUPERUSER NOBYPASSRLS, not the owner), verified in the catalog. PGlite may speed up local work but never replaces this gate.
RLS a generated matrix, table × role × operation × scenario, in db/test/rls An inventory in db classifies every table as workspace, organization or system scoped, with its owner, the roles that may use it and the expected access mechanism; views and privileged functions are inventoried separately. An unclassified table, view or function fails the build, and the inventory is compared with the real catalog. Workspace and organization tables are tested for read, insert, update, delete, upsert and moving rows across tenants, with USING and WITH CHECK, for both runtime roles, plus missing or empty context, multi-workspace reads, composite keys, views, function grants and search_path, connection reuse and a real transaction pooler. Better Auth and Graphile tables are listed explicitly, without pretending they use workspace RLS.
HTTP clients the Channex simulator; MSW 3 where a request must be intercepted No real network in pull requests. Channex staging runs at night only, with project credentials, and “no credentials” is reported as “not run”, never as “passed”.
API Schemathesis against the real server and a throwaway database; Spectral and oasdiff on the spec Two workspaces and two users in the fixtures, so that fuzzing exercises authorization.
Components and end-to-end Vitest browser mode, Playwright with axe (tags up to wcag22aa) On the production build, with the simulator.
Mutation StrykerJS 10 on core After its toolchain canary passes (it needs the TypeScript 6 API).

Test topology

  • Projects: Node unit and property tests per package (vitest.config.ts), Node integration tests per package (vitest.integration.config.ts), Chromium component tests (*.component.test.tsx), and a root acceptance workspace in tests/ for composed API, authorization, fault and artifact tests. Product packages never depend on the acceptance workspace.
  • PostgreSQL 17, and later the pooler, start once from the root integration setup, which migrates a template and gives each parallel worker an isolated database; teardown also runs on failure. pnpm test (per package) never starts Docker.
  • packages/testing holds only generic helpers (clocks, random sources, the PostgreSQL lifecycle with an injected migration step) and depends on no product package, so that it creates no dependency cycle. Domain factories live with the package that owns the domain; seeds of the whole backend live in the acceptance workspace.

Coverage: one Vitest process from the repository root collects V8 coverage for every project. The denominator includes all hand-written sources, also files that no test imports; only tests, declarations and generated code are excluded. Thresholds are per package, as in the analysis appendix B: core 95/90/95/95 (statements, branches, functions, lines), its critical modules 99/95/100/99 per file, db, api, adapters and the worker 90/85/90/90, clients 95/90/95/95, UI 85/80/85/85, global minimum 85/80/85/85. Lines changed by a pull request must be 90% covered, 100% for money, isolation and sync. A missing or empty report fails the gate, and so does a package that declares tests but has no covered file. SQL, RLS and end-to-end evidence are reported separately, not added to the JavaScript denominator.

Flaky tests: no retries for unit and integration tests; at most one diagnostic retry for end-to-end tests, which still marks the test flaky. Quarantine needs an issue, an owner and an expiry within seven days, and never applies to isolation, money or data loss.

Required checks on every pull request, all runnable locally without any account: frozen install; Biome; dependency-cruiser and the license direction check; Knip; type checking; tests with coverage; REUSE; migration lint once migrations exist; contract checks once the API exists. At night (.github/workflows/nightly.yml): the property tests with 10,000 runs, under time zones far from UTC. Since the repository is public (2026-10-28): secret scanning with push protection and CodeQL, which GitHub offers free to public repositories and only with GitHub Advanced Security to private ones. Dependency review is not enabled yet.

  • Contributors need Docker to run the full suite (pnpm test:coverage); pnpm test stays Docker-free for quick feedback.
  • Coverage is reported locally and in the CI summary; no external coverage service is required. Codecov can be added later for trends.
  • The thresholds start high because the code base starts empty; they are lowered only through an ADR.
  • pg-mem or PGlite as the database gate. Rejected: they cannot prove RLS, locks, concurrent connections or pooler behaviour.
  • Coverage per package process, merged afterwards. More moving parts; kept as the fallback if one process becomes too slow.