Merchant Onboarding Seam - Manual Test Plan โ
| Field | Value |
|---|---|
| Branch / PR | feat/admin-venue-creation (session PR #788) |
| Issue | #790 (gap-map Tier-1 #2) |
| Environment | admin portal (dev) -> auth-api Cloud Run (dev); reads/writes merchant_applications in lantern-app-dev |
| Build flags / config | none; requires VITE_AUTH_API_URL set in the admin build |
| Build (commit) | fill after deploy |
| Tester | Mechelle |
| Date | fill on run |
Summary โ
| # | Scenario | Result | Notes |
|---|---|---|---|
| 1 | Pending applications list | [ ] pass [ ] fail [ ] blocked | |
| 2 | Empty state | [ ] pass [ ] fail [ ] blocked | |
| 3 | Review & create prefills the form | [ ] pass [ ] fail [ ] blocked | |
| 4 | Approve provisions + marks approved + links merchant | [ ] pass [ ] fail [ ] blocked | end-to-end |
| 5 | Reject records decision, no account created | [ ] pass [ ] fail [ ] blocked | |
| 6 | WIP label is gone | [ ] pass [ ] fail [ ] blocked | |
| 7 | Non-admin cannot list/approve | [ ] pass [ ] fail [ ] blocked | guardrail |
| 8 | (Post-deploy) auth-api reachable from deployed admin | [ ] pass [ ] fail [ ] blocked | activation |
Server layers already verified (pre-merge) โ
- Unit (mocked Firestore,
adminMerchantApplications.route.test.js): 7 green - list filter/serialize, approve/reject writes + audit, 404, zod guards. - Emulator integration (real Admin SDK vs Firestore emulator,
npm run test:merchant-apps:emulator): 4 green - pending-only default, status=all, approve writes status/reviewer/merchant + audit + drops from pending, 404. - Admin component (
MerchantApplications.test.jsx): 6 green - list, empty, review-navigates-with-prefill, reject-drops-row, confirm-dismiss no-op, load error.
Scenarios below are the browser + data + deployed-config layers.
Setup / preconditions โ
- Sign in to the dev admin portal as an admin.
- Nav: Merchants now expands to All Merchants + Applications (
/admin/merchants/applications). - To create a test application, submit the public merchant apply form (
#/merchantsignup on the web app) with a throwaway email, OR seed amerchant_applicationsdoc in the Firebase console (status: 'pending', fields:applicantName,applicantEmail,businessName,venueAddress,businessType,phoneNumber,createdAt). - Data lives in
lantern-app-dev:merchant_applications,merchants,merchantProfiles,users,adminActions. - Use a throwaway email for approvals - approving provisions a REAL merchant account (creates an Auth user + sends an invite email unless "Send email invite" is unchecked).
Scenarios โ
1. Pending applications list โ
- Steps: Go to Merchants -> Applications.
- Expected: A row per pending application: business, applicant, email, type, venue address, with "Review & create" + "Reject" actions.
- Verify (data): The rows match
merchant_applicationsdocs withstatus: 'pending'.
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:2. Empty state โ
- Steps: With no pending applications (reject/approve all, or a clean project), open Applications.
- Expected: "No pending applications" empty state, no error.
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:3. Review & create prefills the form โ
- Steps: Click "Review & create" on an application.
- Expected: Lands on Create Merchant with email/business/contact/phone prefilled from the application, and a banner noting you are reviewing an application. Notes carry the venue address / type / website / message.
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:4. Approve provisions the account + marks approved + links merchant (end-to-end) โ
- Steps: From the prefilled form (scenario 3), confirm the fields, leave "Send email invite" as desired (uncheck to avoid a real email), click Create Merchant.
- Expected: Merchant account is created; you land on the merchant detail with the created banner. Back on Applications, the application is gone from the pending list.
- Verify (data):
merchants/{merchantId}created (statuspending_setup,ownerUserIds,businessName).- The
merchant_applicationsdoc:status: 'approved',reviewedBy= your uid,reviewedAtset,merchantId+userIdlinked. adminActionshas areviewMerchantApplicationentry (and thecreateMerchantUser/promoteToMerchantentry from provisioning).
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence: (merchantId + application id)
Follow-up:5. Reject records the decision, no account created โ
- Steps: Click "Reject" on an application, confirm the dialog.
- Expected: The row disappears from pending.
- Verify (data): The application doc
status: 'rejected',reviewedByset; NO newmerchants/usersdoc created for it.
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:6. WIP label is gone โ
- Steps: Open Create Merchant directly (Merchants -> All Merchants -> Create), not via an application.
- Expected: No "Work in Progress" notice; the form works as before. (Via an application, the review banner shows instead.)
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:7. Non-admin cannot list or approve (guardrail) โ
- Steps: As a signed-in non-admin (or no auth), call
GET /auth/admin/merchant-applicationsandPOST /auth/admin/merchant-applications/<id>/statuson the dev auth-api (curl / devtools). - Expected:
403 FORBIDDEN(non-admin) or401(no auth); no state change. - Verify: HTTP status; application doc unchanged.
Result: [ ] pass [ ] fail [ ] blocked
Actual:
Evidence:
Follow-up:8. (Post-deploy) auth-api reachable from the deployed admin build โ
- Steps: On the DEPLOYED dev admin portal, open Merchants -> Applications with no local overrides.
- Expected: The list loads (or shows the empty state) rather than an error; the request targets the dev auth-api host.
- Verify: devtools Network shows
GET /auth/admin/merchant-applicationshitting the real auth-api URL.
Result: [ ] pass [ ] fail [ ] blocked
Actual: (deploy run id + commit)
Evidence:
Follow-up:Agent live run (2026-08-04, local stack) โ
Driven headless (Playwright) against a local stack: admin portal (:3001) -> local auth-api (:8084, branch code) -> dev Firebase (Admin SDK via ADC), signed in as Agent Probe admin, App Check bypassed locally.
- Nav + page render: PASS. Merchants expands to All Merchants + Applications; the Applications page renders (header + Refresh).
- BUG CAUGHT + FIXED (this is why the live pass matters): the list endpoint first threw
9 FAILED_PRECONDITION: The query requires an indexon real dev Firestore. The query waswhere('status','==',x).orderBy('createdAt','desc'), which needs a composite index that real Firestore does not have; the emulator auto-creates indexes, so the 4 emulator tests + 7 unit tests all passed while the real endpoint would 500. Fix: dropped the DB-levelorderByand sort the (small) pending queue in memory, so no composite index is needed. After the fix the endpoint returns200with no index error. Re-verified: 7 unit + 4 emulator green post-fix. - List/empty display: the API returns 200 (empty, no pending apps in dev); the screenshot caught the async call mid-load ("Loading applications..."). Endpoint + auth confirmed via logs (
200, Agent Probe authenticated).
Scenarios 3-8 (prefill, approve end-to-end, reject, WIP gone, non-admin, post-deploy) remain for the operator; the list endpoint and nav are confirmed live, and the index bug is fixed.
Notes โ
- Approval is intentionally human-in-the-loop: applications come from a public form, so the server never auto-provisions. The admin reviews, provisions via the proven create path, and the app is marked approved on success.
- Venue association remains a separate step (now unblocked by Theme 1's venue creation): associate a venue to the new merchant so it can create offers (
VENUE_NOT_ASSOCIATEDotherwise).