Skip to Content
WhatsApp MCPTestingTest Strategy

Test strategy

Source of truth: ../plan/standards.md §1. This file expands the strategy and is the doc to extend as the test surface grows.

Layers

LayerToolingScope
UnitVitestPure utilities, validators, schema parsers, scope checkers, rate-limit math, signed-URL helpers, phone normalisers, Meta error mappers
IntegrationVitest + testcontainers  PostgresTool handlers, Inngest functions, webhook routes, auth middleware. Real ephemeral Postgres, not DB mocks
End-to-end (selective)Vitest + supertest against a running appCritical flows: webhook → message persisted → notification routed; send → mocked Meta API → row written
Cross-tenant isolationVitest + testcontainersTwo clients with disjoint grants; assert client B cannot read/write client A’s rows via any tool, scope, or grant path
Webhook signatureVitestValid signature passes; tampered body → 404; missing header → 404; replay (same wamid) → idempotent no-op

Coverage gates

Enforced in vitest.config.ts.

Directory / fileMinimum
src/ (overall)80%
src/auth/95%
src/webhook/verify-signature.ts100%
src/db/scoped.ts95%
src/audit/95%
src/media/signed-url.ts100%

A PR that drops coverage below threshold fails CI.

What we never mock

  • Postgres. Use testcontainers. Mocks of DBs have a long history of letting integration bugs reach production.
  • HMAC / crypto primitives. Test the real crypto.timingSafeEqual and friends; the wrappers around them are the security boundary.
  • Meta error code → typed error mapping. Real strings, real integers, real shapes.

What we always mock

  • Meta Cloud API HTTP boundary. msw interceptor at the fetch layer. The wire calls are slow, rate-limited, and have real-world side effects.
  • Filesystem writes outside MEDIA_ROOT. memfs for anything that touches arbitrary host paths.
  • Time for sliding-window tests. vi.useFakeTimers().

When to write what

ChangeRequired test
New functionAt least one unit test before the function ships
New toolIntegration test against testcontainers Postgres: auth, scope, grant, rate-limit interaction, happy path
New Inngest functionIntegration test asserting idempotency and retry semantics
Bug fixA failing test that reproduces the bug first, then the fix that turns it green

Running tests

See running-tests-locally.md.

CommandWhat it runs
pnpm testunit tests only — fast, no Docker
pnpm test:integrationintegration tests; testcontainers Postgres
pnpm test:cieverything + coverage
pnpm test:watchunit tests, watch mode

CI

GitHub Actions runs pnpm test:ci on every PR. The coverage gate is reported per-PR and blocks merge if any threshold drops. docs/reference/ is gitignored generated output — no CI regen gate; a future docs project will own it.