Files
api/UAT_PLAYBOOK.md
T
Flea Flicker 49d8ccc249
CI / Lint & Typecheck (pull_request) Successful in 15s
CI / Test (pull_request) Successful in 17s
CI / Build & Push Docker Images (pull_request) Successful in 30s
docs(UAT_PLAYBOOK): add TC-API-3.28 for pet_size_category enum verification (GRO-1999)
The 0037 migration (merged via PR #124 / GRO-1979) adds 'extra_large'
to the pet_size_category enum, fixing the 22P02 error in the UAT
seed-test-data job. Mirror the regression test added for coat_type
in TC-API-3.27: TC-API-3.28 explicitly verifies pet_size_category
contains all 4 values used by seed.ts  after
the seed job completes, so future enum drift on either dimension
surfaces at the UAT gate.

Refs: GRO-1999, GRO-1971 (precedent), PR #124 (the code fix).

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-06-01 13:37:01 +00:00

24 KiB

UAT Playbook — GroomBook API

Overview

GroomBook API is a Hono-based REST service (TypeScript/Node.js) powering the pet grooming management platform. Handles authentication, client/pet management, appointment scheduling, invoicing, payments, staff management, and the customer portal.

Environments

Environment URL
Dev dev.groombook.dev
UAT uat.groombook.dev
Prod demo.groombook.app

Pre-conditions

  • UAT environment accessible and healthy
  • Test accounts seeded (manager, staff, client personas)
  • OIDC authentication provider configured
  • Seed data present (clients, pets, services, staff)

Test Cases

4.0 Health Check

# Scenario Steps Expected
TC-API-0.1 Unauthenticated health check GET /api/health 200 OK, {"status":"ok"}

Note (GRO-1544): Health endpoint registered on api basePath before auth middleware at /api/health. The old path /health was incorrect (routed to web pod via HTTPRoute /* rule).

4.1 Authentication

# Scenario Steps Expected
TC-API-1.1 Login via OIDC POST to OIDC provider callback, verify JWT token issued 200 OK, JWT returned with valid claims
TC-API-1.4 Email+password login (UAT) POST /api/auth/sign-in/email with uat-super@groombook.dev + SEED_UAT_SUPER_PASSWORD 200 OK, session cookie returned
TC-API-1.5 Email+password login — groomer POST /api/auth/sign-in/email with uat-groomer@groombook.dev + SEED_UAT_GROOMER_PASSWORD 200 OK, session cookie returned
TC-API-1.6 Email+password login — customer POST /api/auth/sign-in/email with uat-customer@groombook.dev + SEED_UAT_CUSTOMER_PASSWORD 200 OK, session cookie returned
TC-API-1.7 Email+password login — tester POST /api/auth/sign-in/email with uat-tester@groombook.dev + SEED_UAT_TESTER_PASSWORD 200 OK, session cookie returned
TC-API-1.8 Email+password — invalid password POST /api/auth/sign-in/email with wrong password 400 Bad Request, error returned
TC-API-1.9 Email+password — unknown user POST /api/auth/sign-in/email with non-existent email 400 Bad Request, error returned
TC-API-1.10 Auto-provision on first OIDC login First login as a Better-Auth user with no existing staff record 200 OK, access granted; groomer staff record auto-created with name/email from user table

Note (GRO-1977): Seed credential provisioning is idempotent — re-running the seed with updated SEED_UAT_*_PASSWORD env vars rotates stored credential hashes. TC-API-1.4 through TC-API-1.7 now return 200 for all 4 UAT personas (previously returned 401 due to frozen-hash bug). | TC-API-1.11 | Existing staff unaffected by OIDC login | Login as uat-groomer@groombook.dev (email+password), then GET /api/staff to find that record | 200 OK, staff record unchanged — no duplicate created, original role and isSuperUser preserved | | TC-API-1.12 | Auto-provisioned role and superUser flags | After TC-API-1.10, GET /api/staff and inspect the auto-created record | role = "groomer", isSuperUser = false, active = true | | TC-API-1.13 | Name fallback — user.name present | Auto-provision where Better-Auth user has name set | Staff name = user.name value from user table | | TC-API-1.14 | Name fallback — no name, email present | Auto-provision where Better-Auth user has name = null, email = "test@example.com" | Staff name = "test" (email prefix before @) | | TC-API-1.15 | Name fallback — no name, no email | Auto-provision where Better-Auth user has name = null, email = null | Staff name = "Unknown" | | TC-API-1.16 | OIDC login — Terraform-provisioned user | Initiate OIDC login as any UAT persona (uat-super, uat-groomer, uat-customer, uat-tester), complete authentik callback | 200 OK, session created — no account_not_linked error |

SSO Login Journey (Authentik OIDC end-to-end)

# Scenario Steps Pass Criteria Fail Criteria
TC-API-1.17 SSO redirect to Authentik Navigate to app → sign-in page shown → click "Sign in with SSO" Redirected to Authentik at auth.farh.net 403 error, redirect loop, no SSO button
TC-API-1.18 Authenticate with valid OIDC credentials At Authentik login page, enter valid credentials and authenticate Redirected back to app with valid session Redirect loop, 403, missing session cookie
TC-API-1.19 SSO user auto-provisioned as groomer Complete SSO login as a user with no pre-existing staff record 200 response; groomer staff record auto-created; session active 403 Forbidden, staff record not created
TC-API-1.20 Existing staff record resolves correctly Complete SSO login as uat-groomer (pre-existing staff) 200 OK, correct staff identity resolved, no duplicate record created 403, duplicate record, wrong staff data
TC-API-1.21 SSO session grants dashboard access After TC-API-1.18 SSO login, GET /api/staff/me 200 OK, valid staff record returned, correct role displayed 401/403, missing session, wrong identity

OOBE Flow Post-Login

# Scenario Steps Pass Criteria Fail Criteria
TC-API-1.22 Fresh DB reports needsSetup On a fresh DB (no super user), GET /api/setup/status needsSetup: true returned needsSetup: false when it should be true
TC-API-1.23 Configure OIDC via auth-provider endpoint POST /api/setup/auth-provider with valid OIDC config 200 OK, auth provider configured, no 403 403, setup blocked, invalid config rejected
TC-API-1.24 Complete setup creates super user POST /api/setup with business name (after TC-API-1.23) First user becomes super user, setup completes Setup errors, 403 on admin endpoints
TC-API-1.25 Super user accesses admin features After TC-API-1.24, GET /api/staff/me and verify isSuperUser: true isSuperUser: true, admin endpoints accessible 403 on admin, isSuperUser: false
TC-API-1.26 Auto-provision skipped during OOBE During fresh setup (needsSetup: true), complete OIDC login — verify no duplicate staff record created before setup completes No duplicate staff, OOBE completes successfully Duplicate staff record, 403 before setup, auto-provision interferes with OOBE

4.2 Client Management

# Scenario Steps Expected
TC-API-2.1 List clients GET /api/clients 200 OK, list of active clients returned
TC-API-2.2 Get client details GET /api/clients/{id} 200 OK, client details returned
TC-API-2.3 Create client POST /api/clients with valid data 201 Created, client record created
TC-API-2.4 Update client PATCH /api/clients/{id} with updated fields 200 OK, client updated
TC-API-2.5 Disable client PATCH /api/clients/{id} with status: "disabled" 200 OK, client marked as disabled
TC-API-2.6 Delete client DELETE /api/clients/{id}?confirm=true 200 OK, client deleted (if no appointments)

4.3 Pet Management

# Scenario Steps Expected
TC-API-3.1 List pets GET /api/pets 200 OK, list of pets returned
TC-API-3.2 Get pet details GET /api/pets/{id} 200 OK, pet details including history returned
TC-API-3.3 Add pet POST /api/pets with valid pet data 201 Created, pet record created
TC-API-3.4 Update pet PATCH /api/pets/{id} with updated fields 200 OK, pet updated
TC-API-3.5 Delete pet DELETE /api/pets/{id} 200 OK, pet deleted
TC-API-3.6 Upload pet photo POST /api/pets/{id}/photo/upload-url, then confirm 200 OK, photo uploaded and key stored
TC-API-3.7 View pet photo GET /api/pets/{id}/photo 200 OK, presigned URL returned
TC-API-3.8 Create pet with extended fields POST /api/pets with coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts 201 Created, all extended fields stored and returned
TC-API-3.9 Update pet extended fields PATCH /api/pets/{id} with coatType, temperamentScore, medicalAlerts 200 OK, extended fields updated
TC-API-3.10 Reject invalid coatType POST /api/pets with coatType: "smooth" 400 Bad Request, invalid coatType rejected
TC-API-3.11 Reject out-of-range temperamentScore POST /api/pets with temperamentScore: 0 or 6 400 Bad Request, score out of range rejected
TC-API-3.12 Reject invalid medicalAlert severity POST /api/pets with medicalAlerts severity: "critical" 400 Bad Request, invalid severity rejected
TC-API-3.13 Reject too many temperamentFlags POST /api/pets with 21 temperamentFlags 400 Bad Request, max 20 flags enforced
TC-API-3.14 Reject too many preferredCuts POST /api/pets with 21 preferredCuts 400 Bad Request, max 20 cuts enforced
TC-API-3.15 Reject too many medicalAlerts POST /api/pets with 51 medicalAlerts 400 Bad Request, max 50 alerts enforced
TC-API-3.16 Get pet profile summary GET /api/pets/{id}/profile-summary 200 OK, aggregated profile with grooming history, visit count, upcoming appointment
TC-API-3.17 Get pet profile summary — groomer restricted GET /api/pets/{id}/profile-summary as groomer with no pet linkage 403 Forbidden
TC-API-3.18 Get pet profile summary — visitCount returns full count GET /api/pets/{id}/profile-summary with 2+ completed appointments visitCount >= 2 (not capped at 1)
TC-API-3.19 Get pet profile summary — upcomingAppointment excludes past GET /api/pets/{id}/profile-summary with a past confirmed/scheduled appointment upcomingAppointment is null (past appointments filtered by startTime >= now)

Seed Data Verification (GRO-1898)

As of PR #98, UAT seed data populates all 5 extended profile fields for every pet, including the 5 deterministic UAT test client pets (Alpha, Bravo, Charlie, Delta, Echo). This enables manual verification of extended profile rendering without requiring a DB reset.

# Scenario Steps Expected
TC-API-3.20 GET /api/clients returns seed data GET /api/clients 200 OK, array with 1+ clients (UAT seed creates 500 + 5 deterministic UAT clients)
TC-API-3.21 GET /api/pets/{id} returns extended fields for seed pet Pick any pet ID from UAT test clients (uat-alpha through uat-echo pet names: TestBuddy, TestMax, TestCooper, TestRocky, TestDuke) and GET /api/pets/{id} 200 OK; coatType, temperamentScore, temperamentFlags, medicalAlerts, preferredCuts all non-null
TC-API-3.22 Verify medicalAlerts shape GET /api/pets/{id} for any pet with non-empty medicalAlerts medicalAlerts is an array; each entry has type, description, severity
TC-API-3.23 Verify UAT test pet Charlie has behavioral alert GET /api/pets/{id} where name = "TestCooper" (pet for uat-charlie@groombook.dev) medicalAlerts includes an entry with type: "behavioral", severity: "low" or "high"
TC-API-3.24 Verify UAT test pet Delta has skin alert GET /api/pets/{id} where name = "TestRocky" (pet for uat-delta@groombook.dev) medicalAlerts includes an entry with type: "skin"
TC-API-3.25 Verify 30+ total pets in UAT DB GET /api/pets then count total 30+ pets returned (UAT seed creates 500 random-pool + 5 UAT test clients + 2 UAT customer = 507 total)
TC-API-3.26 Verify 25-35% medicalAlerts distribution GET /api/pets (first 30 pets), count how many have non-empty medicalAlerts Ratio is 25-35% (seed uses rand() < 0.3 for ~30% distribution)
TC-API-3.27 Verify coat_type enum has all seed values After UAT seed completes, inspect the coat_type enum on the UAT DB — it must contain: short, medium, long, double, wire, silky, curly, hairless UAT seed jobs (reset-demo-data, seed-test-data) complete 1/1 with no enum_in error; coat_type includes all 8 values used by seed.ts coatTypePool
TC-API-3.28 Verify pet_size_category enum has all seed values After UAT seed completes, inspect the pet_size_category enum on the UAT DB — it must contain: small, medium, large, extra_large UAT seed jobs (reset-demo-data, seed-test-data) complete 1/1 with no enum_in error; pet_size_category includes all 4 values used by seed.ts petSizeCategoryPool (regression for GRO-1999, mirrors TC-API-3.27)

4.4 Appointment Scheduling

# Scenario Steps Expected
TC-API-4.1 List appointments GET /api/appointments 200 OK, list of appointments returned
TC-API-4.2 Get appointment details GET /api/appointments/{id} 200 OK, appointment details returned
TC-API-4.3 Create single appointment POST /api/appointments with valid data 201 Created, appointment created
TC-API-4.4 Create recurring appointment POST /api/appointments with recurrence object 201 Created, series of appointments created
TC-API-4.5 Update appointment PATCH /api/appointments/{id} with updated fields 200 OK, appointment updated
TC-API-4.6 Reschedule with cascade PATCH /api/appointments/{id} with cascadeMode: "this_and_future" 200 OK, future appointments updated
TC-API-4.7 Cancel appointment DELETE /api/appointments/{id} 200 OK, appointment marked as cancelled
TC-API-4.8 Confirm appointment POST /api/appointments/{id}/confirm 200 OK, confirmation status set to confirmed
TC-API-4.9 Cancel confirmation POST /api/appointments/{id}/cancel 200 OK, confirmation cancelled
TC-API-4.10 Conflict detection POST /api/appointments with conflicting time 409 Conflict, error message returned

4.5 Services

# Scenario Steps Expected
TC-API-5.1 List services GET /api/services 200 OK, list of active services returned
TC-API-5.2 Get service details GET /api/services/{id} 200 OK, service details returned
TC-API-5.3 Create service POST /api/services with valid data 201 Created, service created
TC-API-5.4 Update service PATCH /api/services/{id} with updated fields 200 OK, service updated
TC-API-5.5 Delete service DELETE /api/services/{id} 200 OK, service deleted

4.6 Staff Management

# Scenario Steps Expected
TC-API-6.1 List staff GET /api/staff 200 OK, list of active staff returned
TC-API-6.2 Get staff details GET /api/staff/{id} 200 OK, staff details returned
TC-API-6.3 Create staff POST /api/staff with valid data 201 Created, staff created
TC-API-6.4 Update staff PATCH /api/staff/{id} with updated fields 200 OK, staff updated
TC-API-6.5 Delete staff DELETE /api/staff/{id} 200 OK, staff deleted (if no appointments)
TC-API-6.6 RBAC check Access manager-only endpoint as groomer 403 Forbidden, error message returned

4.7 Invoicing & Payments

# Scenario Steps Expected
TC-API-7.1 List invoices GET /api/invoices 200 OK, list of invoices returned
TC-API-7.2 Get invoice details GET /api/invoices/{id} 200 OK, invoice with line items returned
TC-API-7.3 Create invoice POST /api/invoices with line items 201 Created, invoice created
TC-API-7.4 Create from appointment POST /api/invoices/from-appointment/{appointmentId} 201 Created, invoice created from appointment
TC-API-7.5 Update invoice PATCH /api/invoices/{id} with status and payment method 200 OK, invoice updated
TC-API-7.6 Process payment via Stripe POST /api/invoices/{id}/pay with Stripe data 200 OK, payment intent created
TC-API-7.7 Save tip splits POST /api/invoices/{id}/tip-splits with splits array 201 Created, tip splits saved
TC-API-7.8 Process refund POST /api/invoices/{id}/refund with amount 200 OK, refund processed

4.8 Customer Portal

# Scenario Steps Expected
TC-API-8.1 Access portal GET /api/portal/me with valid session token 200 OK, client profile returned
TC-API-8.2 View portal appointments GET /api/portal/appointments 200 OK, list of client's appointments returned
TC-API-8.3 Confirm appointment via portal POST /api/portal/appointments/{id}/confirm 200 OK, appointment confirmed
TC-API-8.4 Cancel appointment via portal POST /api/portal/appointments/{id}/cancel 200 OK, appointment cancelled
TC-API-8.5 Add waitlist entry POST /api/portal/waitlist with pet and service 201 Created, waitlist entry created
TC-API-8.6 View portal invoices GET /api/portal/invoices 200 OK, list of client's invoices returned
TC-API-8.7 Pay multiple invoices POST /api/portal/invoices/pay-multiple with invoice IDs 200 OK, payment intent created
TC-API-8.8 SSO bridge — valid Better Auth session POST /api/portal/session-from-auth with valid Better Auth session cookie (authenticated SSO user with matching client email) 201 Created, {sessionId, clientId, clientName} returned
TC-API-8.9 SSO bridge — no Better Auth session POST /api/portal/session-from-auth without Better Auth session cookie 401 Unauthorized
TC-API-8.10 SSO bridge — no matching client POST /api/portal/session-from-auth with valid Better Auth session for a user with no client record 404 Not Found, error "No client record found for this user"
TC-API-8.11 SSO bridge — returned session works on portal routes After TC-API-8.8, use returned sessionId as X-Impersonation-Session-Id header on GET /api/portal/me 200 OK, client profile returned

4.9 Waitlist

# Scenario Steps Expected
TC-API-9.1 List waitlist GET /api/waitlist 200 OK, list of waitlist entries returned
TC-API-9.2 Add to waitlist POST /api/waitlist with client, pet, service 201 Created, entry added
TC-API-9.3 Promote from waitlist Create appointment from waitlist entry 201 Created, appointment created, waitlist updated
# Scenario Steps Expected
TC-API-10.1 Global search clients GET /api/search?q={client_name} 200 OK, matching clients returned
TC-API-10.2 Global search pets GET /api/search?q={pet_name} 200 OK, matching pets with owners returned
TC-API-10.3 Search by email GET /api/search?q={email} 200 OK, matching client returned
TC-API-10.4 Search by phone GET /api/search?q={phone} 200 OK, matching client returned

4.11 Reports

# Scenario Steps Expected
TC-API-11.1 Revenue summary GET /api/reports/summary?from={date}&to={date} 200 OK, revenue KPIs returned
TC-API-11.2 Revenue by period GET /api/reports/revenue?groupBy=day 200 OK, daily revenue breakdown returned
TC-API-11.3 Appointment analytics GET /api/reports/appointments 200 OK, appointment stats returned
TC-API-11.4 Service popularity GET /api/reports/services 200 OK, service usage stats returned
TC-API-11.5 Client retention GET /api/reports/clients 200 OK, new/returning/churn client data returned
TC-API-11.6 Tip splits report GET /api/reports/tip-splits 200 OK, tip earnings per staff returned
TC-API-11.7 Export revenue CSV GET /api/reports/export.csv?type=revenue 200 OK, CSV file downloaded

4.12 Impersonation

# Scenario Steps Expected
TC-API-12.1 Start impersonation session POST /api/impersonation/sessions with clientId 201 Created, session token returned
TC-API-12.2 Get session details GET /api/impersonation/sessions/{id} 200 OK, session details returned
TC-API-12.3 Extend session POST /api/impersonation/sessions/{id}/extend 200 OK, session expiry extended
TC-API-12.4 End session POST /api/impersonation/sessions/{id}/end 200 OK, session marked as ended
TC-API-12.5 Log audit entry POST /api/impersonation/sessions/{id}/log 201 Created, audit log entry created
TC-API-12.6 View audit log GET /api/impersonation/sessions/{id}/audit-log 200 OK, audit trail returned

4.13 Settings & Setup

# Scenario Steps Expected
TC-API-13.1 Get business settings GET /api/admin/settings 200 OK, business settings returned
TC-API-13.2 Update business settings PATCH /api/admin/settings with updated values 200 OK, settings updated
TC-API-13.3 Upload logo POST /api/admin/settings/logo/upload with file 200 OK, logo uploaded and stored
TC-API-13.4 View logo GET /api/admin/settings/logo 200 OK, logo image returned
TC-API-13.5 Delete logo DELETE /api/admin/settings/logo 200 OK, logo removed
TC-API-13.6 Check setup status GET /api/setup/status 200 OK, setup needs returned
TC-API-13.7 Complete setup POST /api/setup with business name 201 Created, super user created
TC-API-13.8 Configure auth provider POST /api/setup/auth-provider with OIDC config 201 Created, auth provider configured
TC-API-13.9 Test auth provider POST /api/setup/auth-provider/test with issuer URL 200 OK, OIDC discovery successful

4.14 Appointment Groups

# Scenario Steps Expected
TC-API-14.1 List appointment groups GET /api/appointment-groups 200 OK, list of groups returned
TC-API-14.2 Get group details GET /api/appointment-groups/{id} 200 OK, group with appointments returned
TC-API-14.3 Create group booking POST /api/appointment-groups with client and pets 201 Created, group and appointments created
TC-API-14.4 Update group notes PATCH /api/appointment-groups/{id} with notes 200 OK, notes updated
TC-API-14.5 Cancel group DELETE /api/appointment-groups/{id} 200 OK, all appointments cancelled

4.15 Buffer Rules

# Scenario Steps Expected
TC-API-15.1 List buffer rules GET /api/admin/buffer-rules 200 OK, list of active buffer rules returned
TC-API-15.2 Create buffer rule POST /api/admin/buffer-rules with service, species, sizeCategory, bufferMinutes 201 Created, buffer rule created
TC-API-15.3 Update buffer rule PATCH /api/admin/buffer-rules/{id} with updated bufferMinutes 200 OK, buffer rule updated
TC-API-15.4 Delete buffer rule DELETE /api/admin/buffer-rules/{id} 200 OK, buffer rule removed
TC-API-15.5 Reject invalid bufferMinutes POST /api/admin/buffer-rules with bufferMinutes: -5 400 Bad Request, invalid bufferMinutes rejected
TC-API-15.6 Reject missing required fields POST /api/admin/buffer-rules with service only 400 Bad Request, species and sizeCategory required
TC-API-15.7 Booking uses buffer Book appointment for pet with sizeCategory; verify duration reflects buffer 201 Created, appointment duration includes buffer time

Pass/Fail Criteria

Pass:

  • All test cases execute without errors
  • Expected results match actual results
  • No regressions in previously working features
  • API responses have correct status codes and data structures
  • Authentication and authorization enforced correctly
  • Business rules (conflicts, validations) work as expected

Fail:

  • Any unexpected result or error
  • API returns incorrect status codes
  • Data integrity issues
  • Authentication/authorization bypass
  • Business rules not enforced
  • Severity documented with steps to reproduce and screenshot

Update Policy

Any PR that changes user-facing behaviour MUST update this file. Test cases must be added, modified, or removed to reflect the new behaviour. The PR description must reference which playbook section was updated (e.g., "Updated UAT_PLAYBOOK.md §4.4 — new appointment rescheduling flow").