Skip to main content

TESTING_STRATEGY — bff-consumer-service

Sibling: APPLICATION_LOGIC · API_CONTRACTS · SECURITY_MODEL · FAILURE_MODES

1. Test pyramid

┌──────────────┐
│ E2E (3%) │ ← real upstream stage env
├──────────────┤
│ Contract (7%)│ ← Pact: BFF↔upstream + BFF↔BFF (handoff)
├──────────────┤
│ Integration│ ← NestJS Test app + ephemeral Postgres + Memorystore
│ (25%) │
├──────────────┤
│ Unit (65%) │ ← orchestrators, BotDetector, HandoffSigner, schema validators
└──────────────┘

Coverage gates (Vitest c8):

  • Statements ≥ 90%
  • Branches ≥ 85%
  • Functions ≥ 90%
  • Lines ≥ 90%

2. Tooling

LayerTool
UnitVitest + @nestjs/testing
IntegrationVitest + Testcontainers (Postgres 16, Redis 7) + nock for HTTP fakes
ContractPact JS (consumer + provider)
E2EPlaywright + dedicated bff-consumer-e2e test runner against stage
Loadk6 + dedicated load-test project + scripted realistic traffic mix
SecurityOWASP ZAP (DAST), pnpm audit, Trivy (image), Cosign (signing)
MutationStryker (nightly on critical files: HandoffSigner, BotDetector, orchestrators)

3. Unit tests

3.1 Coverage targets per file (critical)

FileTarget
src/orchestrators/execute-search-list-view.ts100% lines
src/orchestrators/read-hotel-detail.ts100% lines
src/orchestrators/mint-booking-handoff.ts100% lines
src/orchestrators/consume-booking-handoff.ts100% lines
src/orchestrators/add-to-wishlist.ts100% lines
src/security/handoff-signer.ts100% lines + property-based tests
src/security/bot-detector.ts100% lines
src/composers/listing-card.ts100% lines
src/composers/hotel-detail-vm.ts100% lines
src/cache/single-flight.ts100% lines + race tests

3.2 Property-based tests (fast-check)

  • HMAC sign/verify roundtrip for any valid BookingHandoff payload — never tampers, never accepts mutated payload.
  • Canonical signing string is byte-stable across orderings of input object keys.
  • cacheKey derivation is collision-free for distinct (query, locale, currency) tuples.
  • BotDetector verdict is monotone in score.

4. Integration tests

Run against ephemeral Postgres + Redis containers via Testcontainers.

4.1 Scenarios

ScenarioAsserts
Bootstrap session for first-time visitorgms_ cookie set; Memorystore key present; session.started.v1 enqueued in outbox
Search with cold cacheSingle-flight leader path; cache populated; follower returns same payload
Search with hot cacheNo upstream HTTP call; Cloud Trace span has cache.singleflight.outcome=hit
Search with degraded pricing-service (timeout 50% of fanouts)Returns full results; missing prices marked priceFromCheapest=null; MELMASTOON.BFF.UPSTREAM_PARTIAL annotation in response meta
Hotel detail with stale light-availabilityBest-effort placeholder; warning header present
Wishlist add → list → removePostgres + Memorystore stay in sync; second-add idempotent
Handoff mint → upstream consumeconsumed=true; second consume returns already-consumed outcome
Handoff mint with suspended tenant422 + MELMASTOON.BFF.CONSUMER.HANDOFF_TENANT_SUSPENDED
Currency override X-Currency: ZZZ422 + MELMASTOON.COMMON.VALIDATION_FAILED
Locale fallback chainAccept-Language: ps-AF, ar-SA;q=0.5 selects ps-AF then ar-SA then en-US
Bot pre-screen (curl UA + cadence > 30 rpm)429 + MELMASTOON.BFF.CONSUMER.BOT_SUSPECTED; bot_suspected.v1 queued
reCAPTCHA challenge satisfaction200 returned after X-Recaptcha-Token accepted
Memorystore downReturns 503 + MELMASTOON.BFF.CACHE_UNAVAILABLE; circuit opens
Postgres outbox enqueue retriedPub/Sub publisher retries from outbox after Pub/Sub downtime

4.2 Idempotency tests

  • Same Idempotency-Key for POST /handoff returns identical handoffToken and handoffId across 5 concurrent retries.
  • Same key for POST /wishlist returns the same wishlistItemId and emits wishlist.added.v1 exactly once.
  • Different bodies under the same key are rejected with 409.

5. Contract tests (Pact)

5.1 As consumer

This BFF is a Pact consumer of:

  • search-aggregation-service (search, search/pins, light-availability, facets, popularity)
  • pricing-service (POST /quotes/preview)
  • property-service (detail bundle endpoints)
  • theme-config-service (/brand-peek/batch)
  • tenant-service (suspended-tenant lookup)

For each consumer pact, we publish to the Pact Broker and gate provider releases on the broker's verification report.

5.2 As provider

This BFF is a Pact provider for:

  • bff-tenant-booking-service's POST /internal/handoff/{id}/consume consumer pact (cross-BFF handoff handshake).

We verify against pacts published by the tenant BFF on every PR.

6. E2E tests (stage)

Run nightly + on every release candidate:

FlowSteps
Anonymous search → handoffCold session bootstrap; search Kabul; click first result; mint handoff; redirect URL resolves; tenant BFF accepts and renders bootstrap
Wishlist persistence across sessionsAdd → cookie clear → restore from access token (Phase 2 only)
Bot challenge satisfiedHeadless detection triggers reCAPTCHA; mocked verifier returns success; second request succeeds
Handoff replay rejectedMint → consume → consume again → tenant BFF returns HANDOFF_REPLAYED
Currency switchSwitch from AFN to USD on detail page; price refreshes; currency.changed.v1 emitted

E2E uses Playwright with real Cloud Run stage targets and synthetic guest sessions. Test users / tenants are pre-provisioned via Terraform.

7. Load tests (k6)

7.1 Profiles

ProfileRPSDurationMix
Steady-state200 RPS15 min70% search, 15% hotel detail, 5% handoff, 5% wishlist, 5% telemetry
Campaign spike0 → 1500 RPS over 2 min, hold 10 min12 min90% search list, 5% search detail, 5% handoff
Bot wave50 RPS legitimate + 200 RPS scripted-bot pattern10 minAsserts ≥ 95% of bot pattern is rejected with 429
Long-soak100 RPS8 hMemory + connection-leak detection

7.2 Targets

  • Steady-state: p95 < 700 ms; error rate < 0.1%; instance count converges < 8.
  • Campaign spike: p95 < 900 ms after warm-up; error rate < 0.5%; cache hit ratio > 90%.
  • Bot wave: legitimate p95 unaffected (within 10%); bot pattern rejection rate > 95%.
  • Long-soak: no memory growth > 10% over baseline; no connection leaks.

8. Chaos tests

Quarterly in stage:

ExperimentExpected behaviour
pricing-service 500 ms latency injection/search p95 still < 1500 ms (parallel fanout absorbs slow requests; cheapest-rate marked null after 800 ms cap)
search-aggregation-service 30 s outageCircuit opens; cached responses served until expiry; new searches return 503 + MELMASTOON.BFF.UPSTREAM_UNAVAILABLE
Memorystore failover (replica promoted)< 30 s of elevated latency; no errors after failover; sessions preserved
Postgres failoverMutating endpoints return 503 for ≤ 60 s; reads unaffected (no Postgres on hot read path)
Pub/Sub publish errors 100%Outbox grows; redrive job consumes when Pub/Sub recovers; no event loss
Cosign signature verification disabledDeploy is rejected by binary authorization (must remain enabled)

9. Security tests

  • DAST nightly: OWASP ZAP scan against stage; baseline + active scan; HTML/JSON report archived.
  • Per-PR static: ESLint + @typescript-eslint/strict + eslint-plugin-security + pnpm audit.
  • Secrets scan: gitleaks on every PR.
  • Image scan: Trivy on every container build; high/critical CVE blocks promotion to prod.
  • HMAC fuzzing: Property-based tests + handcrafted tampered tokens; expected: 100% reject.
  • CSRF tests: Verify mutating endpoints reject when Origin header is missing or off-allow-list.
  • Cookie tests: Verify gms_ cookie carries HttpOnly, Secure, SameSite=Lax.

10. Test data

  • Tenants: 5 fixed seed tenants in stage (kabul-grand-hotel, herat-bazaar-inn, mazar-pearl, jalalabad-orchard, bamiyan-cliff).
  • Properties: 12 per tenant.
  • Currencies: 3 (AFN, USD, EUR).
  • Locales: 3 (ps-AF, fa-AF, en-US).

Test fixtures live in services/bff-consumer-service/test/fixtures/ and are regenerated quarterly from the seed environment.

11. CI pipeline

Every PR:

  1. pnpm install --frozen-lockfile
  2. pnpm lint && pnpm typecheck
  3. pnpm test:unit (Vitest, parallel)
  4. pnpm test:integration (Testcontainers Postgres + Redis)
  5. Pact consumer publish + verify upstream pacts
  6. pnpm audit && gitleaks detect
  7. Build + Trivy scan + Cosign sign
  8. Push to dev artifact registry; deploy to dev Cloud Run; smoke test
  9. (PR label e2e) — run subset of Playwright E2E against dev

Nightly:

  • Stryker mutation tests on critical files (must achieve mutation score ≥ 75%).
  • ZAP DAST against stage.
  • Long-soak load test.
  • Chaos experiments (rotating).

12. Release readiness checklist (linked from SERVICE_READINESS)

  • All P1 alerts have ack'd runbooks.
  • p95/p99 SLOs met in load test.
  • HMAC key rotation drill executed in stage in last 90 days.
  • Mutation score ≥ 75% on critical files.
  • Pact verifications green for all upstreams + tenant-BFF consumer pact.
  • DAST report has no high/critical findings.