# 9. Test plan

## 9.1 Automated tests (in the repository)

| Suite | Location | What it proves |
|-------|----------|----------------|
| Unit: util | `apps/api/test/unit/util.spec.ts` | decimal arithmetic, week/month keys, date helpers, serialisation |
| Unit: permissions | `test/unit/permissions.spec.ts` | wildcard logic, every system role references catalogued permissions, separation of duties |
| Unit: billing pricing | `test/unit/billing-pricing.spec.ts` | flat and tiered rates, tier ordering, tariff conditions |
| Unit: outbound URL guard | `test/unit/net.spec.ts` | private / loopback / link-local address classification used by the SSRF guard |
| Unit: mock provider | `test/unit/mock-provider.spec.ts` | the AI test double honours the structured-output contract and the tool loop |
| E2E: inbound | `test/e2e/inbound.e2e-spec.ts` | ASN creation and pack conversion, duplicate reference, arrival, receive by barcode, idempotent receipt, lot/expiry enforcement, damaged routing + discrepancy, over-receipt tolerance, RF putaway with wrong-location rejection, short close with GRN PDF, ledger reconciles with balances |
| E2E: outbound | `test/e2e/outbound.e2e-spec.ts` | ATP excludes damaged, wave allocation/release, RF pick, short pick raising a spot count, order status progression, packing, load/dispatch with gate pass and DN, goods issue, no negative balances, cancel releases allocations |
| E2E: billing | `test/e2e/billing.e2e-spec.ts` | contract/tariffs, event-driven inbound charges with minimum charge, idempotent replay, outbound per order/case tiers, manual VAS and waiver, storage snapshots and weekly report, invoice run with monthly minimum, issue + PDF, payment and credit note |
| E2E: tenancy | `test/e2e/tenancy.e2e-spec.ts` | credentials, cross-tenant read/write isolation (RLS + reference guard), platform console access, feature flags per plan, plan limits, RBAC, storer-scoped portal users, refresh token rotation, API key scopes and daily quota, audit log |
| E2E: integrations | `test/e2e/integrations.e2e-spec.ts` | public API v1 (items, idempotent ASN, orders, inventory, unknown item), SAP MATMAS/ORDERS IDocs, webhook secrets and deliveries, Excel template/dry run/real import |
| E2E: AI | `test/e2e/ai.e2e-spec.ts` | provider status, intake draft with matching/warnings, acceptance guard and applied ASN, analytics proposals, slotting apply, copilot tool loop and persistence, token metering and budget, tenant scoping of proposals |

Run: `pnpm --filter @wms/api test && pnpm --filter @wms/api test:e2e`. The e2e run creates `wms_test` from scratch (migrations included) on every run. CI runs both on every push.

Front end: every page was exercised against the live API with a headless browser (login, navigation, console error capture) and the main flows were driven through the UI (ASN creation, stock move/hold, order to load, SKU/supplier/role creation, dry-run import, webhook creation, AI intake and copilot, RF receive and putaway). Vitest is configured for component tests; adding them is on the roadmap.

## 9.2 Manual UAT script (one warehouse day)

Pre-requisites: demo tenant, one storer with 3 SKUs (one lot/expiry controlled, one serial controlled, one plain), locations generated, an ACTIVE contract with inbound/outbound/storage tariffs, two RF users, one supervisor, one CSO, one billing user.

| # | Step | Expected |
|---|------|----------|
| 1 | Create ASN with 3 lines (back office) and a dock appointment | ASN SCHEDULED, appointment visible on the day view |
| 2 | Gate: mark arrived with seal and vehicle | ARRIVED, ASN listed on RF Receive |
| 3 | RF Receive: scan ASN, scan case barcode, receive 8 of 10 cases lot/expiry, new LPN | LPN at DOCK, PUTAWAY task suggested in AMB zone; `GET /documents/labels/lpn/<LPN>` returns ZPL |
| 4 | RF Receive: 1 case damaged with reason | LPN routed to DMG-01, discrepancy DAMAGED |
| 5 | RF Receive: try 5 more cases | OVER_RECEIPT refused (10% tolerance) |
| 6 | RF Putaway: scan wrong location then right one | WRONG_LOCATION then success; stock enquiry shows balance AVAILABLE |
| 7 | Close ASN short, send RCR | CLOSED, GRN PDF, SHORT discrepancy, RCR email in outbox, inbound charges captured within seconds |
| 8 | Create 2 orders, create wave with allocate+release, print pick list | RELEASED, PICK tasks, ATP decreased |
| 9 | RF Pick order 1 fully; order 2 short pick 1 case | order 1 PICKED; SPOT count created; order 2 flagged short-pending, packing refused until Accept short or re-allocation |
| 10 | Packing station: pack order 1, complete | shipment PACKED/STAGED, packing list PDF; carton label ZPL via API |
| 11 | Load: create, add shipment, loaded, dispatch with seal | DISPATCHED, gate pass + DN PDFs, order SHIPPED, outbound charges captured |
| 12 | Cycle count on the short-pick location, enter count (RF user), review and approve variance (inventory controller, not the counter) | adjustment posted, balance corrected, audit entries; the counter or a user without inventory.adjust.approve is refused (FOUR_EYES) |
| 13 | Adjustment request by RF user, approval by supervisor | requester cannot approve own; POSTED after approval |
| 14 | Hold a lot, check ATP, release | ATP excludes held stock, restored after release |
| 15 | Storage: run snapshot, view weekly report | CBM/pallets per day, week groups |
| 16 | Billing: run invoice, issue with PDF, record payment | lines per group, minimum applied if below, charges INVOICED, PARTIALLY_PAID |
| 17 | Integrations: create API key, POST /v1/asns twice with the same Idempotency-Key | one ASN; message log shows calls |
| 18 | Webhook to a test endpoint (webhook.site), create an order | delivery with signature; redeliver works |
| 19 | AI: intake a PO PDF, review, accept | ASN created with source AI; proposal APPLIED |
| 20 | AI: run inventory anomaly and workload forecast; copilot "which orders are late?" | proposals in inbox; copilot answer cites tool data |
| 21 | Portal user (storer scope) logs in | sees only own storer's data; cannot see other storers |
| 22 | Platform console: change plan to STARTER | AI menu disappears for the tenant; warehouse creation blocked by plan limit |
| 23 | Export tenant data | zip with NDJSON per table, secrets redacted |

## 9.3 Non-functional checks before go-live

- Load: 50 concurrent RF users confirming tasks (k6 script against `/tasks/next` and `/tasks/:id/confirm`); target p95 < 300 ms on the API with Postgres on SSD.
- Allocation contention: two waves over the same stock at the same time never double-allocate (covered by `SKIP LOCKED` and the check constraint; verify with a parallel script).
- Failover: kill the worker during a dispatch burst; events remain PENDING and are processed when it returns.
- Restore drill: restore last night's backup to staging and run the e2e suite against it.
- Security: dependency audit (`pnpm audit`), TLS configuration, secret scanning of the repository.
