Patient Portal Service — Testing Strategy
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · docs/standards/TESTING_STANDARDS.md · 02 DDD
1. Coverage Targets
| Layer | Target | Tool |
|---|---|---|
| Unit (domain + application) | ≥ 85% | Vitest |
| Integration (adapters + DB) | ≥ 80% | Vitest + testcontainers |
| Contract (BFF API) | 100% of public endpoints | Pact (consumer-driven) |
| E2E (patient flows) | Critical paths covered | Playwright |
2. Test Layers
2.1 Unit Tests
Scope: Domain logic, use-case handlers, policy filters, proxy scope checks.
| Test | What it validates |
|---|---|
PortalAccount state machine | register → verify → active → suspend → reinstate → close transitions |
ProxyDelegation scope enforcement | Proxy request with scope not in delegation returns 403 |
ProxyDelegation validity window | Expired validTo blocks access |
ResultReleasePolicy filter | Unreleased observations excluded from patient response |
MFA ACR check | Actions requiring acr >= 2 rejected when claim insufficient |
DemographicsUpdateRequest | Submit command creates entity with pending status |
ExportJob idempotency | Second export request for same patient returns existing in-progress job |
2.2 Integration Tests
Scope: Repository adapters, outbox relay, cache write-through, and service-to-service HTTP adapters.
| Test | What it validates |
|---|---|
tenant-isolation.spec.ts (mandatory) | Portal account for tenant A is not visible from tenant B context |
outbox.spec.ts (mandatory) | portal.account.created.v1 event published to NATS after account creation |
inbox.spec.ts (mandatory) | IDENTITY.patient.registered.v1 consumed and portal account auto-created |
portal-account.repository.spec.ts | CRUD against real PostgreSQL via testcontainers |
proxy-delegation.repository.spec.ts | Delegation queries scoped to correct tenant |
access-event.repository.spec.ts | Append-only: no update/delete operations succeed |
cache-adapter.spec.ts | Cache hit returns upstream response; miss triggers HTTP call |
circuit-breaker.spec.ts | Upstream failure opens circuit; degraded response returned |
2.3 Contract Tests (Pact)
The patient-portal-service is the provider of its own REST surface. Consumers (web app, mobile app) generate Pact consumer contracts that are verified on every PR.
| Contract | Provider state | Verified |
|---|---|---|
GET /v1/portal/me | Account active, MFA enabled | Yes |
GET /v1/portal/results/lab | Patient has 2 released observations | Yes |
POST /v1/portal/appointments/request | Scheduling-service available | Yes |
POST /v1/portal/proxy/delegations | No existing delegation | Yes |
POST /v1/portal/export | Export job not in progress | Yes |
2.4 E2E Tests (Playwright)
Critical patient journeys tested against a locally-deployed stack (Docker Compose):
| Flow | Steps |
|---|---|
| Patient login with MFA | Register → verify email → activate MFA → login → see dashboard |
| View lab results | Login → navigate to Results → lab section loads released observations |
| Request appointment | Login → Appointments → Request → submits to scheduling-service stub |
| Grant + use proxy access | Patient grants proxy → proxy logs in → views scoped data → revoke |
| PHR export | Request export → poll status → download FHIR bundle |
3. Security-Specific Tests
| Test | Validates |
|---|---|
| Unauthenticated request → 401 | All /v1/portal/* endpoints reject missing JWT |
| Wrong tenant JWT → 403 | tenant_id mismatch returns 403 FORBIDDEN |
| Expired JWT → 401 | Expired token rejected |
| Missing SMART scope → 403 | INSUFFICIENT_SCOPE returned |
| Proxy exceeds scope → 403 | PROXY_SCOPE_EXCEEDED returned |
| Unreleased result excluded | BFF never returns patient-not-visible observations |
| PHI absent from AI prompt | Prompt builder unit test asserts no PHI fields included |
4. Test Data
Seed data script: test/seed/portal-seed.ts
Provides:
- 2 tenants:
tenant_a,tenant_b - 1 active portal account per tenant with linked patient
- 1 proxy delegation for
tenant_a(parent → minor) - 3 released lab observations for
tenant_apatient - 1 upcoming appointment for
tenant_apatient
5. CI Integration
| Step | Trigger | Command |
|---|---|---|
| Unit tests | Every PR | pnpm test:unit |
| Integration tests | Every PR | pnpm test:integration (requires Docker) |
| Contract tests | Every PR | pnpm test:contract |
| E2E tests | Merge to main | pnpm test:e2e |
| Coverage report | Every PR | pnpm test:coverage (fails if < 80%) |