CODEX // oaoisme wiki

§ unswayed-backend · The agent operating model

Testing & coverage

updated 2026-06-09

Testing & coverage

"Strict testing for everything" is a policy with teeth here. This page explains how it's enforced on the NestJS stack and why it's shaped this way.

TDD, then the pyramid

Work starts with a failing test (red → green → refactor). Tests are organized as a pyramid, all on Jest:

  • Unit — services, pure logic, and edge cases, fast and isolated; colocated as src/**/*.spec.ts.
  • Integration — a module's providers/controllers wired through the @nestjs/testing DI container.
  • End-to-end — the real Nest app booted and driven over HTTP with supertest (test/*.e2e-spec.ts): status codes, response shapes, error bodies, auth, and stateful effects.

The scaffold ships one of each: a controller unit spec, a module DI smoke spec, and an e2e spec that hits GET /.

The 90% coverage gate

package.jsonjest.coverageThreshold.global enforces ≥ 90% on statements, branches, functions, and lines (v8 provider). collectCoverageFrom counts all of src except main.ts (bootstrap) and the spec files; the module smoke test keeps modules counted. npm run verify (lint + typecheck + coverage + e2e) fails below the gate — a hard build failure, not a dashboard you can ignore.

The one inviolable rule: never lower the threshold or disable a test to go green. Add the missing test or delete dead code. Changing the gate at all requires a recorded decision (an ADR).

Why SWC transforms the tests

This is the subtle part. With ts-jest, TypeScript's emitDecoratorMetadata injects a conditional helper (a branch) into every decorated DI constructor — code you can't meaningfully test. On a single controller that dragged branch coverage down to ~75%, so a true 90% branch gate was unreachable without padding.

Switching the Jest transform to SWC (@swc/jest, configured in .swcrc with legacyDecorator + decoratorMetadata) eliminates the phantom branch, so the scaffold reports an honest 100%. The trade-off: SWC doesn't type-check, so tsc runs as a separate npm run typecheck step inside verify. (See ADR-0006 in docs/DECISIONS.md.)

E2E never reads the developer's .env

E2E runs against .env.test, but for a long time the developer's real .env could still leak into the run: @prisma/client (and the prisma CLI) auto-load .env through a bundled dotenv the moment they're imported, injecting live keys (CLOUDINARY_URL, OPENAI_API_KEY, RAPIDAPI_KEY, …) that flip the storage / AI / external-jobs ports from their deterministic stubs to the real adapters mid-suite. The first defence was a curated blanklist — but a list only protects the keys someone remembered to add.

Since Phase 6 the defence is structural, in two layers (test/env-shelf.ts):

  1. The shelf. Jest's globalSetup renames .env.env.e2e-shelved before anything can read it; globalTeardown renames it back, pass or fail. A shelf stranded by a hard-killed run self-heals on the next setup. With the file gone, every key — present and future — is protected with zero maintenance. (The production service reads .env only at boot, so a running server doesn't notice a test run.)
  2. The shell guard. test/e2e-setup.ts still blanks the integration keys per worker, because a developer may have export CLOUDINARY_URL=… in the shell environment — which no file shelving can remove. The keys are set to '' (not deleted): dotenv never overrides an existing key, and every configured predicate reads '' as falsy → stub.

Proving the harness on day one

The scaffold's controller, service, and module are fully covered out of the box, so npm run verify is green from the first commit (100% coverage, lint clean, e2e passing) — the gate is real, not aspirational. The first real feature module (nest g resource) extends the suite the same way: unit specs for the service, an e2e spec for the route.