Skip to main content

iam-service — Testing Strategy

Catalog · DOMAIN_MODEL · APPLICATION_LOGIC · SECURITY_MODEL

iam-service holds the keys to the kingdom. Test rigour exceeds the platform baseline: ≥ 90 % line coverage on domain layer, ≥ 85 % on application layer, ≥ 80 % overall. Every security-impacting behavior has at least one negative test.

1. Test Pyramid

┌──────────────┐
│ Chaos / DR │ ← weekly
└──────────────┘
┌──────────────┐
│ Load / Perf │ ← per release + nightly
└──────────────┘
┌──────────────┐
│ Security │ ← per release + on every auth change
└──────────────┘
┌──────────────────┐
│ E2E │ ← per PR (smoke), nightly (full)
└──────────────────┘
┌──────────────────────┐
│ Pact / Contract │ ← per PR
└──────────────────────┘
┌──────────────────────────┐
│ Integration (Postgres, │ ← per PR
│ Redis, Pub/Sub, KMS) │
└──────────────────────────┘
┌──────────────────────────────────┐
│ Unit │ ← per PR
└──────────────────────────────────┘

2. Coverage Targets

LayerLineBranchMutation
domain/95 %90 %≥ 70 %
application/90 %85 %≥ 60 %
infrastructure/80 %70 %n/a
presentation/80 %70 %n/a
Overall≥ 80 %≥ 75 %n/a

CI gate: failing → block merge. Coverage report uploaded to Codecov.

3. Tooling

LayerStack
UnitVitest + @testing-library where DOM applies; pure functions elsewhere
Property-basedfast-check
IntegrationVitest + Testcontainers (Postgres, Redis, fake-pubsub, fake-kms)
ContractPact 4 (consumer + provider)
E2EPlaywright (against full local stack via docker compose -f docker-compose.e2e.yml)
SecurityOWASP ZAP, Semgrep, Trivy/Snyk; custom scripts
Loadk6
ChaosToxiproxy + custom failure injection
MutationStryker (domain only)

4. Unit Suites (key)

4.1 User aggregate

TestAsserts
requestEmailVerification sets token + statusinvariant
Cannot login if status != activeinvariant
Lock idempotentrepeated calls don't multiply events
Unlock requires actorIdguard
GDPR erasure anonymizes email + emits eventinvariant

4.2 Credential

TestAsserts
Verify with correct password → success, no rehash if version currentbehavior
Verify with stale hash_version → success + rehash eventrehash on login
Verify wrong password → failure, no leaktiming test (next section)
change() rejects passwords in last 5 history entriespolicy

4.3 Session family

TestAsserts
Refresh rotates currentTokenHash, appends to previousTokenHashesinvariant
Re-presenting a previousTokenHashes member → entire family revokedreuse detection
Bound session cannot refresh on different devicebinding
Revoke is idempotentinvariant

4.4 Device

TestAsserts
Bind requires fresh-auth ACRprecondition
Bind generates Ed25519 cert serial unique per deviceinvariant
Revoke cascades to sessions bound to deviceside-effect
Renewal preserves deviceId and binding historyinvariant

4.5 MFAFactor

TestAsserts
TOTP enroll requires verification before activeinvariant
WebAuthn sign_count regression marks credential clonedsecurity
Recovery codes are single-useinvariant
Removing last factor when MFA mandatory → policy violationguard

4.6 APIKey

TestAsserts
Issuance returns plaintext only onceinvariant
Validation against expired key failsTTL
Validation against revoked key fails (via denylist after replication)revoke
Scope check enforcedRBAC

4.7 Domain Services

  • PasswordPolicyService: strength rules; breach-list k-anonymity request shape; history check.
  • AdaptiveMFAService: AI raise-only invariant; fallback path; tenant override (require always).
  • TokenSigner (port test against fake KMS): signs valid Ed25519, rejects on bad kid.

5. Integration Suites (Testcontainers)

Spawns: Postgres 15, Redis 7, fake-pubsub, fake-kms, mock-idp (OIDC + SAML), mailhog.

SuiteCoverage
user.repo.spec.tsCRUD + RLS via session vars
credential.repo.spec.tsHistory append + truncate to 5
session.repo.spec.tsRotation, reuse-detection, family revoke
device.repo.spec.tsCert serial uniqueness, revoke cascade
outbox.spec.tsAtomic write with domain row in same tx; relay drains; DLQ on persistent failure
inbox.spec.tsIdempotent consume; out-of-order safe (latest-wins where stated)
oidc.callback.spec.tsFull flow against mock-idp; nonce/state validation; PKCE
saml.assertion.spec.tsAssertion signature; XSW negative; clock-skew tolerance
magic-link.spec.tsTTL, single-use, replay-blocked
device.bind-offline.spec.tsCSR → cert; tenant CA chain validation
kms.signing.spec.tsSign + verify; rotation overlap; emergency rotation
gdpr.erasure.spec.tsSaga participation; idempotent; audit retained

6. Contract Tests (Pact)

6.1 Consumer-side (this service consumes)

ProviderContract
tenant-servicetenant.created.v1, tenant.deleted.v1, tenant.guest.erasure_requested.v1 event shapes
notification-servicenotification.dispatch (used to send magic-link / reset emails)
ai-orchestrator-serviceai.classify.login_risk request/response

6.2 Provider-side (this service provides)

ConsumerContract
tenant-serviceiam.user.registered.v1, iam.user.locked.v1, iam.user.erased.v1
audit-serviceAll audit events listed in SECURITY_MODEL §12
every service/.well-known/jwks.json shape; JWT claim shape
gdpr-serviceiam.user.erased.v1

Pact broker: https://pact.melmastoon.cloud. Provider verification job runs on every iam PR.

7. End-to-End (Playwright)

E2E stack: full docker compose (api gateway + iam + tenant + notification + mock-idp + mailhog + postgres + redis + fake-pubsub).

7.1 Smoke (every PR; ≤ 5 min)

  • Register → verify email → login → fetch profile → logout
  • Login + MFA TOTP enroll + challenge
  • Refresh token round-trip
  • Magic link request → consume

7.2 Full (nightly; ≤ 30 min)

  • SSO OIDC end-to-end with mock-idp
  • SSO SAML end-to-end with mock-idp
  • WebAuthn enroll + login (using @simplewebauthn virtual authenticator)
  • Device register → trust → bind-for-offline → renew
  • Offline mode: cut network, refresh succeeds with offline cert; reconnect; sync resumes
  • Password reset request → email link → reset → old refresh tokens invalidated
  • Account lock by admin → user logged out → unlock by admin
  • API key issue → use → rotate → revoke
  • GDPR erasure: tenant emits guest erasure → user disappears within SLA
  • Tenant deleted → all sessions revoked

8. Security Tests

8.1 Automated per release

TestExpectation
OWASP ZAP baselineno high findings
Semgrep (security ruleset)clean
Trivy / Snyk SCAno critical CVEs
Secret scan (gitleaks)clean
Dependency confusion checkclean
npm audit --omit=devclean (high/critical)

8.2 Custom security scenarios

ScenarioMechanism
Credential stuffingReplay 10 000 known-bad creds; assert lockout + WAF rate limit
Refresh-token theftSteal refresh, attacker uses, then victim presents → both blocked + family revoked + alert
SSO callback tamperingModify state, nonce, signature → all rejected
SAML XSWInject wrapped assertion → rejected
MFA bypass attemptSubmit mfa_token for different user → rejected
Magic-link replayConsume twice → second rejected
Account enumeration via timingStatistical test: register/login/reset response time difference for known vs unknown email is < 5 ms
API key reuse after revokeUse within Redis denylist TTL → blocked
JIT SSO substitutionIdP returns claim for different tenant → rejected
Device fingerprint spoofSubmit hand-crafted fingerprint → bind requires fresh-auth + Ed25519 → spoofed bind cannot login another session
JWKS poisoningPublish unauthorized key → consumer rejects (signature verify)
JWT replay after revokeToken in iam:revoked:jti:* → API gateway rejects
Rate limit bypass attemptsHeader injection / IP spoofing → blocked at WAF

8.3 Cryptographic tests

  • Argon2id parameter compliance (m, t, p match config)
  • Ed25519 sign / verify round-trip with KMS
  • HKDF-derived bundle key matches client implementation (cross-language KAT)
  • TOTP RFC 6238 compliance KAT vectors

9. Load Tests (k6)

ScenarioTarget
Steady login500 RPS sustained for 30 min, p95 < 200 ms, p99 < 800 ms
Refresh burst2 000 RPS for 10 min, p95 < 100 ms
JWKS read10 000 RPS for 10 min, p95 < 20 ms (CDN-backed)
Cold startfirst 60 s after deploy: error rate < 0.5 %
MFA challenge200 RPS, p95 < 200 ms
Worst-case attack5 000 RPS of failed logins → graceful degradation, lockouts triggered, no 5xx

Load profile recorded as baseline; regression > 20 % blocks release.

10. Chaos Engineering

Run weekly in staging via Toxiproxy.

ScenarioExpected
KMS adds 2-s latencyLogin p99 elevated but < 2 500 ms; no cascading failure
Postgres primary failover< 30 s unavailability; auto-recover
Redis cluster partitionLogin still works (DB fallback for rate-limit); MFA throttles fallback to in-memory
Pub/Sub publish errors 50 %Outbox retries; no data loss
Notification-service downMagic-link / reset return 202; queued; eventually delivered
Mock-IdP timeoutOIDC callback returns 504 problem; user can fall back to password
AI orchestrator downAdaptive MFA falls back to rules; counter iam_ai_fallback_total increments

11. Negative-Path Coverage Catalog

Maintained in test/negative-paths.csv; CI asserts every row has ≥ 1 covering test.

CodePath
MELMASTOON.IAM.AUTH.INVALID_CREDENTIALSlogin wrong password
…AUTH.MFA_REQUIREDlogin when MFA enrolled
…AUTH.MFA_INVALIDwrong TOTP
…AUTH.ACCOUNT_LOCKEDlocked user login
…AUTH.RATE_LIMITEDexceed throttle
…AUTH.PASSWORD_BREACHEDreset with breached password
…AUTH.MAGIC_LINK_EXPIREDTTL exceeded
…AUTH.MAGIC_LINK_USEDreplay
…SSO.STATE_MISMATCHtampered state
…SSO.NONCE_REPLAYreused nonce
…SSO.ASSERTION_INVALIDSAML signature broken
…DEVICE.BIND_REQUIRES_FRESH_AUTHstale ACR
…DEVICE.OFFLINE_LIMIT_EXCEEDEDlast-seen > 7 d
…SESSION.ROTATION_REUSEtoken theft
…APIKEY.SCOPE_INVALIDwrong scope
…APIKEY.REVOKEDpost-revoke use

12. Test Data

  • Faker-generated tenants, users, devices in test/fixtures/.
  • Deterministic seed (MELMASTOON_TEST_SEED=42) → reproducible runs.
  • No production data ever copied (synthetic-only policy).
  • Snapshot tests for OpenAPI + AsyncAPI; drift fails CI.

13. Definition of Done — Test Aspect

Per SERVICE_READINESS:

  • All new/changed paths covered by unit + integration tests.
  • Negative-path catalog entry exists for every new error code.
  • Pact contract update merged in pact-broker if shape changes.
  • E2E smoke updated if user-facing flow changes.
  • Coverage thresholds green.
  • ZAP baseline + Semgrep clean.
  • Load baseline within 20 % of previous release.