# ADR-011: TDD Foundation

**Status:** Accepted
**Date:** 2026-05-05
**Supersedes:** none

## Context

The vertical-feature monorepo refactor (ADRs 001-010) established
clean architecture with per-feature DI containers but did not enforce
TDD as the path of least resistance. Agentic workers were producing
code-first commits with tests added later, leading to test theatre and
mock/real drift.

## Decision

1. **New package `@repo/core-testing` (tag: tooling)** — shared test
   utilities: defineFactory, defineContractSuite, renderWithProviders,
   mock-payload helpers, jsdom setup file. Tagged `tooling` so any
   package may depend on it as devDependency without boundary violation.

2. **Vitest base configs split into node + jsdom** with safety defaults
   (clearMocks, restoreMocks, mockReset, unstubGlobals, sequence.shuffle)
   and coverage thresholds (80/75/80/80 baseline; 100% in entities +
   use-cases + controllers).

3. **Factories per feature** in `src/__factories__/` replace inline
   fixtures. Stable date defaults (2026-01-01) so snapshot diffs reflect
   SUT behavior only.

4. **Contract suites per repository interface** in `src/__contracts__/`
   run against every implementation (Mock + Payload). Eliminates the
   class of bug where the mock and the real impl drift apart.

5. **Tests in core-\* packages and apps** — composition smoke tests
   (appRouter, payloadConfig, bind-production, providers).

6. **Storybook test-runner** — every story executed as a smoke test.

7. **CI workflow** — typecheck + lint + boundaries + test + build +
   e2e + storybook on every PR. Coverage uploaded as artifact.

8. **Two new docs** — tdd-workflow.md (process) + restructured
   adding-a-feature.md (interleaves tests with impl).

## Alternatives considered

- **Fixture files instead of factories** — rejected. Fixtures rot with
  schema changes and require manual updates per test.
- **One shared test file per impl** — rejected. Contract suites give
  the same coverage in fewer LOC and prevent drift.
- **Real Postgres in tests via testcontainers** — rejected for unit
  tests (slow, complex). Repository contract suites + vi.mock('payload')
  give equivalent confidence in milliseconds.
- **Stryker mutation testing** — deferred. Coverage thresholds + contract
  suites get us most of the way; mutation testing is incremental.

## Consequences

- New package to maintain (small, mostly stable surface).
- Coverage thresholds may fail builds initially; we add tests to cross
  threshold as features land.
- Sequence shuffle may surface latent flakes; we fix as found.
- Templates for new features now require writing tests first; this is
  by design.

## Refines

- ADR-006 (boundary tags) — adds @repo/core-testing as a tooling package.
