All documents

MERIDIAN · DOC 23 / 25

Testing

Layered suite (unit/integration/e2e), production-safety guard, coverage matrix, release gate.

The reliability contract. 156 checks, three layers, always green before every commit.


1 · Stack & layers

LayerToolLocationPurpose
UnitVitesttests/unit/**pure logic: passwords/hashing, tracking URLs/WhatsApp URLs, host parsing, rate limiter, origin allow-list, validation hardness, index inventory, status enum drift
IntegrationVitest + mongodb-memory-servertests/integration/**services against a REAL in-process MongoDB (replica set for transaction flows): auth, tenant provisioning, packages, isolation matrix, public tracking, website config, realtime emission, health/readiness
Service-level E2EVitest + mongodb-memory-servertests/e2e/**the full business path through the same services the HTTP layer calls (provision → login → package → public track → mutation → archive/restore)

No extra frameworks: the Vitest setup matched the Next/React stack and already existed — HTTP-driver/browser E2E (e.g. Playwright) is a possible later addition, not a V1 requirement (docs/production-checklist.md §B is the live smoke pass).

2 · Commands

npm test               # entire suite (all layers)
npm run test:unit      # pure/no-db units
npm run test:integration  # MongoDB-backed services
npm run test:e2e       # service-level business flow
npm run quality        # lint + typecheck + all tests + production build

3 · Test environment & production safety

4 · Coverage map (security matrix)

AreaGuarded byWhere
Auth: valid loginlogin succeeds, safe DTO onlyintegration/auth.service
Auth: wrong password / unknown emailidentical generic 401 (no enumeration)auth.service
Auth: inactive user, suspended/archived tenant loginblocked with same generic messageauth.service
Session: expiry, logout, password changeinvalidation semantics incl. keep-current-onlyauth.service
AuthZ: role gates401 anonymous, 403 wrong role, both directionsauthorization
Tenancy: AuthContext is source of truth403 when binding missing; context is authoritativeauthorization
Create: one admin, config, slug rules, rollbacktransactional provisioning + compensating deletetenant.service
Lifecycle: ACTIVE⇄SUSPENDED, ARCHIVED→ACTIVEtransitions + invalid rejections + session killstenant.service
Reset: temp password generationold sessions/old password die; new password workstenant.service
Packages: five statuses onlystatus schema + enum drift alarmunit/validation-hardness
Status flow incl. repeated IN_TRANSIT + overrideevents preserved, chronologicalpackage.service
Transactions: create/status/locationforced failure → full rollbackpackage.service
Archive/restorehidden by default, intact history, same trackingIdpackage.service
Lat/lng ranges, notes, free-text payment methodmodel + boundarypackage.service + unit
Tenant isolation (THE matrix)cross-tenant read/update/archive/subscribe → same 404; direct history reads see 0 rows; smuggled tenantId ignoredintegration/isolation-matrix
Public: allowlistphones/emails/addresses/costs/ids/tenantId all absentpublic-tracking
Public: archived ≡ unknownidentical messagepublic-tracking
Public: suspended/archived tenanttracking/website offpublic-tracking
Website config: validation batterymarkup, bad hex/URL/icons, unknown sections, smuggled keyswebsite-config
Realtime: subscription guardmongo-ID ≠ access, statuses, archived, cross-tenantrealtime
Realtime: DB-firstzero emits on failed write; payloads scrubbedrealtime
Geocoding: normalization/failuresprovider abstractionunit/geocoding
Sharing: URLs + wa.meenv-aware links, encoding, privacyunit/sharing
Rate limiting/templatebuckets, independence, window rollunit/security-middleware
Origin allow-listplatform/subdomains/dev-localhost/foreignunit/security-middleware
Indexeslocked inventory per collectionunit/index-inventory
Validation batterynegative weight/cost, bad emails, oversized, smuggled & unknown fieldsunit/validation-hardness
Health/readinessup/down reaches truth, never throws, no leaksintegration/health
Full business pathprovision → login → package → track → mutate → archive → suspend → restoree2e/business-flow

5 · HTTP hardening in place (§26–§33 of the phase scope)

ControlValueWhy
Global rate limit120 req/IP/min every API responseblanket abuse floor
Login10/IP/10 minbrute-force
Public tracking30/IP/mincustomers refresh; scrapers don't
Geocoding20/IP/minprovider quota respect
Origin allow-listany non-GET from unknown Origin → 403CSRF baseline
JSON body ceiling1 MB on mutating endpoints (+2 m at Nginx)oversized POST abuse
Security headersnosniff · DENY framing · referrer/permissions policy · CSP for API · no-store · HSTS in productionpassive hardening, zero app impact measured
Error contractstructured envelope only, internals loggedno stack/DB leakage

6 · Frontend states & UX verification (manual + structural)

The UIs maintain explicit states everywhere: loading/empty/error/success, confirm-pattern dangerous actions, archived drawer separation, connection chip (Live/Reconnecting/Updated), graceful map-failure panels, archival and suspension surfaces. Responsive/a11y smoke passes are part of docs/production-checklist.md.

7 · Common failures & fixes

OutputCauseAction
Test suite refuses to run with NODE_ENV=productionprod flag leaked into the shellremove the flag; tests are dev/CI only
binary download of mongod fails first runoffline envre-run with network (first-run fetch, cached after)
argon2/vitest binding error on Alpine/muslplatform optional bindingnpm i -D @rolldown/binding-wasm32-wasi (documented in repo)
"indexes ensured" missing at runtimedev boot never connectedrun npx tsx scripts/ensure-indexes.ts once
Flaky replica-set startheavy CI hosthookTimeout is 120 s; shard larger suites

8 · Release gate (non-negotiable)

npm run quality must be green before any deploy; Phase-10 rollout procedures depend on it. Anything that fails the suite fails the phase — tests describe the architecture, they are not suggestions.