14 KiB
Flows — what is implemented, and how to test it
One file per user-meaningful journey. Each answers three questions and no others: what it does, what is real versus mocked, and the exact steps to walk it against a running stack.
Start here → testing-setup.md for bring-up, the demo accounts and the OTP. Then pick a row below.
Last verified: 2026-08-02 against commit
c841bde, against a booted stack — API onhttp://localhost:5002against the shared remote SQL Server, client onhttp://localhost:3000(and a production build on:3001), all 8 demo accounts logged in over the real phone-OTP round-trip.
Status table
Client / Server are independent on purpose. A real UI on a mocked service is the trap this atlas
exists to expose — six flows below are exactly that.
| Flow | Actor | Status | Client | Server | Gaps | File |
|---|---|---|---|---|---|---|
| auth-login-otp | all | built | real | real | 15 | → |
| public-front-door | guest | partial | real | real | 10 | → |
| onboarding-customer | customer | partial | real | real | 9 | → |
| care-circle-patients | customer | partial | partial | real | 15 | → |
| addresses-and-map | customer | partial | real | real | 10 | → |
| onboarding-nurse | nurse | partial | partial | real | 11 | → |
| nurse-service-areas | nurse | partial | real | real | 8 | → |
| nurse-verification | nurse + admin | mocked | mock | partial | 12 | → |
| nurse-catalog-and-pricing | nurse | partial | real | real | 9 | → |
| search-and-discovery | customer | partial | partial | real | 14 | → |
| booking-request | customer + nurse | partial | real | real | 12 | → |
| checkout-and-payment | customer | partial | partial | partial | 11 | → |
| bnpl-installments | customer | mocked | mock | partial | 12 | → |
| booking-lifecycle-evv | nurse + customer | partial | real | real | 13 | → |
| cancellation-and-refunds | customer + admin | mocked | mock | partial | 17 | → |
| reviews | customer + admin | partial | real | real | 11 | → |
| patient-care-records | nurse + customer | mocked | mock | real | 12 | → |
| nurse-earnings-and-payouts | nurse + admin | mocked | mock | partial | 18 | → |
| messaging-tickets | all | partial | partial | real | 14 | → |
| notifications | all | partial | real | real | 13 | → |
| admin-backoffice | admin | mocked | partial | partial | 13 | → |
| partner-center | partner | mocked | mock | partial | 12 | → |
| account-and-settings | all | partial | partial | real | 12 | → |
1 built · 15 partial · 7 mocked · 0 not started · 0 UNVERIFIED. 283 gaps recorded — Phase 4's input.
built = end-to-end real and observed working · partial = real end to end with named gaps · mocked =
the UI is real, the data is fake · not started = no implementation. Every row is backed by a code trace,
and every money flow plus auth was additionally walked against the running API.
The six things that surprise everyone
- No seeded admin can reach any admin endpoint. All 16 admin GET operations return
403for both09120000020(super_admin) and09120000021(finance).DynamicPermissionService.CanAccessgrants on the literal role"admin"or a per-controllerDynamicPermissionclaim; the demo admins holdsuper_admin/finance, and no code anywhere writes that claim. The client hides it completely becauseUSE_ADMIN_MOCK = true. → admin-backoffice.md - A
falsemock flag does not mean the domain is honest.paymentis flag-real with only 2 of 6 operations working;searchhardcodesisVerified: trueandnurseGender: 'female';tickets.getUnreadTotalis literallyasync () => null. - A
truemock flag does not mean the real half is missing.verificationhas 10 of 14 operations live and probed,admin9 of 14,payouts5 of 10 — all suppressed by one shared flag. The nurse's verification status answers correctly tocurlright now; nothing in the browser calls it. - The seeded demo world is dated 2026-07-26 and has aged out. No
pendingoracceptedbooking request survived the 60-second expiry job, so the nurse inbox and checkout have nothing seeded to act on. → testing-setup.md - No card payment can complete from a browser.
MockPaymentProviderredirects tohttps://mock-psp.local/pay/…, a host that does not exist. The server money path is correct and idempotent; only the last hop is unreachable. → checkout-and-payment.md - The real BNPL rail is dead on the live stack.
SeedPaymentGatewaysAsyncseeds only aStandardgateway, never aBnplone, soPOST /checkout_bnpl/initiatereturns400 "No active BNPL gateway is configured."for every request — verified live. The UI never notices because it runs on a mock. → bnpl-installments.md
Mock-vs-real map — client
Derived from code across all 22 client/src/services/ domains, 153 seam operations. Real-half verdict
is about the clientApi.ts implementation, independent of the flag: COMPLETE = every op maps a published
route with no fabrication · PARTIAL = ≥1 op fabricates or derives a value · GAPPED = ≥1 op targets a
route that does not exist server-side.
| Domain | Flag | Ops | Real-half verdict | live / proposed / fabricated |
|---|---|---|---|---|
addresses |
real | 5 | COMPLETE | 5 / 0 / 0 |
admin |
mock | 14 | GAPPED | 9 / 5 / 0 |
auth |
real | 6 | COMPLETE | 6 / 0 / 0 |
bnpl |
mock | 7 | GAPPED | 2 / 4 / 1 |
bookingRequests |
real | 6 | COMPLETE | 6 / 0 / 0 |
bookings |
real | 7 | COMPLETE | 7 / 0 / 0 |
catalog |
real | 7 | COMPLETE | 7 / 0 / 0 |
geography |
real | 3 | COMPLETE | 3 / 0 / 0 |
notifications |
real | 4 | COMPLETE | 4 / 0 / 0 |
nurse (bank) |
real | 4 | COMPLETE | 4 / 0 / 0 |
partnerCenter |
mock | 13 | GAPPED | 6 / 7 / 0 |
patientRecords |
mock | 5 | GAPPED | 2 / 3 / 0 |
patients |
real | 5 | COMPLETE | 5 / 0 / 0 |
payment |
real | 6 | GAPPED + PARTIAL — worst real domain | 2 / 2 / 2 |
payouts |
mock | 10 | GAPPED + PARTIAL | 5 / 4 / 1 |
profiles |
real | 6 | COMPLETE | 6 / 0 / 0 |
refunds |
mock | 9 | GAPPED | 2 / 7 / 0 |
reviews |
real | 6 | COMPLETE | 6 / 0 / 0 |
search |
real | 2 | PARTIAL | 2 / 0 / 3 fields |
serviceAreas |
real | 3 | COMPLETE | 3 / 0 / 0 |
tickets |
real | 11 | PARTIAL + GAPPED (gated) | 7 / 3 / 1 |
verification |
mock | 14 | GAPPED + PARTIAL | 10 / 3 / 1 |
Totals: 15 real / 7 mock · ~107 ops map published routes · 34 would 404 if the flag were flipped today · 12 fabricate or client-derive a value.
Three further facts a reader needs:
- 20 of 22 mocks hold mutable module state — a mocked demo that crosses a full page navigation restarts from its seed.
- Two cross-domain mock edges break live paths: the
refundsandbnplmocks read thebookingsmock store, which is seeded only with ids5001-5005, andfindBookingthrows404for anything else. Real booking ids therefore404inside those wizards. - One production seam breach:
nurse/verification/page.tsx:12imports__mockApproveAll/__mockRejectStepfromapis/mockApiunconditionally. The render is gated; the module edge is not, so the mock ships in every build.
Mock-vs-real map — server seams
Exactly one Seams:*:Provider is set anywhere in the repo — Seams:Sms:Provider = telegram. Every
other rail runs on its mock, in development and in the deployed stack: docker-compose.yml overrides only
the relay's BaseUrl and the object-storage RootPath, never a selector.
| Rail | Mock | Real adapter available | Selected today |
|---|---|---|---|
| SMS / OTP | LoggingSmsSender |
KavenegarSmsSender · TelegramSmsSender |
telegram |
| Card PSP | MockPaymentProvider |
ZarinPalPaymentProvider |
mock |
| Settlement split (تسهیم) | MockSettlementSplitProvider |
ProviderSettlementSplitProvider |
mock |
| BNPL | MockBnplProvider |
SnappPayBnplProvider · DigipayBnplProvider |
mock |
| Object storage | LocalDiskObjectStorage |
S3ObjectStorage |
mock |
| Geocoder | MockGeocoder |
NeshanGeocoder |
mock |
| Bank transfer (payouts) | MockBankTransferProvider |
none | mock — moves no money |
| e-invoicing (مودیان) | MockMoadianClient |
MoadianClient |
mock |
| Shahkar · e-KYC · IBAN ownership | mocks | Finnotech adapters | mock |
| Credential (MoH/INO) · eNamad · review moderation | mocks | none — deliberate | mock, always |
| Search | (no mock) | SqlNurseSearch |
real |
Two rails throw at startup instead of falling back to the mock: Seams:Sms:Provider = smsir|ghasedak,
and Search:Backend set to anything but sql. And the PSP selector is not a token match —
ServiceCollectionExtension.cs:174 treats any non-mock string as ZarinPal, so a typo silently selects a
real gateway.
Corrections to mocks-registry.md
archive/build-chain/working-context/reports/mocks-registry.md
was cross-checked row by row against the code: the whole frontend section — 26 rows plus its prose header
— and 17 disagreements were found. The registry now carries a banner pointing here.
The systemic cause: its "Config flag … default true" column was never updated after the
refinement-phase-4 de-mock, although the same file's prose header and Status column were.
| Kind | Count | Detail |
|---|---|---|
| Stale flag default | 8 | patients, profiles, nurse (bank), geography, addresses, serviceAreas, catalog, reviews, notifications rows all still say "default true"; the code says false |
| Actively wrong about behaviour | 3 | profilesClientApi.uploadAvatar "throws 501" — it does a real multipart upload · AddressMapPicker "not a real map, no Neshan tiles, no network" — it renders real Leaflet Neshan tiles when the key is set · the payment row records the flip as clean and never records the 4 broken ops it produced |
| Wrong in both directions at once | 1 | One row's flag column says all-mock and its own Status column says all-real; reality is 2 real / 3 mock |
| Missing entirely | 3 | SearchApi, BookingRequestsApi (the exact store behind the BNPL breakage), and geography/neshan.ts's direct third-party fetch calls |
| Internal contradiction | 1 | The registry says the REQ-027 endpoints exist; patientRecords/apis/clientApi.ts says none exist. Needs a server-side ruling |
| Verified and holding | 15+ | Including all 7 remaining flag values, the EVV GPS default, and the deleted card mock-gateway harness |
Backend seam rows were not re-audited row-by-row; the current server picture is the seam table above.
Coverage accounting
All 14 business areas are covered. Each maps to a primary flow; none is orphaned.
| Area | Primary flow | Area | Primary flow |
|---|---|---|---|
| 01 Actors & Onboarding | auth-login-otp, onboarding-* |
08 Payments & Escrow | checkout-and-payment |
| 02 Nurse Verification | nurse-verification |
09 Installments / BNPL | bnpl-installments |
| 03 Catalog & Pricing | nurse-catalog-and-pricing |
10 Payouts | nurse-earnings-and-payouts |
| 04 Search & Matching | search-and-discovery |
11 Reviews, Trust & Safety | reviews |
| 05 Booking & Scheduling | booking-request, booking-lifecycle-evv |
12 Messaging & Emergencies | messaging-tickets |
| 06 EVV / Service Delivery | booking-lifecycle-evv |
13 Tax, Invoicing & Legal | partner-center ⚠ weakest |
| 07 Cancellation & Refunds | cancellation-and-refunds |
14 Notifications & Admin | notifications, admin-backoffice |
Area 13 is the thin one. Invoicing and VAT are split across checkout-and-payment (the per-booking
invoice) and admin-backoffice (the مودیان reconciliation); no flow owns the legal/tax story end to end.
Phase 4 backlog item.
All 22 client service domains are covered by at least one flow (see the map above). All 83 client routes are reachable from a flow file's Screens table; the 9 routes with no service domain are static pages, and the orphans (nothing links to them) are recorded in the flow that owns their area.
One flow has no product source: account-and-settings is a UI-phase-9 decision with no
product/business/ file behind it. public-front-door's depth decision lives in
product/notes/open-questions.md rather than a business area.
Conventions these files follow
- Every file carries
> Last verified: <date> against <commit>. A file without one is a claim, not a fact. - Every status is backed by a code trace (route → hook → seam → endpoint → controller → handler) with
file:lineevidence. The money flows and auth were additionally walked against the running API. - What could not be checked is written with an explicit
UNVERIFIED:prefix and a reason. - API shapes are not restated here — docs/integration/ owns them, and every flow links to its domain file. Business rules are not restated — product/ owns them; a flow states the number and links to its source.
- Each file stays under 200 lines.
testing-setup.mdis a deliberate exception: it is the single page you hand someone, and splitting it would defeat that.