Config Service — Testing Strategy
Status: populated Owner: TBD Last updated: 2026-04-18 Companion: Service Template · TESTING_STANDARDS.md
1. Coverage Targets
| Test type | Coverage target | Tool |
|---|---|---|
| Unit tests | ≥ 80 % statement + branch | Vitest |
| Integration tests | All use cases covered | Vitest + Testcontainers (PostgreSQL + Redis) |
| Contract tests (consumer) | All internal API endpoints | Pact |
| Contract tests (event schema) | All 13 produced event types | JSON Schema |
| E2E tests | Critical resolution paths | Playwright / SuperTest against running service |
2. Mandatory Test Scenarios
2.1 Tenant Isolation (test/integration/tenant-isolation.spec.ts)
| Scenario | Expected |
|---|---|
| Resolve for user in Tenant A returns Tenant A config | ✓ allow, Tenant A data |
| Resolve for user in Tenant A with Tenant B nodeId | { effect: "deny", reason: "CROSS_TENANT" } |
| GET /internal/config/tokens with nodeId belonging to Tenant B | 403 CROSS_TENANT |
| TENANT_ADMIN from Tenant A cannot create role in Tenant B | 403 Forbidden |
RLS policy prevents SELECT of Tenant B config_nodes when app.current_tenant_id = Tenant A | 0 rows returned |
2.2 Outbox (test/integration/outbox.spec.ts)
| Scenario | Expected |
|---|---|
| Feature definition created → event in outbox before tx commit | Event present in outbox table |
Outbox relay publishes to NATS → marks delivered_at | delivered_at not null |
| Crash between tx commit and NATS publish → relay replays | Event delivered exactly once (dedup by event ID) |
2.3 Inbox (test/integration/inbox.spec.ts)
| Scenario | Expected |
|---|---|
| Same event delivered twice (NATS redelivery) | Second delivery acknowledged, not processed |
| inbox table prevents duplicate cache eviction | Cache evicted exactly once |
3. Unit Test Scenarios
| Module | Key scenarios |
|---|---|
ResolveConfigUseCase | Step-by-step: module deny, feature deny, FORBIDDEN, ABAC deny, ExplicitAllow, ExplicitDeny |
RoleBFSExpander | BFS stops at depth 10; cycles skipped; empty role set |
DesignTokenMerger | LWW priority: User > Module > OrgNode > Tenant > Global; locale override |
UIVisibilityResolver | Role rule applied; user rule overrides role rule; ExplicitAllow cascades to bound elements |
UserNodeOverrideChecker | Expired override silently ignored; active override wins; conflict detection |
ConfigNodeCycleDetector | Detects cycle in parent chain; depth-limited traversal |
CacheKeyBuilder | Key format correctness per namespace convention |
4. Contract Tests
Pact Consumer Contracts
| Provider | Contract | Key interactions |
|---|---|---|
facility-service | GET /internal/hierarchy/nodes/{id}/ancestors | Returns ancestor chain for valid nodeId; 403 for cross-tenant |
platform-admin-service | GET /internal/licenses/check | Returns { active: true/false } |
platform-admin-service | GET /internal/platform/features/{key} | Returns { enabled: true/false } |
access-policy | POST /internal/access/evaluate | Returns { effect: "allow|deny", reason, policyId } |
Event Schema Tests
All 13 event subjects validated against registered JSON schema on publish.
5. Performance Tests
| Scenario | Target |
|---|---|
| Resolution with cold cache (no Redis hit) | p95 < 300 ms |
| Resolution with warm cache | p95 < 20 ms |
| Concurrent resolutions: 100 rps | No degradation, p99 < 500 ms |
| BFS expansion with 8-level role tree | < 10 ms |
6. Security Tests
| Test | Expected |
|---|---|
| Resolution request with Tenant B nodeId | CROSS_TENANT deny |
POST override with missing justification | 422 Unprocessable Entity |
POST role with isSystem=true by TENANT_ADMIN | 403 Forbidden |
| BFS with cycle (cycle injected in test DB) | Cycle skipped; terminates cleanly |
RLS bypass attempt via SQL injection in featureKey | Parameterized query; no bypass |