Billing Service — Testing Strategy
Status: populated Owner: TBD Last updated: 2026-04-17 Companion: Service Template · Testing standards
1. Coverage targets
| Layer | Statement / branch | Notes |
|---|---|---|
| Domain | ≥ 95 % | Pure logic — no excuses |
| Application | ≥ 90 % | Includes sagas |
| Infrastructure adapters | ≥ 80 % | |
| Presentation (controllers) | ≥ 80 % | Plus Pact |
| Overall service | ≥ 85 % | CI gate |
2. Test types & mandatory suites
| Suite | Location | Mandatory? |
|---|---|---|
| Unit — domain | test/unit/domain/*.spec.ts | yes |
| Unit — application | test/unit/application/*.spec.ts | yes |
| Integration — repository | test/integration/repositories/*.integration.spec.ts | yes |
| Integration — tenant isolation | test/integration/tenant-isolation.integration.spec.ts | mandatory |
| Integration — outbox | test/integration/outbox.integration.spec.ts | mandatory |
| Integration — inbox | test/integration/inbox.integration.spec.ts | mandatory |
| Integration — ledger immutability | test/integration/ledger-immutability.integration.spec.ts | mandatory |
| Integration — idempotency | test/integration/idempotency.integration.spec.ts | mandatory |
| Contract — OpenAPI Pact | test/contract/*.pact.spec.ts | yes (claims-service, portal BFF consumers) |
| Contract — event schema | test/contract/*.schema.spec.ts | yes |
| E2E — charge → invoice → pay → statement | test/e2e/revenue-cycle.e2e.spec.ts | yes |
| Property-based — ledger math | test/unit/domain/ledger.property.spec.ts | yes |
3. Critical test scenarios
3.1 Ledger integrity
- Append-only: any attempt to
UPDATEorDELETEledger_entriesraises. - Balance invariant: for 10 000 random charge/payment sequences,
sum(ledger.amount) == account.balance. - Reversal: reverse charge creates a matching negative entry; original remains.
3.2 Idempotency
- Replay same
Idempotency-Key+ same body → 200/201 with original response. - Same key + different body → 409
IDEMPOTENCY_CONFLICT. - TTL expiry: after
expires_at, same key is usable again.
3.3 Tenant isolation
set_config('app.tenant_id', 'ten_A', true)then attempt toSELECTrows forten_B→ empty.- API JWT for tenant A attempting to reference invoice
inv_…of tenant B → 403CROSS_TENANT_REFERENCE.
3.4 Refund approval
- Request above threshold without
billing:refund:approvescope →pending_approval+ 403 on/approvefrom unprivileged user. - Approval + post → ledger entry +
billing.refund.issued.v1.
3.5 Currency safety
- Payment currency ≠ account currency →
MONEY_CURRENCY_MISMATCH. - Arithmetic with
minor_unitsnever round-trips through float.
3.6 Price resolution
- No active price row for
(facility, code, date)→PRICE_NOT_FOUND. - Effective-dating: service date inside price window is selected; outside → next fallback.
3.7 Consumer flows
registration.encounter.discharged.v1drives charge capture; dedup on CloudEventsid.claims.remittance.posted.v1posts PAYER_REMITTANCE payment + CONTRACTUAL adjustment.
3.8 Statement run
- Job partitioned by facility; failure in one account doesn't fail the run.
- Artifact stored at tenant-prefixed object key; link expires per signed URL TTL.
4. Fixtures & builders
__builders__/moneyBuilder.ts,accountBuilder.ts,chargeBuilder.ts, etc.- Seed a baseline tenant with facility + price list + tax rule in
test/integration/_setup/seed.ts.
5. Containers
- Testcontainers-node: Postgres 16, NATS JetStream, MinIO (for statement PDFs).
- Running tests requires docker-compose test stack up (
pnpm test:integration:up).
6. CI gates
- Unit + integration green.
- Coverage ≥ 85 %.
- OpenAPI Pact: no breaking change vs consumer contracts.
- Event schema conformance vs schema registry.
npm audit --productionno HIGH/CRITICAL.- Trivy image scan no HIGH/CRITICAL.
- ESLint domain import-restriction pass.
7. Performance tests
- k6 load profile: 50 rps
POST /charges, 20 rpsPOST /paymentssustained 10 min — target p95 latency SLOs. - Run in staging pre-release.
8. Chaos tests
- NATS partition: outbox drains after connection restored; no duplicates at consumers.
- DB failover: in-flight transactions roll back; outbox still consistent.
- Clock skew on node: idempotency TTL remains correct via UTC.