Ghasi e-Prescribing Gateway Service — Testing Strategy
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · 03 platform-services · 02 DDD
Coverage Targets
| Layer | Minimum coverage |
|---|---|
| Unit (domain + use cases) | 90% |
| Integration | 85% |
| Contract (Pact + golden FHIR) | All API endpoints and all events |
| E2E | Prescribe → dispense → notify three-party correlation (AC-RX-006) |
Unit Tests (test/unit/)
- Persona enforcement logic — ehr-backend cannot write MD; pharmacy-backend cannot write MR
- FHIR resource mapper — MR/MD domain entity ↔ FHIR R4
- Idempotency key fingerprint computation
- ETag computation (sha256 of canonical resource)
- Pharmacy routing resolver — preferred, fallback, region logic
- Outbox event construction per event type
- Subscription criteria matching
- FHIR validation Zod schemas (Phase 1)
Integration Tests (test/integration/)
Mandatory CI gates:
| Test file | What it proves |
|---|---|
tenant-isolation.integration.spec.ts | Tenant A cannot read or write Tenant B's MR/MD; zero cross-tenant leakage |
outbox.integration.spec.ts | Domain events written to outbox in same DB transaction; relay publishes to NATS |
inbox.integration.spec.ts | Consumed events deduplicated; no side effects on duplicate delivery |
Additional:
| Test file | Scenario |
|---|---|
create-medication-request.integration.spec.ts | Full create → DB → outbox → event published; 201 + ETag |
idempotency.integration.spec.ts | Same Idempotency-Key + same payload → same resource (no duplicate); different payload → 409 |
etag-conflict.integration.spec.ts | Stale If-Match → 412; fresh ETag → 200 |
persona-enforcement.integration.spec.ts | EHR writes MR = 201; EHR writes MD = 403; Pharmacy writes MR = 403; Pharmacy writes MD = 201 |
subscription-notification.integration.spec.ts | Subscription fires after MR/MD creation within SLO |
dlq-replay.integration.spec.ts | Failed delivery → DLQ; replay delivers to endpoint |
ig-validation.integration.spec.ts | Valid FHIR MR → 201; invalid (missing required field) → 422 OperationOutcome |
licensing-gate.integration.spec.ts | Unlicensed tenant → 403 MODULE_NOT_LICENSED |
Contract Tests (test/contract/)
medication-request-api.pact.spec.ts— Pact contracts for orders-service (consumer) ↔ gatewaymedication-dispense-api.pact.spec.ts— Pact contracts for pharmacy-service (consumer) ↔ gatewaymedication-request.fhir.schema.spec.ts— Golden FHIR MR Bundle validated against R4 StructureDefinitionmedication-dispense.fhir.schema.spec.ts— Golden FHIR MD Bundle validated against R4eprescribing-events.schema.spec.ts— All produced events validated against schema registry
Golden fixtures stored in test/fixtures/fhir/:
medication-request-valid.json— valid AFG/UAE profile MRmedication-request-invalid-missing-requester.json— expected OperationOutcomemedication-dispense-valid.jsonthree-party-bundle.json— full prescribe → dispense → notify scenario
E2E Tests (test/e2e/)
| Scenario | File | Notes |
|---|---|---|
| Three-party: prescribe → dispense → notify (AC-RX-001..006) | three-party-correlation.e2e.spec.ts | EHR creates MR; pharmacy gets notification; pharmacy creates MD; EHR gets notification; audit trail verified |
| Idempotent create on network retry | idempotent-retry.e2e.spec.ts | Retry with same Idempotency-Key returns same resource |
| ETag conflict recovery | etag-recovery.e2e.spec.ts | Client recovers from 412 correctly |
| Subscription replay from DLQ | dlq-replay.e2e.spec.ts | Endpoint down → DLQ → manual replay → delivery |
Performance Tests
- Load test: 100 concurrent MR creates; verify p95 < 800 ms (NFR-RX-001).
- Subscription fan-out: 1 MR; 10 registered subscriptions; verify all notified within SLO (NFR-RX-002).
Test Infrastructure
- Vitest for unit and integration
- Testcontainers (Postgres, NATS)
- Pact broker for consumer-driven contracts
- WireMock / MSW for mocking fhir-gateway and provider-directory-service in integration tests
- FHIR golden fixtures in
test/fixtures/fhir/— CI gate: all valid fixtures must pass R4 validator (NFR-RX-004)