# AssetTrack — QA & Production-Load Test Report (12.3.14 tested → 12.3.15 fixed)

**Environment:** Node 22, MariaDB 10.11 (MySQL-compatible), demo seed data, one Node process, **1 vCPU shared by app + database + load generator** (so latencies are pessimistic; the correctness results are not affected by speed).
**Method:** the 4 original test scripts + a new QA suite (`tests/qa/`) that drives the real HTTP API, checks values against hand-computed expectations, then audits the database with SQL. Final certification was run on a **wiped and reseeded database** with the shipped defaults.

## 1. Verdict
12.3.14 was functionally correct at normal volume but **not safe when several inventory operations ran at the same moment**: 5 data-integrity defects (plus a 6th found in the follow-up run), all silent (no error shown). All are fixed in 12.3.15, together with the availability and configuration issues below. **Final run: every check passes, zero server errors in every load tier, all post-load audits clean.**

## 2. Final results (12.3.15)

| Suite | Result |
|---|---|
| Original: pricing fuzz (100,000 bills) / charges / order logic / POS end-to-end | 0 failures / 10/10 / 12/12 / 29/29 |
| P1 Inventory functions & costing | **34 / 34** |
| P2 POS correctness & inventory↔POS information flow | **28 / 28** |
| P3 Races + production load + audits | **14 / 14** |
| P7 Item-edit race, manager-approval race | **2 / 2** |
| P4 Concurrency matrix (7 scenarios × 6 runs) | **0 / 42 inconsistent** |
| P5 Phantom cost layers | **0** (only the genuine opening layer) |

| Load tier | Offered load | Errors | p50 / p95 / p99 | Audit afterwards |
|---|---|---|---|---|
| Realistic peak | 10 tills (a bill every ~4 s each) + 3 back-office users, 60 s, ≈3.4 req/s | **0** | 14 / 90 / 218 ms | clean |
| Stress | 60 tills + 8 back-office users, 60 s, ≈92 req/s (10–20× a busy canteen) | **0** | 462 / 853 / 1,077 ms | clean: 3,809 bills, ledger 0 violations, conservation 0, no duplicate bill/token numbers, no gaps, summary = SQL (Rs 1,118,574.28), 0 deadlocks |
| 300 simultaneous reads | catalog + overview from 300 tills at once | **0** | p50 1.7 s, p95 2.2 s | — |

Race tests (all released at the same instant): 60 and 200 simultaneous sales of a 50-unit item → **exactly 50 sold**, 0 errors, no negative stock; 40 identical submissions → **1 bill**; 20 simultaneous voids → **1** applies; 20 simultaneous transfer approvals → **1** applies (70/30); 20 simultaneous procurement receives → **1 item, 25 units**; 150 sales + 150 consumes + 20 restocks on one item → units conserved exactly (expected 650, actual 650).

## 3. Defects found and fixed

| # | Sev | Defect (12.3.14) | Evidence before → after |
|---|---|---|---|
| 1 | High | Concurrent **procurement receive** created duplicate stock (check-then-act, no lock) | 20 simultaneous receives: 20 items / 500 units → **1 item / 25 units** |
| 2 | High | Concurrent **transfer approval** applied more than once | 30-unit transfer: 90 moved (source 10 / dest 90 in 3 records) → **70 / 30 in 1 record** |
| 3 | High | **Quick restock lost units** (stale read-modify-write) | 15 simultaneous restocks: quantity 1,010 vs batches 1,075 → **1,075 = 1,075**; bad in 6/6 runs → 0/6 |
| 4 | High | **Phantom cost layers**: stock-ledger helpers used the transaction's older snapshot | 8 add-stocks + 8 sales: batches 1,152 vs quantity 1,032, 6 phantom layers → none |
| 5 | High | **FIFO/LIFO cost wrong** after "restock before first issue" (opening layer priced at blended cost, dated today) — quick restock and procurement restock-receive | issue 120 of 100@10 + 50@20: Rs 1,933 → **Rs 1,400** |
| 6 | High | **Item-edit (quantity/cost) racing with sales**: layers changed in one transaction, quantity saved later from a stale copy; a generic save could overwrite stock columns | 8/8 runs quantity ≠ batches (e.g. 493 vs 990) → **0/8** |
| 7 | Med | **Logins froze the whole server** (bcrypt on the main thread) | 10 simultaneous logins: other requests waited **3,406 ms** → **≤ 50 ms** (worker threads) |
| 8 | Med | **Pool saturation → 503**: queue of 30 beside 8 connections | 200 simultaneous sales: 38 served, 162 failed; stress run 25% 503 → queue 500: **0 errors** |
| 9 | Med | **Rate limit per IP** shared by every till behind one school router | 700 requests from one IP: 100 throttled → per signed-in user: **0** throttled; one abusive user still limited |
| 10 | Med | **Repair status** accepted any text ("banana" stored) | now 400 with the list of valid statuses |
| 11 | Low | Fresh-install warning `idx_pe_linked_pr` (index created before its column) | gone |

## 4. What was verified to be correct (no change needed)
Pricing against hand-computed totals (VAT added/included, % and flat discount, service + fixed charges, rounding); FIFO/LIFO over multiple layers; void restores quantity **and** value; order place → correct → finalize → reopen → void with audit trail; inventory consume/restock/edit/transfer/dispose/delete all reflected correctly at the till; POS-linked consumption cannot be reverted from inventory; disposed items cannot be sold; deleting an item keeps old bills readable; reports/exports for all kinds; permissions (staff, manager scope, admin-only actions); input fuzzing and an SQL-injection string; the manager-approval route applies once.

## 5. Behaviour changes and limits
* A second simultaneous approve/receive now gets **409 "Someone else is processing this request"**.
* A generic item save no longer changes `quantity`/`purchase_cost` of an existing **stock** item (these change only through restock, consume, transfer, POS, or Edit item).
* After a quick restock the unit cost is the weighted average of the cost layers on hand.
* Logins take ~0.4 s CPU each (10 at once ≈ 4 s total on one core) but no longer block anyone.
* The 300-way read burst on this machine took ~2 s p95 — correct, but a reminder that one core is the ceiling.

## 6. Not covered by this test pass
Browser UI (till screens, printing, PWA, Nepali date picker), email delivery, file/photo uploads, CSV import, long soak (hours), multi-instance deployment (designed for, not exercised), MySQL 8 specifically (tested on MariaDB 10.11), and POS behaviour with `finalizeStep` enabled under heavy load (functional order flow was tested, load was run with it off).

## 7. Audit-formula note
An early "conservation" audit counted units returned by voids as receipts and wrongly flagged clean data. The shipped audit uses *opening stock + recorded purchases − live POS sales − live manual consumption* and was validated both on clean data and against the broken build (where it correctly flagged 18 of 24 items).
