Skip to main content

Patient Chart Service — Testing Strategy

Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · TESTING_STANDARDS

1. Coverage targets

LayerTargetTool
Domain (aggregates, invariants, value objects)≥ 90 %Vitest
Application (use cases, ports)≥ 85 %Vitest
Infrastructure (adapters)≥ 70 %Vitest + TestContainers
Integration (full stack)≥ 70 %Vitest + TestContainers
Contract100 % of published events + consumed eventsPact + schema registry
E2E (critical flows)Mandatory flows onlyPlaywright / Supertest

2. Test types and mandatory tests

2.1 Unit tests (test/unit/)

Aggregate / moduleKey scenarios
ProblemAll state transitions; entered-in-error with/without reason; duplicate problem detection
AllergyNKA rule conflict; NKDA conflict; duplicate substance; reaction severity combinations
VitalsSetBMI derivation; range validation (warn/reject policy); LOINC code requirement; immutable correction model
ClinicalNoteDraft→signed transition; cosign-required block; addendum post-sign; AI provenance missing → reject
ChartAccessBreak-glass reason required; sensitive-segment denial
Domain errorsAll error codes in DOMAIN_MODEL.md §10 tested

2.2 Integration tests (test/integration/) — MANDATORY

Test fileWhat it verifiesBlock on failure
tenant-isolation.spec.tsten_dev_a data is NEVER readable by ten_dev_b at REST and DB level; RLS enforcedHard block
outbox.spec.tsEvery domain event written to outbox in same transaction as aggregate; relay publishes to NATSHard block
inbox.spec.tsregistration.patient.merged.v1 and gdpr.subject_request.received.v1 are idempotently handledHard block
allergy-advisory.spec.tsAdvisory returns correct allergy list with severity; NKA returns advisory with isNKA=true; concurrent allergy write handled
vitals-range-validation.spec.tsHard-stop fires when reject policy set; warn policy allows record with flag
note-cosign-routing.spec.tsSignNote blocks without cosign when policy requires; pending_cosign state set
breakglass.spec.tsBreak-glass without reason → 422; with reason → access granted + breakglass.invoked.v1 emitted
ai-provenance.spec.tsAcceptAIChunk without provenanceIdCHART_AI_PROVENANCE_MISSING; with provenance → note.ai_accepted.v1 emitted
fhir-read.spec.tsCondition, AllergyIntolerance, Observation FHIR read surface returns conformant resources

2.3 Contract tests (test/contract/)

Provider contracts (patient-chart-service publishes):

EventSchema conformance test
patient_chart.problem.added.v1problem-added.schema.spec.ts
patient_chart.allergy.added.v1allergy-added.schema.spec.ts
patient_chart.vitals.recorded.v1vitals-recorded.schema.spec.ts
patient_chart.note.signed.v1note-signed.schema.spec.ts
patient_chart.note.ai_accepted.v1note-ai-accepted.schema.spec.ts
patient_chart.breakglass.invoked.v1breakglass-invoked.schema.spec.ts

Consumer contracts (patient-chart-service is consumer):

APIPact consumer test
GET /v1/ai/assist (ai-gateway-service)ai-gateway-provider.pact.spec.ts
GET /v1/terminology/lookup (terminology-service)terminology-provider.pact.spec.ts
GET /v1/patients/:id (registration-service)registration-provider.pact.spec.ts

2.4 E2E tests (test/e2e/)

FlowTest
Add problem → allergy → vitals → create note draft → sign notechart-core-flow.e2e.spec.ts
AI-assist → HITL accept → note signed with provenancechart-ai-assist-flow.e2e.spec.ts
Break-glass → chart access → audit entry in audit-servicebreakglass-audit-flow.e2e.spec.ts
GDPR erasure saga → author PII redacted, clinical content retainedgdpr-erasure.e2e.spec.ts
Allergy advisory sync → medication-service receives advisoryallergy-advisory-consumer.e2e.spec.ts

3. Test data strategy

  • All test data uses synthetic patients with pat_dev_* prefix; no real PHI.
  • __builders__ directory in each aggregate domain folder provides builder functions: ProblemBuilder.active(), AllergyBuilder.nka(), etc.
  • TestContainers provides a real Postgres 16 instance per integration test run (isolated schema per test file).
  • NATS JetStream mock via @nestjs/testing for unit; real NATS via TestContainers for integration.

4. CI pipeline

┌─────────────────────────────────────────────┐
│ 1. lint + typecheck │ ← fail fast
│ 2. unit tests (vitest) │ ← no external deps
│ 3. integration tests (TestContainers) │ ← real Postgres + NATS
│ ├── tenant-isolation.spec.ts (MANDATORY) │
│ ├── outbox.spec.ts (MANDATORY) │
│ └── inbox.spec.ts (MANDATORY) │
│ 4. contract tests (Pact broker) │
│ 5. schema conformance tests │
│ 6. coverage gate (≥ 80 % overall) │
│ 7. e2e tests (staging deploy) │
└─────────────────────────────────────────────┘

5. Quality gates

GateThresholdEnforced by
Overall coverage≥ 80 %Vitest coverage report in CI
Domain coverage≥ 90 %Vitest --coverage with domain path filter
tenant-isolationMust passCI hard block
Pact verificationMust passPact broker CI step
Schema conformanceMust passSchema registry CI step
Build time (unit)< 60 sCI timeout
Build time (integration)< 300 sCI timeout

6. Test naming conventions

// Pattern: describe aggregate + scenario + expectation
describe('Allergy.record', () => {
it('rejects substance allergy when NKA is active', () => { ... })
it('allows medication allergy when only NKDA is active and category is food', () => { ... })
})

describe('ClinicalNote.sign', () => {
it('blocks signing when cosign policy requires and cosigner is not attested', () => { ... })
it('transitions to signed state when no cosign policy required', () => { ... })
})