End-to-end test plan
QA-01 on staging: the fifteen priority scenarios and the 17 October decision scenarios run on the seeded data, step by step per app with the API operations, the cross-app and returns / after-sales runs, the device matrix, the defect scale and daily triage, the hardening checks and the test-data reset.
This page is the plan of QA-01: the whole ecosystem tested end to end on staging with real devices before HOCO's acceptance. The scripts themselves are the acceptance scenarios (objective, requirements, evidence); this page says how the team runs them on staging — which seeded account, product and order each step uses, what is done in which app, which API operations are involved and what must be observed — plus everything the run needs around them: the data reset, the setup after a reset, the device matrix, the defect scale and triage, the hardening report's logged items and the exit report.
| Item | Detail |
|---|---|
| Task | QA-01 — owners Anis (server, back-office, web, triage), Imad (reseller and delegate apps, real devices), Akram (design conformance) |
| Requirement | OPS-05 |
| Environment | Staging, a frozen candidate of every app (environments) |
| Data | Reference and demo seeds of OPS-01, reset before the run with pnpm qa:reset (reset), then the setup steps |
| Out of scope | HOCO's own execution on pre-production and the PV (QA-02); the recette seed with the named stores of the acceptance page (prepared in QA-02) |
App codes: BO back-office, RW reseller web, RA reseller app, DA delegate app, API a direct call (the API reference).
Entry criteria
- SRV-28, MOB-14, QA-03 and WEB-05 are done (the task's dependencies).
- Staging runs one tagged build of the API and worker, the back-office and the reseller web; the reseller and delegate apps are
stagingbuilds from Firebase App Distribution. The build numbers are written at the top of the execution log and in every defect. - The server suite is green on that build, including the cross-app smoke (
pnpm test). - The data was reset the morning of the run (reset) and the setup steps are done.
- Staging sends real pushes (Firebase credentials set) — without them pushes are only logged as
push (simulated)and every push received check fails. SMS stay simulated (D-13): one-time codes are read from the API log linesms (simulated). - The GitHub labels and the QA-01 milestone exist (defects).
Test data
Everything below is created by the reference seed and the demo seed (packages/db/prisma/seed). The demo seed is deterministic: after a reset the same numbers, names and prices come back, and the dated rows (orders, today's tours) are anchored on the reset day in Africa/Algiers. Business numbers carry the year of the reset (CMD-2026-… in 2026).
Passwords are never written here. Staff, delegates and resellers use the demo password documented in apps/server/MODULES.md; the super administrator's password is SEED_ADMIN_PASSWORD of the staging environment (in the team vault), or the one printed once by the first seed when that variable is empty.
Personas
The acceptance scripts were written for the recette seed (Lina, Nadia, Magasin El Wafa…), which QA-02 prepares. On staging the same roles are played by the demo identities:
| Acceptance persona | Staging identity (demo seed) | Sign-in | Role, scope |
|---|---|---|---|
| Lina — super admin | Super administrateur | admin@hoco.local | SUPER_ADMIN |
| Nadia — commercial admin | Nadia Ouali | commercial@hoco.local | COMMERCIAL_ADMIN (orders.validate, CRM, promotions, field) |
| Rachid — stock manager | Hakim Bensalem | stock@hoco.local | STOCK_MANAGER (stock.validate) |
| Amel — finance manager | Samia Kaddour | finance@hoco.local | FINANCE_MANAGER (finance.validate) |
| Sofiane — delivery manager | Rafik Hamdani | livraison@hoco.local | DELIVERY_MANAGER |
| Mourad — direction | Karim Lounis | direction@hoco.local | DIRECTION (read-only) |
| Yacine — delegate, Oran | Yacine Meziane, DLG-0002 | +213555000202 | Zones ORN (wilaya 31) and CST (wilaya 25) |
| Samir — delegate, Alger | Abdelkader Benmoussa, DLG-0001 | +213555000201 | Zone ALG (wilaya 16) |
Magasin El Wafa (DEMI_GROS, Oran, credit) | Galaxy Phone Aïn El Turck, REV-000008 | +213555010008 | DEMI_GROS, wilaya 31, Yacine, credit limit 500 000,00 DA |
Phone Center Bahia (DETAIL, limit 0) | Mega Phone Ouled Rahmoune, REV-000003 | +213555010003 | DETAIL, wilaya 25, Yacine, limit 0 |
Alger Mobile Store (SUPER_GROS, Alger) | Planet Mobile Bouzaréah, REV-000007 | +213555010007 | SUPER_GROS, wilaya 16, Abdelkader, limit 0 |
Bazar El Amel (prospect INTERESTED) | City Mobile Aïn Smara, REV-000030 | — (no account) | INTERESTED, declared DETAIL, wilaya 25, Yacine |
| A sign-up waiting for validation | Smart Accessoires Es Sénia, REV-000017 | +213555010017 | ACCOUNT_CREATED, validation PENDING |
| A suspended reseller | Star Mobile Beni Hamidène, REV-000021 | +213555010021 | SUSPENDED |
| Phones created during the run | Accessoires Nour +213555000204, Téléphonie Belhadj +213555000206, a rejected sign-up +213555000207, Kiosque Test +213555000210 | — | Not in any seed; free after every reset |
Other resellers follow the same pattern: REV-0000NN signs in with +2135550100NN. REV-000001 to REV-000012 are ACTIVE, 013–016 FIRST_ORDER, 017–020 waiting for validation (020 rejected), 021–022 suspended, 023–040 prospects. Customer types rotate SUPER_GROS (wilaya 16) → DEMI_GROS (31) → DETAIL (25). Credit limits: REV-000004, 008, 012 and 016 at 500 000,00 DA, the others 0.
Products
Prices are HT per customer type (super-gros · demi-gros · détail); TTC = HT + 19 % VAT, the reseller sees TTC (D-17). Stock is the physical quantity right after a reset (one warehouse).
| Role in the scripts (acceptance product) | SKU · product | Prices HT (DA) | Stock | Notes |
|---|---|---|---|---|
| The in-stock reference product (HOCO N32 30W) | HOCO-C13-BL · HOCO C13 Chargeur secteur 20 W USB-C | 1 483 · 1 631 · 1 809 | 107 | TTC 1 764,77 · 1 940,89 · 2 152,71 |
| The promoted BOROFONE cable (BX51) | BOROFONE-BX26-1M · BOROFONE BX26 Câble USB-A vers USB-C 3 A | 409 · 450 · 499 | 385 | Category Câbles › USB-C; no sale step in the demo data (min 1, step 1) |
| The HOCO cable outside the promotion (X88) | HOCO-U34-BL · HOCO U34 Câble USB vers Lightning 2,4 A | 347 · 382 · 423 | 282 | |
| The low-stock product (ACEFAST T6, 8 units) | BOROFONE-BU32-BL · BOROFONE BU32 Câble USB vers Lightning 2,4 A | 234 · 257 · 285 | 8 | Below the default threshold 20: a back-office alert only |
| The daily-offer power bank (J101) | HOCO-J55-WH · HOCO J55 Power bank 20 000 mAh 22.5 W | 2 224 · 2 446 · 2 713 | 329 | Daily offer created in setup |
| A large order line (credit tests) | HOCO-J52-BL · HOCO J52 Power bank 20 000 mAh 20 W | 4 387 · 4 826 · 5 352 | 247 | |
| The out-of-stock product (CA75) | HOCO-Y67-WH · HOCO Y67 Montre connectée | 5 683 · 6 251 · 6 933 | 0 | Status OUT_OF_STOCK |
| The segment-only product (BA21) | BOROFONE-BO50-BL · BOROFONE BO50 Casque Bluetooth pliable | 3 775 · 4 153 · 4 606 | 255 | Visible to everyone after a reset; restricted in setup |
The never-visible product (A55, DRAFT) | HOCO-EW49-WH · HOCO EW49 Écouteurs sans fil TWS | — | 515 | DRAFT; ACEFAST-C27-1M is HIDDEN, two products are ARCHIVED |
The catalog holds 60 products (48 ACTIVE) in 8 root categories with 17 attribute definitions (power, ports, connector, current, length, colour, capacity…), so the facets have real values.
Settings, regions, orders and tours
| Item | Seeded value |
|---|---|
| Ordering | Minimum 10 000,00 DA TTC; quantities 1–999, step 1 (D-19); the reseller cancels while SUBMITTED or PENDING; confirmation reminder after 4 h |
| Stock | Reserve at VALIDATED, deduct at SHIPPED (D-22); threshold 20 |
| Credit | BLOCK over the limit (D-04); new resellers at 0 |
| Prices before validation | Shown, declared type (D-18) |
| Assisted orders | 72 h to confirm (D-23) |
| Returns, after-sales | Returns 7 days after delivery; claims 180 days (D-35) |
| Field | GPS tolerance 150 m, accuracy ≤ 100 m, out-of-tolerance check-in refused; positions every 300 s or 200 m; report required to end a visit; unlock 24 h; closing requires sync |
| Support regions | Centre (wilaya 16 …) +213000000002, Est (25 …) +213000000003, Ouest (31 …) +213000000004, Sud +213000000005, default National +213000000001 — placeholder numbers until D-36 |
| App versions | Minimum and latest 1.0.0 for every app and platform |
| Not seeded | Promotions, banners, segments, visibility rules, invitations, carriers — created in setup or during the scenarios |
Seeded orders (each status of the lifecycle):
| Order | Status | Channel | Reseller | Used by |
|---|---|---|---|---|
CMD-2026-000017 · 000018 | SUBMITTED | App · Web | REV-000002 · REV-000003 | The queue is not empty at the start (AT-30, AT-42) |
CMD-2026-000019 | SUBMITTED, assisted, confirmed by the reseller, first order | Assisted | REV-000013 | First-order flag on BO-41 |
CMD-2026-000012 | PENDING (credit) | App, credit | REV-000004 | AT-17 on-hold row |
CMD-2026-000013 | PENDING, AWAITING_RESELLER | Assisted | REV-000005 | AT-18 (one assisted order may wait per reseller) |
CMD-2026-000014 · 000015 · 000011 | VALIDATED | App · Back-office · App | REV-000006 · REV-000007 · REV-000014 | AT-35 cancellation of a confirmed order |
CMD-2026-000016 · 000010 · 000008 · 000009 | PREPARING · READY · SHIPPED · SHIPPED | Delivery board | ||
CMD-2026-000004 · 000002 · 000003 | DELIVERED 6, 10 and 8 days before the reset | REV-000016 · REV-000004 · REV-000012 | 000003 for the return-window refusal and the open claim | |
CMD-2026-000001 | RETURNED (RET-2026-000001 refunded, credit note AVO-2026-000001) | REV-000001 | Finance views | |
CMD-2026-000007 · 000005 · 000006 | REFUSED · CANCELLED (assisted expired) · CANCELLED | History |
Open after-sales claim TKT-2026-000001 on CMD-2026-000003; invoices FAC-2026-000001…000006 (three unpaid).
Today's tours (the day of the reset): TRN-2026-000001 Abdelkader, IN_PROGRESS, 5 visits; TRN-2026-000002 Yacine, TO_START, 4 visits — REV-000002 (Suivi), REV-000024 (Prospection), REV-000003 (Recouvrement), REV-000026 (Prospection). Seeded positions are random points around the wilaya seat: on-site GPS steps need the setup step S-04.
Resetting the test data
pnpm qa:reset (apps/server/scripts/reset-test-data.ts) brings an environment back to the seeded state above.
| Step | What it does |
|---|---|
| 1. Guard | Refuses, listing every reason, unless all of these hold: NODE_ENV and APP_ENV are not production; the database name contains staging, test or local as a word (hoco_staging, hoco-test); neither the database name nor its host mentions prod; HOCO_RESET_CONFIRM equals the database name; REDIS_URL names a database number (redis://redis:6379/3). With --buckets, both bucket names must carry a marker too. The rules are unit-tested (apps/server/src/testing/reset-guard.test.ts). |
| 2. Database | Default: prisma migrate deploy (pending migrations), then one TRUNCATE … RESTART IDENTITY CASCADE of every table except _prisma_migrations — works with an application role that does not own the schema. --migrate-reset: prisma migrate reset --force instead (drops and recreates the schema; needs the schema owner). |
| 3. Seeds | The reference seed, then the demo seed (--no-demo for reference data only). |
| 4. Redis | FLUSHDB on the database of REDIS_URL only — rate limits, caches, the SSE replay buffers and the BullMQ queues of HOCO; never FLUSHALL. --skip-redis keeps it. |
| 5. Buckets | Only with --buckets: empties S3_BUCKET_PUBLIC and S3_BUCKET_PRIVATE (uploaded photos, documents, generated PDFs). Without it, files stay in the bucket but no row points to them any more. |
# on the staging host, api and worker stopped, with the staging environment loaded
HOCO_RESET_CONFIRM=hoco_staging pnpm qa:reset # truncate, reseed, flush Redis db
HOCO_RESET_CONFIRM=hoco_staging pnpm qa:reset --buckets # also empty the staging bucketsRules:
- Stop the api and the worker first and restart them after: open connections hold locks during the truncation, and a running worker would refill Redis or dispatch events of rows that no longer exist. Mobile testers sign in again after a reset (sessions are gone; the delegate app's offline store must be empty — finish or discard pending items first).
- Reset the morning of a run day: the demo seed creates that day's tours and dates the orders from it.
- Set
SEED_ADMIN_PASSWORDin the staging environment, otherwise the super administrator gets a new random password printed once. - The script is not meant for pre-production (its name contains
prodand is refused): QA-02 loads HOCO's real catalog there. - Measured on the development machine against a scratch database: about 25 s for truncation, both seeds and the Redis flush; re-running gives identical counts and numbers (20 orders up to
CMD-2026-000019, 40 resellers up toREV-000040, 60 products, 30 users).
Setup after a reset
Done once per reset, by the person named, before the scenarios that need it. Each is also a check of the back-office screen it uses.
| # | Who (app) | Action | API | For |
|---|---|---|---|---|
| S-01 | Nadia (BO-50) | Promotion « -15 % sur les câbles BOROFONE »: PERCENT_OFF 15 %, scope category Câbles + brand BOROFONE, audience wilaya 31, from D 00:00 to D+7 23:59, cap 200 units; activate | admin.promotions.crud, admin.promotions.setStatus | AT-03, AT-06, AT-39 |
| S-02 | Nadia (BO-50) | Daily offer on HOCO-J55-WH for D: FIXED_PRICE per customer type, entered HT like every promotional price (« Prix promotionnel (HT) »): e.g. 2 000 · 2 200 · 2 400,00 DA HT, below each type's price (demi-gros 2 200,00 HT = 2 618,00 DA TTC instead of 2 910,74). A fixed price above the type's price never applies (BR-65) | Same | AT-06 |
| S-03 | Nadia (BO-11), Hakim (BO-28) | Segment Partenaires with REV-000008 and REV-000007; BOROFONE-BO50-BL visible to that segment only | admin.segments.crud, admin.products.visibility | AT-02, AT-07a |
| S-04 | Nadia (BO-10, BO-23) | Move the four stops of TRN-2026-000002 to real points around the test site (≥ 400 m apart), so Yacine's tester can stand inside and outside the 150 m tolerance | admin.resellers.update, admin.tours.setStops | AT-08, AT-09, AT-15 |
| S-05 | Samia (BO-46) | Lower REV-000008's credit limit to 50 000,00 DA | admin.credit.update | AT-17 |
| S-06 | Lina (BO-66) | Check the app-version policies against the installed staging builds (minimum ≤ installed) | admin.appVersions.crud | Every app scenario; AT-28 changes them |
Run order
Several scripts feed each other (AT-10 → AT-16, AT-11 → AT-12, the order of X-1 → AT-05 → the returns run). One campaign day per block; field blocks run on site.
| Block | Scenarios | Owner | Where |
|---|---|---|---|
| 1. Access and catalog | AT-01, AT-02, AT-38, AT-39, AT-40, AT-41, AT-14 | Anis (BO, RW), Imad (RA) | Office |
| 2. Ordering and live status | X-1, AT-03, AT-04, AT-42, AT-43, AT-05, AT-35, AT-17, AT-20 | Anis, Imad | Office |
| 3. Promotions, support | AT-06, AT-07a, AT-44 | Anis, Imad | Office |
| 4. Field (on site) | AT-08, AT-09, AT-10, AT-13, AT-15, AT-24, AT-11, AT-12, X-2 | Imad (DA, RA), Anis (BO) | On site in Oran or the test site of S-04 |
| 5. Returns and after-sales | R-1, R-2 (AT-46, AT-45) | Anis, Imad | Office |
| 6. Regression | Every step linked to a fixed defect, then the automated checks | Anis, Imad | — |
Akram reviews the four interfaces against the design handoff during blocks 1–5 and logs conformance issues with the same severity scale.
Priority scenarios on staging
Each scenario keeps the acceptance page's numbering and evidence; the tables below bind it to the staging data and the API. D is the run day.
AT-01 — Reseller account: sign-up with customer type, validation
| Item | Detail |
|---|---|
| Preconditions | No record with +213555000206 or +213555000207 (true after a reset); Nadia signed in on BO-13 |
| Data | New store Téléphonie Belhadj, wilaya 31 Oran, commune Oran, type Détail; queue already holds REV-000017–000019 |
| API | auth.otp.send, auth.otp.verify, auth.signup.reseller, cart.items.add, admin.resellers.validation.list, admin.resellers.validate, admin.events.stream |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | « Créer un compte », 0555 00 02 06, no invitation code | Code sent (read it in the API log, sms (simulated)); 60-second resend countdown |
| 2 | RA | Wrong code, then the right one | « Code incorrect » with 4 attempts left; then the store form |
| 3 | RA | Fill the store, type Détail, password; submit | PENDING_VALIDATION, « Compte en attente de validation » |
| 4 | RA | Open HOCO-C13-BL, « Ajouter au panier » | Price 2 152,71 DA (détail, D-18 default); the line is added but the cart shows the blocking issue ACCOUNT_PENDING_VALIDATION and checkout answers 403 ACCOUNT_PENDING_VALIDATION (cart rule 5) |
| 5 | BO | BO-13 open, no reload | The account appears within 5 s, source SELF_SIGNUP, declared Détail |
| 6 | BO | Nadia approves with type Détail | ACTIVE; journal VALIDATE with Nadia and the type |
| 7 | RA | — | Push « Compte validé » (SMS in the API log); the cart accepts HOCO-C13-BL |
| 8 | RA + BO | Repeat 1–3 with +213555000207 on the reseller web (RW-01); Nadia rejects with a reason | « Compte non validé » with the reason on RW-01 and in the app if signed in there |
AT-02 — Catalog: only authorised products, prices and availability
| Item | Detail |
|---|---|
| Preconditions | Setup S-03 done; REV-000003 (détail) on RA, REV-000008 (demi-gros, segment Partenaires) on RW |
| API | catalog.products.list, catalog.products.get, admin.products.setStatus, cart.get |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000003 opens the catalog | The 48 ACTIVE and 3 OUT_OF_STOCK products less BOROFONE-BO50-BL; no DRAFT, HIDDEN or ARCHIVED product (HOCO-EW49-WH, ACEFAST-C27-1M absent) |
| 2 | RA | Opens HOCO-C13-BL | 2 152,71 DA TTC, a recommended price, « En stock » — no quantity anywhere |
| 3 | RA | Opens BOROFONE-BU32-BL (8 units) | « En stock » only |
| 4 | RA | Opens HOCO-Y67-WH | « En rupture »; add to cart disabled |
| 5 | RW | REV-000008 opens HOCO-C13-BL, then the catalog | 1 940,89 DA TTC; BOROFONE-BO50-BL listed |
| 6 | BO + RA/RW | Hakim hides BOROFONE-BX26-1M after both added it to their cart | Gone from both catalogs after a refresh; the cart line shows « Produit indisponible » |
| 7 | API | GET /v1/catalog/products/{id of BOROFONE-BO50-BL} with REV-000003's token | 404 NOT_FOUND |
Restore BOROFONE-BX26-1M to ACTIVE after step 6.
AT-03 — Order: unique number, visible in the back-office, confirmed there
| Item | Detail |
|---|---|
| Preconditions | Setup S-01; REV-000008 on RA; Nadia on BO-41 (queue already shows CMD-2026-000017–000019) |
| API | cart.items.add, cart.get, cart.checkoutOptions, cart.checkout, admin.orders.toConfirm, admin.orders.validate, events.stream |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | HOCO-C13-BL × 3 | Cart 5 822,67 DA TTC; checkout blocked: « Le montant minimum de commande est de 10 000,00 DA » (ORDER_MIN_AMOUNT_NOT_MET) |
| 2 | RA | HOCO-C13-BL × 6 and BOROFONE-BX26-1M × 10 | Demi-gros prices; -15 % on the BOROFONE cable only; HT, VAT, TTC totals; free delivery (D-20) |
| 3 | RA | Default address, Paiement à la livraison, « Valider la commande » | A CMD-2026-NNNNNN number (the sequence continues after the seeded 000019); status « Envoyée » |
| 4 | BO | BO-41, no reload | Row within 5 s, channel RESELLER_APP, controls green |
| 5 | BO | Wait two minutes | Still SUBMITTED |
| 6 | BO | Nadia confirms | VALIDATED; the row leaves the queue; the app shows « Validée » live |
| 7 | RA | Second order (HOCO-U34-BL × 30) | The next number of the sequence |
| 8 | RW | REV-000008 on the web | Cart empty (server-side cart); both orders in RW-08 |
AT-04 — Stock: reservation, deduction, automatic restock
| Item | Detail |
|---|---|
| Preconditions | HOCO-C13-BL: physical 107, reserved 0 after a reset (less what AT-03 reserved: note the values first); Hakim on BO-33 / BO-34 |
| API | admin.stock.levels, admin.stock.movements, admin.orders.validate, admin.orders.transition, admin.deliveries.update, admin.orders.cancel |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000008 orders HOCO-C13-BL × 10 | Reserved unchanged (reservation at confirmation) |
| 2 | BO | Nadia confirms | Reserved +10, available −10; RESERVATION movement with the order number |
| 3 | BO | Rafik: En préparation, Prête (packages) | Quantities unchanged; delivery note BL-… |
| 4 | BO | Rafik sets own fleet, driver name and phone, ships | Physical −10, reserved −10; SHIPMENT movement |
| 5 | BO | A second order × 5 confirmed, then cancelled by Nadia with a reason | Reserved back automatically; RESERVATION_RELEASE |
| 6 | RA | Throughout | Only « En stock » |
AT-05 — Tracking: back-office changes live in the app and notified
Run on the order of X-1 (confirmed, still VALIDATED). Data: REV-000008; the app closed on one phone, RW-09 open on the order. API: admin.orders.transition, admin.deliveries.proof, orders.get, orders.document, notifications.list, notifications.markRead, events.stream.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | BO | Rafik: En préparation | RW-09 updates within 5 s without reload; push on the closed app's phone; dated inbox entry |
| 2 | BO | Prête, then Expédiée (own fleet, driver, phone) | Same three effects each time; tracker steps per the reseller view |
| 3 | BO | Proof of delivery (recipient, photo) | « Livrée » live and notified; invoice FAC-… downloadable from RA-22 / RW-09 through a short-lived link (worker must run) |
| 4 | RA + BO | Compare the RA-22 timeline with BO-39 | Same statuses and times (Africa/Algiers) |
| 5 | RA + RW | Open the push, then the web inbox | Read on both; badge decremented |
AT-06 — Promotion: only authorised products, dates and targets
| Item | Detail |
|---|---|
| Preconditions | S-01, S-02 |
| API | promotions.list, cart.get, admin.promotions.crud, admin.promotions.preview |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000008 (wilaya 31): BOROFONE-BX26-1M × 20, HOCO-U34-BL × 10 | -15 % on the BOROFONE cable only |
| 2 | RW | REV-000007 (wilaya 16): BOROFONE-BX26-1M × 20 | No discount; promotion absent from RW-10 |
| 3 | BO | Nadia schedules a second promotion from D+1 | Programmée, not applied on D |
| 4 | BO + RA | A second promotion on HOCO-U34-BL with a cap of 5 units (an active promotion's cap can only be raised); REV-000008 checks out 20 | 5 units discounted, the promotion ends (ENDED, 5/5); later carts show « Offre terminée » |
| 5 | RA | HOCO-J55-WH on D | Daily-offer price with the countdown banner |
| 6 | BO | The submitted order after the promotion ended | Lines keep the label and discount |
AT-07a — V1 variant: promotions only, no loyalty surface
AT-07 (loyalty) is V2 (D-16) and not run. Data: S-03's segment. API: catalog.home, promotions.list, me.get.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA, RW | Home, account, promotions | No points, level or rewards; no RW-15 entry |
| 2 | BO + RA | Promotion for segment Partenaires on HOCO-C13-BL | REV-000008 gets it; REV-000003 does not |
| 3 | RA | Two promotions on the same product | Best discount only (PRM-03); the cart names it |
| 4 | BO | Order detail, promotion report | The promotion per line |
AT-08 — Tour: start, visits, closing with full summary
| Item | Detail |
|---|---|
| Preconditions | Reset on D; S-04 done; Yacine (+213555000202) on site with the DA; Nadia on BO-06 |
| Data | TRN-2026-000002, TO_START, stops REV-000002 (Suivi), REV-000024 (Prospection), REV-000003 (Recouvrement), REV-000026 (Prospection) |
| API | delegate.tours.today, delegate.tours.start, delegate.tours.reorder, delegate.visits.arrive, delegate.visits.checklist, delegate.visits.addPhoto, delegate.visits.report, delegate.visits.end, delegate.visits.notDone, delegate.tours.closingPreview, delegate.tours.close, admin.live.delegates, admin.tours.get |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | DA-05 | Today's tour, 4 visits; « Démarrer la tournée » enabled |
| 2 | DA + BO | Start | IN_PROGRESS; Yacine on BO-06 within 5 s |
| 3 | DA + BO | Swap stops 2 and 4 | Accepted (RECOMMENDED); new order on BO-23 |
| 4 | DA | REV-000002: arrive, start, checklist, note, photo, report, end | DONE, locked |
| 5 | DA | A Prospection stop: end without the façade photo | Refused with the missing items; DONE after photo and report |
| 6 | DA | REV-000003: « Visite non réalisée », Magasin fermé | NOT_DONE with the reason |
| 7 | DA | Last stop done; closing preview | Done / not done counts, orders, revenue, kilometres; no blocking item |
| 8 | DA + BO | Close with a comment | COMPLETED; BO-23 full summary; performance updated |
AT-09 — GPS: check-in tolerance, consented positions only
Same tour and preconditions as AT-08 (run it on the first stop before AT-08 step 4), on a fresh install of the DA. API: delegate.visits.arrive, sync.locationPings, delegate.tours.suspend, delegate.tours.resume, admin.tours.track, admin.visits.get.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | First sign-in | DA-04 consent text; system prompt; consent recorded |
| 2 | DA | About 400 m from the stop, « Arrivé » | Refused, distance shown; nothing recorded |
| 3 | DA | Indoors, accuracy above 100 m | « Précision insuffisante » |
| 4 | DA + BO | About 80 m away, good accuracy | Accepted; BO-25 shows position, accuracy, distance, time |
| 5 | DA + BO | Screen off 15 min while moving | A position about every 5 min or 200 m on the BO-23 trail; « Tournée en cours » notification |
| 6 | DA | Suspend 15 min, resume | No position during the suspension |
| 7 | DA | After closing (AT-08) | No position; no location indicator |
| 8 | DA | Mock-location app, « Arrivé » | GPS_MOCK_LOCATION |
AT-10 — Prospect: field record in the CRM without duplicate
Data: Accessoires Nour +213555000204 and Kiosque Test +213555000210 (free after a reset), wilaya 31; Abdelkader (zone ALG) checks across zones. API: delegate.resellers.checkPhone, delegate.resellers.create, delegate.resellers.addPhoto, sync.batch, admin.resellers.create, admin.resellers.checkPhone.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA + BO | Yacine creates Accessoires Nour with GPS and façade photo | A new REV- number; on BO-09 with source DELEGATE_APP, delegate Yacine, position on BO-10, photo in documents |
| 2 | DA | Nour Accessoires with +213 555 00 02 04 | Duplicate detected before saving |
| 3 | DA | Airplane mode: Kiosque Test; reconnect; force a second replay | One record |
| 4 | BO | Nadia creates 0555000204 | 409 PHONE_ALREADY_EXISTS with a link |
| 5 | DA | Abdelkader checks 0555000204 | « Numéro déjà enregistré », no store details |
AT-11 — Invitation: QR and link attribute to the right delegate
Data: prospect REV-000030 (City Mobile Aïn Smara, INTERESTED, Yacine); a test phone without the RA. Its phone +213555010030 has no account. API: delegate.invitations.create, delegate.invitations.markShared, invitations.landing, invitations.resolve, invitations.attribute, auth.signup.reseller, admin.invitations.list.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | DA-16 for REV-000030 | Code XXXX-XXXX, link on the staging web host /i/…, QR |
| 2 | DA | Share by WhatsApp | Prefilled French text with the link; step Invitation envoyée |
| 3 | RA | Scan the QR on the test phone | Play Store page (or Firebase App Distribution for the staging build — note which) |
| 4 | RA + DA | Install, open | Code prefilled; DA-16 Application téléchargée within a minute |
| 5 | RA + DA | Sign up with +213555010030 | Same REV-000030 claimed; Yacine pushed |
| 6 | RA | Browse | Catalogue consulté |
| 7 | BO | Invitation funnel | Attributed to Yacine |
AT-12 — First order: linked to the reseller and the source delegate
After AT-11, Nadia validates REV-000030 (BO-13). Then run X-2 with REV-000030 instead of REV-000008, and check in addition: the order is flagged Première commande on BO-39, REV-000030 becomes FIRST_ORDER, and the reports first orders by delegate, Yacine's performance and the invitation funnel each count +1. API in addition to X-2: admin.reports.run, admin.delegates.performance.
AT-13 — Visit report: saved, visible, triggers the follow-up
During AT-08 at REV-000002. API: delegate.visits.report, admin.resellers.timeline, admin.tasks.list, delegate.tasks.list, delegate.tasks.complete.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | Report: Gérant, interest Élevé, products HOCO-C13-BL and HOCO-J55-WH, the S-01 promotion, issue Prix concurrent, next action Relance téléphonique at D+2 | Saved; the visit can end |
| 2 | BO | BO-11 of REV-000002 | The report on the timeline with every field |
| 3 | BO + DA | BO-14, DA-19 | Task Relance téléphonique due D+2, Yacine |
| 4 | DA | Due date set to D for the test; 08:00 digest (or the job run by hand) | Push « Vos relances du jour » |
| 5 | DA + BO | Complete with an outcome | DONE; interaction on the timeline |
AT-14 — Permissions: no access to unauthorised functions
API: admin.payments.list, admin.orders.validate, admin.products.get, admin.roles.updatePermissions, admin.audit.list, delegate.home.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | BO | Hakim signs in; types the finance URL | His modules only; « Accès refusé » |
| 2 | API | Hakim's session on GET /v1/admin/payments | 403 PERMISSION_DENIED |
| 3 | BO + API | Karim (direction) on CMD-2026-000017 | No confirm button; POST /v1/admin/orders/{id}/validate → 403 |
| 4 | BO + API | Rafik opens HOCO-C13-BL | No purchase price in the screen or the response |
| 5 | BO | Lina removes orders.validate from COMMERCIAL_ADMIN | Nadia's button gone after refresh; API refuses; journal PERMISSION_CHANGE; restore it |
| 6 | API | Yacine's token on /v1/admin/orders; REV-000008's on /v1/delegate/home | 403 |
| 7 | — | Attach the permission-sweep output of the tested build (apps/server/src/testing/permission-sweep.test.ts) | Every route declares a rule; zero unexpected allow |
AT-15 — Offline: pending field data syncs when the network returns
On site, during AT-08 (a stop not yet visited) — the QA-01 acceptance « a delegate tour done fully offline syncs without duplicates or lost reports » (SYN-04, SYN-05). API: sync.batch, sync.delta, files.createUpload, files.completeUpload, admin.visits.get.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | Tour started online, then airplane mode | Tour, visits and points of sale available |
| 2 | DA | Full visit (GPS check-in, checklist, note, two photos, report, end); create a prospect | Saved locally; DA-27 pending count |
| 3 | DA | Kill and reopen the app | Pending items kept |
| 4 | DA | Network back | Replay in order; « Synchronisé » within a minute; photos uploaded |
| 5 | BO | Visit record | occurredAt (device) and a later receivedAt; no duplicate |
| 6 | DA | Cut the network mid-sync, restore | No duplicate; queue resumes |
| 7 | DA + BO | While offline, Nadia edits the same prospect; Yacine too | Conflict in DA-27 with an explanation |
Then AT-24 on the same tour: one stop left without a status while offline blocks the closing (« 1 élément non synchronisé »), the API answers 409 TOUR_CLOSING_BLOCKED, and closing works after the reason and the sync.
Decision scenarios (17 October 2026)
The team decisions the QA-01 task lists, with the scenario that proves each. AT-15 / AT-24 (offline tour) are run with the field block above; AT-45 and AT-46 are the returns and after-sales run.
| Decision | Scenario |
|---|---|
| Guest browsing without prices | AT-38 |
| Three customer-type prices | AT-39 |
| Advanced filters with facets | AT-40 |
| Availability-only stock | AT-41 |
| Manual confirmation of every order | AT-42 |
| Live status for the reseller (SSE + push) | AT-43, X-1 |
| Regional WhatsApp routing | AT-44 |
| Cancellations restock automatically | AT-35 |
| Returns with automatic restock and credit note | R-1 (AT-46) |
| After-sales claims | R-2 (AT-45) |
| Delegate's offline tour with later sync | AT-15, AT-24 |
AT-38 — Guest browsing without prices
API: catalog.products.list, catalog.products.get, catalog.facets, cart.items.add (no session).
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA, RW | Fresh install → « Découvrir le catalogue »; RW without signing in | Home, catalog, product pages, promotions open |
| 2 | RA, RW | HOCO-C13-BL | No price; lock « Créez un compte pour voir les prix » |
| 3 | RA, RW | « Ajouter au panier » | Sign-up prompt; nothing added (401 on the cart) |
| 4 | RA, RW | Filters | No price range; the other filters work |
| 5 | API | GET /v1/catalog/products and /v1/catalog/facets without a session | price: null, priceLocked: true, availability only; no price facet |
| 6 | RA | Sign up from the lock (a free test phone, e.g. +213555000211) | Prices of the declared type right away (D-18) |
AT-39 — Correct price per customer type
Data: REV-000007 (super-gros), REV-000008 (demi-gros), REV-000003 (détail). API: catalog.products.get, cart.items.add, admin.resellers.update, profile.update.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA, RW | The three open HOCO-C13-BL | 1 764,77 · 1 940,89 · 2 152,71 DA TTC |
| 2 | RA, RW | Each adds 10 | Line totals 17 647,70 · 19 408,90 · 21 527,10 DA TTC; HT and VAT consistent on order and invoice |
| 3 | RA | REV-000008 adds BOROFONE-BX26-1M (S-01) | -15 % on top of the demi-gros price; REV-000003 (wilaya 25) gets no discount |
| 4 | BO + RA | Nadia changes REV-000003 to Demi-gros | Journal entry; « Vos tarifs ont changé »; cart re-priced at the next read; submitted orders unchanged |
| 5 | RA | REV-000003 edits the type in the profile | Not editable — « Contactez HOCO » |
Restore REV-000003 to Détail afterwards.
AT-40 — Advanced filters with facets
API: catalog.facets, catalog.products.list, admin.attributes.crud, admin.variants.crud.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | Category Câbles | Sub-categories (USB-C, Lightning, Micro-USB, 3-en-1), brands and attributes with counts |
| 2 | RA | Connector USB-C + length 1 m + brand BOROFONE | BOROFONE-BX26-1M among the results; counts of the other values updated |
| 3 | RA | En promotion + a price range (signed in) | Results and counts consistent; the range uses the user's prices |
| 4 | RA | Facet count vs number of results | Equal for every value tried |
| 5 | BO + RA | Hakim adds a length value (e.g. 1,5 m) and sets it on a variant (BO-71) | The value appears with its count |
| 6 | RW | Same filters in the sidebar (RW-03) | Same results |
AT-41 — Stock quantities never shown to resellers
Data: BOROFONE-BU32-BL (8) and HOCO-Y67-WH (0). API: every reseller and delegate read used in blocks 1–4; the exposure sweep of the tested build.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA, RW, DA | Catalog, product, cart, order and promotion screens; DA-17 assisted order | « En stock » / « En rupture » only; never a number or « Plus que … » |
| 2 | RA | BOROFONE-BU32-BL × 12 | 422 STOCK_INSUFFICIENT with availability only; the message does not reveal 8 |
| 3 | API | Inspect the JSON of those operations (browser dev tools on RW; a proxy for the apps) | No stock-quantity field |
| 4 | BO | Hakim (BO-33) | Physical, reserved, available visible |
AT-42 — Every order needs a back-office confirmation
API: cart.checkout, delegate.assisted.submit, orders.confirmAssisted, admin.orders.create, admin.orders.toConfirm, admin.orders.transition, admin.orders.validate.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA, RW, DA + RA, BO | One order per channel for REV-000008: app, web, assisted (confirmed on RA), back-office on behalf (Nadia) | All four in BO-41 as SUBMITTED (or PENDING on hold), oldest first, with their controls, beside the seeded CMD-2026-000017–000019 |
| 2 | BO | Wait 10 minutes | None leaves the queue alone |
| 3 | BO + API | Rafik moves one to En préparation | Not offered; the API answers 409 ORDER_TRANSITION_NOT_ALLOWED (or 403) |
| 4 | BO | Nadia confirms each | VALIDATED, reservation made, on the delivery board (BO-42) |
| 5 | BO | One left past 4 h (orders.confirmationReminderHours) | Reminder « Commandes en attente de confirmation » to the confirmers |
AT-43 — Live order status on the reseller screen
Run with X-1 and AT-05. API: events.stream, orders.get, orders.list.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | RA-22 open; Nadia confirms | Status changes within 5 s, no pull-to-refresh |
| 2 | RW | RW-09 and RW-08 open | Both update live |
| 3 | RA, RW | Each later status | Live and push and inbox entry |
| 4 | RA | Airplane mode 1 min, status changed, network back | Current status on reconnection (replay with Last-Event-ID) |
| 5 | RA | App in background during a change | Push; screen current on opening |
AT-44 — Regional WhatsApp number
API: support.whatsapp, admin.supportRegions.crud.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000008 (wilaya 31) taps WhatsApp | Ouest +213000000004, prefilled text with the store name and REV-000008 |
| 2 | RW | REV-000007 (wilaya 16) | Centre +213000000002 |
| 3 | RA | Guest with location allowed (in Oran) | Ouest; the position is not stored (API log and database) |
| 4 | RA, RW | Guest, location refused | Wilaya picker; Alger → Centre; remembered |
| 5 | RA | Picker dismissed | National +213000000001 |
| 6 | BO + RA | Nadia moves wilaya 31 to Centre (BO-70) | REV-000008's button uses +213000000002 at once; move it back |
| 7 | RA | Outside the region's hours | Hours shown; WhatsApp still opens |
AT-35 — Cancellation rules and automatic restock
Data: a fresh SUBMITTED order of REV-000008; seeded CMD-2026-000014 (VALIDATED, REV-000006) and CMD-2026-000008 (SHIPPED). API: orders.cancel, admin.orders.cancel, admin.stock.movements.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | Cancel the SUBMITTED order with a reason | CANCELLED; staff notified; no stock movement |
| 2 | RA + API | REV-000006 on CMD-2026-000014 | No cancel button; 409 ORDER_TRANSITION_NOT_ALLOWED |
| 3 | BO | Nadia cancels CMD-2026-000014 with a reason | Reserved quantities released automatically (RESERVATION_RELEASE); REV-000006 notified live and by push |
| 4 | BO | Cancel CMD-2026-000008 | Refused — a return only |
Complementary scenarios in the run
Two complementary scripts run in block 2 because the order controls they exercise sit on every order path.
AT-17 — Credit check blocking and approval
Data: S-05 (REV-000008 limit 50 000,00 DA, nothing open); seeded CMD-2026-000012 (REV-000004, PENDING on credit). API: cart.checkoutOptions, cart.checkout, admin.settings.update, admin.orders.validate, admin.credit.get.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000008: HOCO-J52-BL × 11 (63 172,34 DA TTC), Crédit | Refused, « Crédit disponible : 50 000,00 DA »; cash on delivery works |
| 2 | BO + RA | Lina sets credit.overLimitPolicy to REQUIRE_APPROVAL; same checkout | Order PENDING with the credit control failed, in BO-41 |
| 3 | BO | Samia (finance, no orders.validate) opens BO-41 | Sees the orders pending only for credit (this one and CMD-2026-000012), confirms with an override and a reason |
| 4 | BO | Journal | OVERRIDE with Samia and the reason |
| 5 | RA | REV-000003 (limit 0) opens the payment methods | Crédit not offered |
Restore BLOCK afterwards.
AT-20 — Stock override with permission
Data: BOROFONE-BU32-BL (8 units). API: cart.items.add, admin.orders.create, admin.orders.validate, admin.stock.levels.
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | REV-000008 adds × 12 | 422 STOCK_INSUFFICIENT |
| 2 | BO | Nadia creates the same order on behalf of REV-000008 | Created; stock control failed, PENDING |
| 3 | BO | Nadia (no stock.validate) confirms | Refused with the stock message |
| 4 | BO | Hakim (stock.validate) confirms with an override and a reason | VALIDATED; available −4 in red on BO-33; journal OVERRIDE |
Cancel that order afterwards (the reservation is released).
Cross-app checks
The two paths the QA-01 task names. Their API path is also automated (below); on staging they are run by hand across the real apps.
X-1 — Order from the app, confirmed in the back-office, live on the app and the web
| Item | Detail |
|---|---|
| Preconditions | REV-000008 signed in on a phone (RA, RA-22 later) and in a browser (RW, RW-09); Nadia on BO-41; the worker runs (it dispatches the outbox to the streams and pushes) |
| API | cart.checkout → admin.events.stream (order.to_confirm) → admin.orders.toConfirm → admin.orders.validate → events.stream (order.status_changed) on both reseller sessions → orders.get |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | HOCO-C13-BL × 6, checkout, cash on delivery | SUBMITTED, channel RESELLER_APP, « Envoyée »; the app stays on RA-22 |
| 2 | RW | Open the same order on RW-09 | « Envoyée », awaiting HOCO's confirmation |
| 3 | BO | BO-41 open | Row within 5 s, highlighted, as a new order (order.to_confirm, kind NEW) |
| 4 | — | Wait 2 minutes | Nothing changes |
| 5 | BO | Nadia confirms | VALIDATED; the row leaves BO-41 on every open back-office screen |
| 6 | RA, RW | Watch both screens | « Validée » on both within 5 s without refresh; push on the phone; one inbox entry, read on both once opened |
| 7 | BO | BO-39 history | SUBMITTED → VALIDATED, Nadia, time; RESERVATION movement |
Keep this order: AT-05 continues it to Livrée, and the returns run uses it.
X-2 — Assisted order, reseller confirmation, back-office confirmation
| Item | Detail |
|---|---|
| Preconditions | Yacine on the DA; REV-000008 on RA (phone) and RW; Nadia on BO-41. One assisted order may wait per reseller (ASSISTED_ORDER_ALREADY_PENDING): REV-000008 has none after a reset |
| API | delegate.assisted.create → delegate.assisted.submit → orders.get → orders.confirmAssisted (or orders.rejectAssisted) → admin.orders.toConfirm → admin.orders.validate → delegate.orders.get |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | DA | DA-17 for REV-000008: HOCO-C13-BL × 4, HOCO-J55-WH × 2 | Demi-gros prices; below 10 000,00 DA the send button is disabled with the reason |
| 2 | DA | « Demander la confirmation » | PENDING, AWAITING_RESELLER, channel DELEGATE_ASSISTED, a number; expiry D+72 h |
| 3 | BO | BO-41 | Listed as an assisted order awaiting the reseller; no confirm action (the API answers 409) |
| 4 | RA | Push and SMS (API log); RA-23 | Lines, totals, expiry; Confirmer / Refuser |
| 5 | RW | Confirm on RW-09 instead of the app | SUBMITTED; RA-23 on the phone no longer offers confirmation; Yacine pushed |
| 6 | BO | BO-41 | The order now waits for the back-office (SUBMITTED, confirmed by the reseller) |
| 7 | BO | Nadia confirms | VALIDATED |
| 8 | RA, RW, DA | — | « Validée » live on RA and RW; the order in Yacine's list as confirmed |
| 9 | DA + RA | Repeat 1–4, then the reseller refuses | Confirmation REJECTED_BY_RESELLER, order CANCELLED (ASSISTED_REJECTED); Yacine notified |
Returns and after-sales run
R-1 — Return request, reception, stock and credit note (AT-46)
| Item | Detail |
|---|---|
| Preconditions | The X-1 order delivered by AT-05 (today, inside the 7-day window); its invoice issued; CMD-2026-000003 delivered 8 days before the reset (outside the window); the worker runs (the credit note is issued on return.received) |
| API | returns.create, returns.get, admin.returns.list, admin.returns.decide, admin.returns.receive, admin.stock.movements, admin.invoices.get, invoices.get |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | RA-40 on the X-1 order: 3 × HOCO-C13-BL, reason, photo | RET-… REQUESTED; in BO-69 |
| 2 | RA | A return on CMD-2026-000003 (as REV-000012) | Refused with the 7-day window |
| 3 | RA | More units than delivered | 422 RETURN_QTY_EXCEEDS_DELIVERED |
| 4 | BO | Nadia approves | APPROVED, then En retour; reseller notified live |
| 5 | BO | Hakim receives: 3 received, 3 accepted, 2 restocked, 1 damaged | RECEIVED; physical +2 automatically (RETURN movement); no manual adjustment |
| 6 | BO + RA | Finance and the reseller's invoices | Credit note AVO-… for the accepted lines; invoice balance reduced; return REFUNDED; reseller notified |
| 7 | BO | HOCO-C13-BL on BO-33 / BO-34 | Values match steps 5–6 |
R-2 — After-sales claim to a replacement order (AT-45)
| Item | Detail |
|---|---|
| Preconditions | A delivered order of REV-000008 (X-1); the seeded open claim TKT-2026-000001 (REV-000012, CMD-2026-000003) for the rejection case |
| API | tickets.create, tickets.get, admin.tickets.list, admin.tickets.update, admin.tickets.resolve, admin.orders.toConfirm |
| # | App | Action | Expected |
|---|---|---|---|
| 1 | RA | RA-33: claim on the X-1 order, 2 × HOCO-C13-BL Produit défectueux, photos | AFTER_SALES ticket TKT-… with the lines; staff notified |
| 2 | BO | Nadia diagnoses and resolves Remplacement | A free replacement order, linked to the ticket, enters BO-41 and follows confirmation, preparation and delivery; reseller notified with its number |
| 3 | BO | Second claim on the same order, resolved Retour | A return born APPROVED, linked; continue as R-1 step 5 |
| 4 | BO | Third claim, resolved Avoir | Credit note without a physical return; finance view updated |
| 5 | BO | TKT-2026-000001 resolved Refusée with a reason | REJECTED; REV-000012 notified with the reason |
| 6 | RA | A claim for more units than delivered | Refused |
| 7 | RW | RW-14 for REV-000008 | The tickets, return and credit note of this run, same statuses as BO-56 / BO-69 |
Device matrix
Tiers of NFR-04 (proposed; the pilot users' phones are surveyed at the cadrage — replace the models with the surveyed ones when known). Each row lists what is run on it; a device is used with the staging build of the app.
| Class | Device (Android) | Apps | Scenarios | Conditions |
|---|---|---|---|---|
| Mid-range, tier 1 | Samsung Galaxy A15 or A16 (Android 14–15) | RA, DA | Every RA and DA step of blocks 1–5 | Wi-Fi and 4G; battery saver on for one AT-09 run |
| Mid-range, tier 1 | Xiaomi Redmi Note 13 or Redmi 13C (HyperOS) | DA (main delegate phone), RA | AT-08, AT-09, AT-15, AT-24 on site; AT-43 | HyperOS battery management left at its default: background location and the persistent notification must survive 15 min screen off |
| Mid-range, tier 1 | The model most used by HOCO's delegates (from the survey) | DA | AT-08, AT-15 | On site |
| Low-end, tier 2 | A 2–3 GB RAM phone on Android 10–11 | RA, DA | AT-02, AT-03, X-1, AT-43, AT-15 steps 1–4, AT-37 timings | 3G / weak 4G (4G-DZ profile of performance); app killed by the system and reopened |
| Low-end, tier 2 | An Oppo or Realme A-series (ColorOS) | DA | AT-09 step 5, AT-15 | Aggressive battery saver |
| Minimum SDK | Emulators: Android 7.0 (RA minSdk 24), Android 8.0 (DA minSdk 26) | RA, DA | Install, sign-in, catalog, checkout (RA); tour start, visit, offline replay (DA) | Emulator only — no GPS realism |
| Browsers (BO) | Chrome and Edge (last two), Firefox; 1 024, 1 280, 1 920 px; a 10-inch tablet | BO | Every BO step; AT-30 | — |
| Browsers (RW) | Chrome, Firefox, Safari 16.4+ (when a Mac or iPhone is available); Chrome for Android and Samsung Internet on the tier-1 phones | RW | AT-02, AT-03, X-1, X-2 step 5, AT-38–AT-40, AT-43, AT-44 | 360 px to 1 920 px |
iOS is out of the run unless D-10 changes. Each defect names the device, OS version and app build.
Defects: severity and daily triage
Severity scale
QA-01 uses three levels. Blocking is the cahier's anomalie bloquante — the recette's Blocker and Critical (severities) — so a defect keeps its meaning when QA-02 starts.
| Severity | Label | Definition | Examples | Rule |
|---|---|---|---|---|
| Blocking | sev:blocking | A priority or decision scenario cannot pass: a crash, a step that fails with no acceptable workaround, data loss, another user's data visible, wrong amounts, prices, stock or permissions, live status not updating | Checkout impossible; offline visits lost on sync; wrong customer-type price; stock not reserved at confirmation; a reseller sees a stock quantity | Fixed and redeployed within 1 working day (2 for an amount, stock or permission defect needing a migration); QA-01 cannot exit with one open |
| Major | sev:major | A function fails with a workaround, or a complementary scenario fails | A report export failing; a facet count off by one; a push missing while the live update works | An owner and a fix date at triage; fixed before the QA-02 entry or listed as a reservation |
| Minor | sev:minor | Cosmetic, wording, alignment, design conformance with no functional impact | Truncated label; wrong icon; spacing off the design | Backlog; deferred to LCH-04 with HOCO's agreement when the window is short |
Defect report and board
Each defect is a GitHub issue in the repository that owns the fix (hoco for server, back-office and web; hoco-mobile for the apps) with the milestone QA-01 and the labels qa-01 and one sev: label. Body: scenario and step (X-2 #5), environment and build numbers (API, back-office, web, app version and build), device and OS or browser, account, steps to reproduce, expected and actual result, evidence (screenshot or video, the requestId of the failing call from the error envelope or the API log), proposed severity. Statuses follow the defect report: Nouveau, Confirmé, En correction, Corrigé dans le build …, Vérifié, Fermé, Rejeté, Reporté.
Daily triage
Every campaign day at the end of the sessions, 30 minutes, led by Anis with Imad and Akram:
- New issues: reproduce (or ask for the missing evidence), confirm or change the severity, assign Anis (server, back-office, web) or Imad (apps), set a fix date for blocking and major. Duplicates closed with a link.
- Blocking first: every blocking defect has its fix in progress before anything else is picked; a blocking defect older than its fix time is raised at the next stand-up.
- Fixed issues: the fix lands on
dev, staging is redeployed (mobile: a new staging build on Firebase App Distribution), the issue moves to Corrigé dans le build N. - Verification next morning: the reporter re-runs the failed step and its scenario on build N; Vérifié → Fermé, or back to En correction with the new evidence.
- Minor items are batched; the ones that will not make it are moved to LCH-04 (Reporté) with HOCO's agreement, never silently.
- The day's counts (open by severity, opened, closed) go into the execution log summary.
Hardening checks logged for QA-01
The findings the hardening report logged for QA-01, each turned into a check on staging with an owner. They are tracked like defects (issue per item, label hardening), with the severity of the report mapped to the scale above only if the check fails.
| # | Report severity | Check on staging | Passes when | Owner |
|---|---|---|---|---|
| L-01 | High | Run apps/server/load/catalog.js (k6) against staging with the production-sized copy; record p50 / p95 of catalog.products.list, catalog.facets (cold and cached), catalog.products.suggest | p95 under 300 ms (NFR-01); otherwise the snapshot cache or SQL paging of the report is scheduled before QA-02 | Anis |
| L-02 | Medium | EXPLAIN ANALYZE of the q search on staging; AT-40 search timings | Trigram indexes used, or the measured time accepted against NFR-01 | Anis |
| L-03 | Medium | Time delegate.orders.list, admin.products.list, admin.stock.levels, admin.orders.toConfirm on staging during blocks 2 and 4 (BO network tab, API log) | Within AT-37's 2 s render budget at staging volumes | Anis |
| L-04 | Medium | Inspect the CSP of the back-office and web responses (two reloads of the same page); browse both apps with the console open | script-src carries a nonce that changes on every load and 'strict-dynamic', no 'unsafe-inline' or 'unsafe-eval'; no CSP violation in the console | Anis |
| L-05 | Medium | Sign in on production and staging hosts in the same browser (once production exists), or check the cookie name and domain on staging | The staging session cookie is not sent to production hosts and vice versa (environment-specific name or separate parent domain) | Anis |
| L-06 | Low | R-1 with a delivery manager deciding a return IN_TRANSIT | Allowed per the matrix, or the matrix amended | Anis |
| L-07 | Low | BO reseller form « Localiser » button on staging | Removed, or SEC-0.4 amended; no console error | Anis |
| L-08 | Low | Upload a photo with EXIF GPS data in AT-10 and AT-15; download the stored file | Re-encoded without EXIF and with a thumbnail once SRV-08 lands; until then recorded as open | Anis |
| L-09 | Low | Many guests behind one address (shared Wi-Fi) browsing during AT-38 | Decision recorded with the pilot traffic (limit or none) | Anis |
| L-10 | Info | From outside the VPS, reach the API port directly | Refused; only the reverse proxy reaches it (SEC-0.10) | Anis |
| L-11 | Low | pnpm audit --prod on the tested build | No high or critical | Anis |
| L-12 | Info | CI gitleaks job on the tested commit | Green | Anis |
| L-13 | Info | The SEC-0.3 CI grep rule with the allow-list | The files of the 12 reviewed $queryRawUnsafe calls allow-listed; no new hit | Anis |
| L-14 | Info | pg_stat_statements on the staging database | Preloaded; the slow-query view available (NFR-8.4) | Anis |
| L-15 | Info | Cookie flags on staging over HTTPS | HttpOnly, Secure, SameSite=Lax, the staging domain | Anis |
The other VPS items of the report (the three k6 scripts, the restore drill, TLS and headers, monitoring) belong to OPS-02 and AT-37 / AT-47; their results are attached to the exit report.
Automated checks
Run on the tested build before the run and again before the exit report:
| Check | Covers |
|---|---|
pnpm test (Vitest, server suite) | Every module suite, the permission, scope and exposure sweeps, the hardening tests |
apps/server/src/testing/cross-app-smoke.test.ts | The API path of X-1: an order placed with a reseller-app session waits in admin.orders.toConfirm (and on Nadia's /v1/admin/events stream as order.to_confirm), is confirmed by the commercial admin, and reaches the same reseller's app and web live streams as order.status_changed → VALIDATED; both sessions then read VALIDATED. The API path of X-2: a delegate draft sent for confirmation cannot be confirmed by the back-office first (409), the reseller confirms it with a web session, the back-office confirms it, and the delegate's stream, the delegate, the reseller and the back-office all read VALIDATED. It creates its own product and stock, uses REV-000011 and removes everything it creates; it skips itself without the demo seed |
apps/server/src/testing/reset-guard.test.ts | The refusal rules of pnpm qa:reset |
| Playwright suite (back-office, reseller web) | Named by the QA-01 tests; not in the repository yet — added with the web tasks, else the manual run stands |
The smoke test replays the outbox events itself (as the worker's dispatcher would) because the test process runs no worker; staging proves the real dispatch.
Execution log
Filled during the run (one row per execution; a re-run after a fix adds a row). Result: Passé, Passé avec réserve, Échoué, Non exécuté (reason).
Builds under test: API … · back-office … · web … · reseller app … (build …) · delegate app … (build …) · reset at …
20 October 2026 — API run on the test sandbox (dev at ab6bdbb, demo seed of the same day, no reset): every office scenario marked API ✓ was driven through the API with the staging identities and behaved as expected; the screen run (BO, RW, RA) is still to do. Defects found and fixed before the run: staff password-reset codes sent by SMS instead of e-mail, test numbers refused on staging (#48); the mock API down (#47). Three steps of this plan contradicted the contract and were corrected (AT-01 step 4, S-02, AT-06 step 4).
| Id | Scenario | Owner | Apps | Date | Device / browser | Result | Defects |
|---|---|---|---|---|---|---|---|
| AT-01 | Sign-up, validation | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ — steps 1–8; OTP read in Mailpit | |
| AT-02 | Catalog visibility, prices, availability | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-03 | Order number, queue, confirmation | Anis, Imad | RA · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-04 | Reservation, deduction, restock | Anis | BO · RA | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-05 | Live tracking and notifications | Anis, Imad | BO · RA · RW | 2026-10-20 | API (sandbox) | API ✓ — push not checked (no device) | |
| AT-06 | Promotion targeting | Anis, Imad | BO · RA · RW | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-07a | No loyalty surface in V1 | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-08 | Tour start to closing | Imad, Anis | DA · BO | — | |||
| AT-09 | GPS tolerance and consent | Imad | DA · BO | — | |||
| AT-10 | Field prospect without duplicate | Imad, Anis | DA · BO | — | |||
| AT-11 | Invitation attribution | Imad, Anis | DA · RA · BO | — | |||
| AT-12 | First order and source delegate | Imad, Anis | DA · RA · BO | — | |||
| AT-13 | Visit report and follow-up | Imad, Anis | DA · BO | — | |||
| AT-14 | Permissions | Anis | BO · API | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-15 | Offline tour and sync (with AT-24) | Imad | DA · BO | — | |||
| AT-17 | Credit blocking and approval | Anis, Imad | RA · BO | 2026-10-20 | API (sandbox) | API ✓ — BLOCK policy only | |
| AT-20 | Stock override | Anis | BO | — | |||
| AT-35 | Cancellation and restock | Anis, Imad | RA · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-38 | Guest without prices | Anis, Imad | RA · RW | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-39 | Price per customer type | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-40 | Filters and facets | Anis, Imad | RA · RW · BO | — | |||
| AT-41 | No stock quantity for resellers | Anis, Imad | RA · RW · DA | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-42 | Every order confirmed in the back-office | Anis, Imad | All | 2026-10-20 | API (sandbox) | API ✓ | |
| AT-43 | Live status on the reseller screens | Anis, Imad | BO · RA · RW | — | |||
| AT-44 | Regional WhatsApp | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| X-1 | App order → BO confirmation → live app and web | Anis, Imad | RA · RW · BO | — | |||
| X-2 | Assisted → reseller → BO confirmation | Anis, Imad | DA · RA · RW · BO | — | |||
| R-1 | Return → reception → stock and credit note (AT-46) | Anis, Imad | RA · BO | 2026-10-20 | API (sandbox) | API ✓ | |
| R-2 | Claim → replacement order (AT-45) | Anis, Imad | RA · RW · BO | 2026-10-20 | API (sandbox) | API ✓ — steps 1, 2, 5, 6 | |
| L-01–L-15 | Hardening checks | Anis | API · OPS | — |
The other complementary scenarios (AT-16, AT-18, AT-19, AT-21–AT-23, AT-25–AT-34, AT-36, AT-37, AT-47) run in QA-03 and QA-02 with the acceptance page's scripts; a failure found there on staging is logged on the same board.
Exit report
Attached to the plan before QA-02 starts. It holds:
- The builds tested and the reset time; the device matrix as actually run.
- The execution log above, with every priority scenario Passé on staging across the apps involved (OPS-05).
- Open defects by severity: zero blocking; every major with an owner and a fix date; minor items with their LCH-04 decision.
- The confirmation that every order path (app, web, assisted, back-office) waited for the back-office and that the reseller saw each status live (ORD-14, ORD-15) — X-1, X-2, AT-42, AT-43.
- The offline tour result: synced without duplicates or lost reports (SYN-04, SYN-05) — AT-15, AT-24.
- Akram's conformance review with no open blocking UI issue.
- The hardening checks L-01 to L-15 with their outcome, and the k6 and restore-drill results from OPS-02.
- The automated checks green on the tested build, and the regression run of every fixed defect.
- Readiness for the recette: what QA-02 inherits (open majors, accepted reservations, known issues list).
Acceptance scenarios
The cahier's fifteen priority acceptance scenarios and thirty-two complementary ones as executable test scripts with seed data, steps, expected results, requirements covered and evidence — then the acceptance procedure up to the signed PV de recette.
Requirements
How HOCO's requirements are written, prioritised and traced, and the 25 requirement areas with their V1/V2 counts.