---
name: flow-testing
description: >-
  Boot both sides of Balinyaar locally and walk a real user journey end to end — the right seeded account,
  the right flow doc, and knowing whether you just proved the real path or a mock answering. Use before
  claiming a fix or feature works, or when asked to test, verify, or demo a flow.
---

# Balinyaar Flow Testing

Exercising a flow proves something only if you know which half of the stack actually answered. This is the
procedure; the facts it points at (ports, accounts, known failure modes) live in
[docs/flows/testing-setup.md](../../../archive/docs/flows/testing-setup.md) and are kept current there — don't copy
them here, they will drift.

---

## 1. Boot it

The five-minute path, verbatim from [testing-setup.md](../../../archive/docs/flows/testing-setup.md#the-five-minute-path):

```bash
# API — mock SMS or request_otp 500s
cd server
Seams__Sms__Provider=mock dotnet run --project src/API/Baya.Web.Api/Baya.Web.Api.csproj

# client
cd client && npm install && npm run dev        # http://localhost:3000/fa

# read the OTP — the console does NOT print it
curl http://localhost:5002/api/v1/dev/last_otp/09120000010
```

No database setup: the committed dev config points at an already-seeded remote SQL Server. If anything here
doesn't match reality when you run it, **testing-setup.md is wrong and needs a fix in the same change** — it
carries a `Last verified` stamp for exactly this reason.

---

## 2. Check the mock-vs-real map *before* you conclude anything

A flow "working" through a mocked domain proves the UI, not the server. Before testing:

1. Open [docs/integration/domains/index.md](../../../archive/docs/integration/domains/index.md) — the census table
   names which of the 22 client `services/` domains are real vs **mock** (currently 15 real, 7 mock:
   `admin`, `bnpl`, `partnerCenter`, `patientRecords`, `payouts`, `refunds`, `verification`).
2. A mocked domain is a `USE_<DOMAIN>_MOCK` flag in `client/src/services/<domain>/constants.ts` — check it
   directly if you need certainty for the exact domain you're touching.
3. State your finding in terms of which one you exercised: "the booking flow works end-to-end against the
   real server" is a different claim from "the admin console renders correctly against its mock" — never
   report the second as if it were the first.

---

## 3. Pick the right seeded account

Demo accounts, their roles, and what each one demonstrates are tabulated in
[testing-setup.md](../../../archive/docs/flows/testing-setup.md#demo-accounts) — read it there rather than assuming
a phone number. One standing gap to route around: **the seeded admin accounts (`…020` `super_admin`,
`…021` `finance`) get 403 on every real admin endpoint** (a `DynamicPermission` / role-literal mismatch).
The admin backoffice is only testable against the client's mock; don't spend time trying to walk it against
the real API without first checking whether that gap has been closed.

---

## 4. Walk the flow

[docs/flows/index.md](../../../archive/docs/flows/index.md) is the atlas — one file per user-meaningful journey,
each answering exactly three questions: what it does, what's mocked *for that journey specifically*, and how
to test it. Open the one file that matches what you're testing rather than guessing the steps; it's the
one place gap numbers and REQ references for that journey are tracked.

---

## 5. Two things that will silently invalidate your test

- **The scheduler is live while you test.** `booking_request_expiry` runs every 60 seconds (hardcoded) and
  flips an un-actioned request to `expired_no_response` / `payment_deadline_expired` out from under you. Act
  on a request promptly, or create a fresh one rather than trying to reuse an old test artifact.
- **The OTP endpoints are rate-limited together.** `request_otp` and `verify_otp` share one bucket, 5 calls
  per 60 s per IP — a login is 2 calls, so that's **two logins per minute, total**. Space scripted logins
  ≥ 40 s apart (see [testing-setup.md](../../../archive/docs/flows/testing-setup.md#scripting-logins) for a working
  script) or you'll 429 and misread it as a bug.

---

## 6. When the seeded world has aged out

There is no in-app reseed — both seeders guard on natural keys, so re-running never refreshes stale dates.
If the scenario you need (an "upcoming" booking, an open dispute window, a pending request) no longer exists
because the world was seeded days ago:

- **Fastest fix:** create the scenario fresh yourself (customer → search → booking request → accept → pay) —
  this is the intended way to exercise booking-request and checkout-and-payment anyway.
- **Full reseed:** only against a **local** database — `docker compose down -v && docker compose up -d` under
  `server/`, then boot. **Never drop the shared remote database** casually; it backs the live demo deployment
  and other people's sessions.

---

## 7. Report what you actually saw

Name the account you used, the domain's mock/real status, and the exact response (status code, error
message) rather than "it worked" — the troubleshooting table in
[testing-setup.md](../../../archive/docs/flows/testing-setup.md#troubleshooting) exists because several failure
modes here look identical to an unrelated bug (a rate limit looks like a crash; `/healthz/ready` failing on
Windows looks like the app is down). Check it before filing something as a new defect.
