test(apps): customer-flow smoke test for both apps + 4-digit master-code guard

smoke-apps.cjs walks login (master OTP), catalogue, cart cap, order placement
and order history, with the empty-order and unauthenticated negatives: 43/43.
The cart rules are executed from BOTH apps' own source so the result cannot be
carried by v1 alone.

No behavioural divergence found: all 9 stores are byte-identical, and the router,
API client and composables match except v1's protected variant-image picker.

APP-01: both apps' OTP box is exactly 4 inputs and only submits at 4, so a longer
OTP_MASTER_CODE cannot be typed into them. The login UI was left alone; main.ts
now warns at boot when the code is not 4 digits and .env.example states the rule.

APP-02 logged, not fixed: user-app-v2 has no getHomeSlider, so admin-configured
home slides show in v1 only. Changing v2's hero during a delivery is not worth
the risk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
s.esmaeili
2026-09-23 00:22:50 +03:30
co-authored by Claude Opus 5
parent 0983c42788
commit 8b7da90f51
4 changed files with 370 additions and 1 deletions
+55
View File
@@ -637,3 +637,58 @@ takes `orderId` (not `id`/`sts`). Both corrected.
(1 product + its variants/images, 1 admin user + profile, 2 customers per run).
The order status change is made against an existing order and then **restored to
its original value** in the same run; no pre-existing row is left modified.
## 2026-09-23 — Apps smoke test (D19 step 1)
New `apps/api/scripts/smoke-apps.cjs` — **43/43**. Walks the customer flow the
owner listed: login with the master OTP → see products → add to cart → place an
order → see my orders, plus the negatives (empty order → 400, unauthenticated
order → 401).
Because both apps share one API client, the network flow is exercised once; the
**cart rules are asserted against BOTH apps' real source**, loaded from disk and
executed, so "the apps work" cannot be proved by v1 alone. `capQty` was checked
over five cases (over-stock, under-stock, pre-order, zero, negative) in each app.
**Result: no behavioural divergence between user-app and user-app-v2.**
Confirmed by direct comparison:
| Surface | Result |
|---|---|
| All 9 Pinia stores | **byte-identical** |
| Router (route names) | identical |
| API client call list | identical |
| Composables | identical except `useProduct.image` — v1's variant-image picker, **PROTECTED** |
| Components | none missing from v2; its 4 extra home components are its own design |
The making-charge formula was verified against a real order:
`5.65g +15% = 6.4975` exactly, matching the reference's
`rash × (1 + (wage − discount)/100)`.
### APP-01 (constraint, not a code change) — the master code must be 4 digits
Both apps render the login OTP as **four single-character boxes** and submit
only on `@on-complete`, i.e. at exactly four digits
(`LoginView.vue:43` in each app; `OtpInput.vue:43`). The API accepts any length,
so a longer `OTP_MASTER_CODE` works over HTTP but **cannot be typed into either
app**. The real OTP is 4 digits (`Math.floor(1000 + random*9000)`), so the box
is correct — only the master code has to match it.
The apps were NOT changed (that would touch the shipped login UI for a
delivery-only feature). Instead `main.ts` now checks the code at boot and adds
an explicit warning when it is not 4 digits, and `.env.example` states the rule.
Re-ran the full apps smoke with a 4-digit code: **43/43**.
### APP-02 (logged, not fixed) — v2 does not show the admin's home slider
`user-app` calls `GET /home/slider` and prefers admin-configured slides, falling
back to category-derived ones (`HomeView.vue:258`). `user-app-v2` has no
`getHomeSlider` in its API client at all and always derives its hero from
categories. So slides configured in the admin panel appear in v1 only.
Not fixed tonight: v2's home is its own design (HeroCarousel/ProductPanel), and
wiring a different slide source into it during a delivery risks breaking the
page that is actually being demonstrated. Listed under known issues.
**Data touched:** the script creates one order and deletes it again, with its
items, status events and ledger entries. No pre-existing data is modified.