progress/testing.md

Testing Summary

Rolling snapshot of every automated test running in this workspace. Rebuild this doc (or refresh the tables) whenever test counts shift meaningfully. For feature scope see features.md; for release-level history see changelog.md.

Last updated: 2026-04-22

Totals at a Glance

Repo Framework Files Tests Passing Skipped Failing Wall time (local)
booking-api Jest (unit + integration) 106 1145 1143 2 0 ~5 s
booking-api Jest (e2e, real DB + Redis) 63 61 0 2 † ~45 s
booking-web Vitest (unit + component) 14 112 112 0 0 ~4 s
booking-web Playwright (e2e, smoke + regression) 4 25 24 0 1 fixme ~34 s
booking-web Playwright (@integration, live PSP) 1 1 1 ‡
booking-mobile 0 0 0 0 0
Total (repo-wide, non-integration) 124 1338 1334 2 2

customer-auth.e2e-spec.ts: 2 legacy scenarios around email-dedup flaking on a pre-existing seed interaction. Tracked separately, not caused by the current tranche.

payment-settings-integration.spec.ts: hits live Bambora sandbox, gated by E2E_BAMBORA_* env vars, wrapped in test.skip() locally by default.

Commands

Run from each repo's own root (all tooling is yarn-only).

# booking-api — Jest
yarn test                     # unit + integration, ~5s
yarn test:e2e                 # e2e against docker-compose DB + Redis
yarn test:cov                 # coverage report

# booking-web — Vitest + Playwright
yarn test                     # vitest (unit/component), ~4s
yarn test:watch               # vitest watch mode
yarn test:e2e                 # playwright, excludes @integration
yarn test:e2e:integration     # playwright, only @integration (needs live PSP)
yarn test:e2e:ui              # playwright UI mode for debugging

booking-api — Jest

Unit + integration (yarn test)

Coverage is broken down by bounded context / module. Counts below are grouped from suite manifests; see src/core/**/*.spec.ts for the live source.

Area Suites Tests Notes
Auth (login, refresh, guards, password reset) 12 ~38 Token versioning, tenant isolation, rate-limit guard
Tenant + Onboarding 8 ~26 Settings merge, slug uniqueness, industry resolution
Resource management 9 ~32 Schedule, time-off, skill assignment, soft-delete
Service catalog 7 ~26 Categories, VAT, active-flag, skill links
Booking engine (incl. multi-service) 14 ~50 Conflict detection, cross-item availability, snapshot on create
Availability calculator 5 ~20 Business hours, buffer, time-off overlap, weekday edge cases
Public booking API 6 ~25 Slug resolution, guest path, resource filter, skill scoping
TenantCustomer (saved customer profile) 4 ~12 Dedup by phone/email, per-tenant scope
Loyalty 8 ~28 Discount compute, redemption ledger, idempotency
Loyalty Track L2–L3 (DDD cut) 4 58 Pure helper + DDD reservations + backfill guards
Payments — domain + app (Track A–C) 18 ~280 State machine, intent creation, outbox, refund/capture/void
Payments — provider adapters 12 ~155 Bambora Classic (adapter, MD5, mapper, http-client, retry), Worldline kept for future
Payments — expiry cron + metrics 5 ~25 15-min sweep job, findExpirable, Prometheus labels
Shared primitives (src/shared/) 6 ~30 Clock, UUIDv7, event-bus, outbox port + Prisma impl
Infrastructure (Prisma adapter, logger, etc.) 4 ~25 PrismaService constructor DI, request-id interceptor
Total ~106 ~1145 2 skipped suites reserved for future PSP wiring

Keep coverage ≥ 80 % on any module touched by a PR; use yarn test:cov locally to verify.

End-to-end (yarn test:e2e)

Boots the Nest app against a clean Postgres schema and Redis instance from docker-compose.yml, seeds fixtures per spec, asserts HTTP responses.

Spec Scenarios Passing Purpose
booking.e2e-spec.ts 12 12 Admin booking CRUD + status transitions
public-booking.e2e-spec.ts 18 18 Guest flow, deposit branch, skill enforcement, conflict
customer-auth.e2e-spec.ts 9 7 † Google OAuth callback, token version, profile update
payment.e2e-spec.ts 14 14 Initiate → capture / refund / void via mock provider
outbox.e2e-spec.ts 6 6 Event publishing, retry, dead-letter after max attempts
tenant.e2e-spec.ts 4 4 Settings PATCH, industry change guards
Total 63 61

† The two currently failing scenarios are email-dedup edge cases exposed after the customer-auth cleanup commit — unrelated to current tranche, tracked for a follow-up fix.

booking-web — Vitest

Unit + component tests run with jsdom + @testing-library/react. Colocated under src/**/*.test.tsx.

File Tests Concern
lib/timezone.test.ts ~16 Salon-tz formatters, DST edges, month/day boundaries
lib/booking-display.test.ts ~12 Booking detail formatting, status labels
lib/formatters.test.ts (in domain files) Duration + phone normalisation
lib/payment/bambora-schema.test.ts 17 Zod validation for Bambora Classic credentials (T/P prefix → test/prod)
lib/payment/deposit-calc.test.ts ~8 Percentage vs fixed, zero-price guard, rounding
lib/payment/provider-metadata.test.ts 4 Provider label lookup
lib/payment/public-payment-api.test.ts ~6 redirectToCheckout polling, timeout, failure mapping
components/settings/payment/BamboraConfigForm.test.tsx 5 Merchant-mode badge, required fields
components/settings/payment/ProviderCard.test.tsx 7 Active / inactive / Coming-soon states
components/settings/payment/HealthCheckBadge.test.tsx 3 Healthy / degraded / unknown
components/payments/refund-schema.test.ts 8 Refund amount bounds, partial vs full
components/payments/capture-schema.test.ts 5 Capture ≤ authorized amount
components/payments/collect-remaining-schema.test.ts 7 Remaining > 0, ≤ max, integer minor-units
hooks/usePayments.test.tsx 2 Query terminal-status polling stop
hooks/usePaymentConfigs.test.tsx 8 Active config resolution + invalidation
Total 112

booking-web — Playwright

Headless Chromium by default (playwright.config.ts). Serial execution (workers: 1, fullyParallel: false) because every setting-mutation test PATCHes the shared studio-nordic tenant; restoring in finally keeps state clean.

ownerApi / staffApi fixtures are worker-scoped; loggedInPage / loggedInAsStaffPage load pre-saved storageState (login once per worker per role) so the auth endpoint is hit a handful of times per run — avoids tripping the 10-req/min login rate-limiter on long serial suites.

Smoke (admin)

Spec Scenarios What it covers
payment-settings.spec.ts 3 Provider list renders, Vipps disabled, Bambora drawer opens
payments.spec.ts 2 Payments index renders + shows in sidebar
staff-role.spec.ts 7 STAFF signin + sidebar filter (4 visible / 4 hidden) + OwnerOnlyGuard bounces (/admin/staff, /admin/settings, /admin/services) + /admin/bookings accessible + OWNER→STAFF role switch preserves tenantId

Regression — public booking flow (public-booking.spec.ts)

13 tests, organised in 4 tranches.

Tranche 0 — DateStrip clamp by maxBookingDaysInAdvance

# Scenario
0.1 Cap ≥ 28 → strip saturates at exactly 28 date cells
0.2 Cap = 5 → strip renders exactly 6 cells (today through today+5)

Tranche 1 — Service & staff selection

# Scenario
1.1 Happy path: 1 service → confirmed booking (deposit off)
1.2 Multi-service: 2 services → totals aggregate in summary
1.3 Unassigned booking (bookingMode=allow_unassigned) → succeeds without staff — test.fixme pending API fix: first slot at 09:00 lands on BOOKING_OUTSIDE_BUSINESS_HOURS on the unassigned path only (1.1 same slot + assigned staff passes)
1.4 Assigned-only: submit without staff → inline error on the staff field

Tranche 2 — Settings enforcement

# Scenario
2.3 Closed day (Sunday in seed) renders as disabled cell in DateStrip
2.4 Time slots are bounded by business hours (no post-21:00 leakage)
2.5 depositEnabled=true → deposit notice visible + submit redirects to PSP (Bambora checkout URL)

Tranche 3 — Validation & error states

# Scenario
3.1 Submit with blank customerName → inline error, no redirect
3.2 Staff dropdown count matches /resources?serviceId=… API (skill filter correctness)
3.3 Invalid serviceId → "no-service-selected" state, booking form does not mount

Tranche 4 — Book again

# Scenario
4.1 ?from=<bookingId> pre-fills the service item + notes field from a seeded booking

data-testid catalogue (added for the tranche above)

TestID File Used by
booking-form BookingPage.tsx Reach the form root; assert it didn't mount on invalid deep-link
booking-submit BookingPage.tsx Click to submit
booking-error BookingPage.tsx Top-level submit banner
booking-summary BookingPage.tsx Side panel — tests assert totals appear here
booking-success BookingPage.tsx Renders on requiresPayment=false; data-status attribute mirrors booking status
deposit-notice BookingPage.tsx Amber banner; data-deposit-amount for numeric assertion
no-service-selected BookingPage.tsx Empty-state when deep-link resolves to zero items
service-item ServiceItem.tsx Per-service row (data-service-id on the same node)
service-item-remove ServiceItem.tsx Remove button (only when canRemove=true)
service-item-staff ServiceItem.tsx Staff SearchSelect wrapper (click to open, scope option queries inside)
service-item-time ServiceItem.tsx Time-slot SearchSelect wrapper
service-picker-add ServicePicker.tsx Add-service CTA
service-picker-search ServicePicker.tsx Expanded search mode of the picker
date-strip DateStrip.tsx Scrollable day container
date-strip-day DateStrip.tsx Each day button; data-date=YYYY-MM-DD attribute

booking-mobile

No automated tests yet. When testing starts, this section will mirror the web layout.

Conventions

  • TDD is mandatory — write failing test first, then implement. See development-rules.md.
  • E2E tests always restore settings in a finally block — the studio-nordic tenant is shared between specs and serial execution depends on a known state.
  • Worker-scoped fixtures for auth — prevents rate-limiter false positives when running > 5 tests in one worker.
  • Scope dropdown locators to the SearchSelect wrapper (via data-testid) — the dropdown is rendered as a fixed-position <div> inside the component root, so page.locator('li') without a scope will leak into unrelated lists on the page.
  • Playwright tests create real bookings on studio-nordic — intentional so a developer can spot-check them in admin after a run. Leave a discoverable marker in customerName / notes (e.g. E2E Happy Path, E2E rebook seed <ts>) so the run attribution is obvious.

Known Gaps (targets for the next tranche)

  • Auth flows: no Playwright coverage of customer login / Google OAuth, admin login, or forgot-password round-trip.
  • Admin calendar: drag-and-drop, conflict warning, booking drawer — still manual-test only.
  • Loyalty: unit coverage is good, but no E2E of the redeem-on-checkout flow.
  • Customer portal: booking list pagination + "my bookings" cancel flow have no tests.
  • Settings: only the Payments tab is touched by E2E.
  • Mobile app: zero automated tests (see above).

These are tracked in features.md under each corresponding module.