$ cd App/php && php tests/run.php
Profitina Laundry PHP track - tests
PHP 8.4.21 on Linux, SQLite 3.45.1
Test copy: .../App/php/tests/tmp/run-20260924-053904/server-data/laundry.db

== Unit tests ==
PASS UNIT-PW    PBKDF2 format of database/README.md 6: test vector, owner hash, new hashes (S-5)
PASS TC-1.10    Role derivation for all 14 employees (BR-12)
PASS UNIT-RULES Price, units, promised time, racks, whole numbers, money format
PASS TC-4.7     Rollback: an error after the Bill insert leaves nothing behind; next id stays B2954
PASS TC-5.10    Two requests "mark B2940 finished" at the same moment: exactly one succeeds (AC-5.8)

== HTTP tests on http://127.0.0.1:41531 ==
PASS TC-1.1     Owner login: session, dashboard "Hendra Wijaya (owner", /api/me role owner O01
PASS TC-1.2     Wrong password (wrong case) -> 401 "E-mail or password is wrong", no session
PASS TC-1.3     Unknown e-mail -> exactly the same status and message as TC-1.2
PASS TC-1.4     password_hash NULL (E0007 cuci2) -> refused like TC-1.2
PASS TC-1.5     No session: page -> redirect to /login, API -> 401 not_logged_in
PASS TC-1.6     Washer: reports page, POST /api/services, POST /api/bills -> 403; menu only Dashboard + Queue
PASS TC-1.7     Logout, then /api/me with the old cookie -> 401
PASS TC-1.8     Session id changes at login; cookie HttpOnly + SameSite=Lax
PASS TC-1.11    password_hash only in hashing/login code; no template prints it
PASS TC-3.1     GET /api/services without login: 12 services, S0001 8000, S0005 65000
PASS TC-3.2     Cashier: PUT /api/services/S0001 -> 403; no edit buttons on the page
PASS TC-3.4     Owner: price 0, -1, 8500.5, 100000, "8500.5"; empty description -> 422 with field errors
PASS TC-3.5     Owner: delete S0001 -> 409 "S0001 is used by 1,368 bills"
PASS TC-3.6     Owner: create "Carpet (per m2)" 12000 -> S0013
PASS TC-2.1     Customers page 1 of 13 (256, 20 per page), sorted by name
PASS TC-2.2     Search "wijaya" and "WIJAYA": 8 customers, C0228 Aisyah ... C0082 Yoga Ahmad Wijaya
PASS TC-2.3     Search "0896 0000 1482", "089600001482", "C0151" -> exactly C0151 Syaiful Wijaya
PASS TC-2.4     Search "zzzz" -> "No customer found" + Register new customer
PASS TC-2.5     Create Rina Kusuma -> C0257, enrolled now, credit 0, password *****, hash NULL
PASS TC-2.6     Empty name, e-mail "rina", phone "12", birthdate 2030 -> 4 field errors, values kept, nothing saved
PASS TC-2.7     Edit C0151 address: updated, id and enrolled unchanged
PASS TC-2.8     Supervisor deletes C0094 -> 409 "This customer has 62 bills and cannot be deleted"
PASS TC-2.9     Delete C0257: cashier -> 403, supervisor -> 204
PASS TC-2.10    C0094 detail: 62 bills, total Rp 3.079.000, most recent B2919 (27 Sep 2026, Rp 48.000)
PASS TC-10.1    B2938 + 0896-0000-1482: Ready for pickup, 5 kg, Rp 40.000, still to pay, outlet Wiyung
PASS TC-10.2    b2938 + "0896 0000 1482" / "089600001482" -> the same result
PASS TC-10.3    B2938 + phone of another customer -> "No bill found for this number and phone"
PASS TC-10.4    B9999 + 0896-0000-1482 -> the same message and status as TC-10.3
PASS TC-10.5    The status response has no name, address or e-mail
PASS TC-10.7    B2944 (queued) + its phone: "In process, expected ready by 2 Oct 2026 07:31"
PASS TC-7.1     Receipt B2938: official logo (byte-identical), outlet, bill, 5 kg, prices, rack, ready by, status hint
PASS TC-7.2     Receipt has no customer address, e-mail or birthdate
MAN  TC-7.3     Print preview: navigation hidden, fits 80 mm and A4
      (needs a browser print dialog; @media print hides .no-print (checked in TC-7.1))
PASS TC-8.1     Owner dashboard 2026-09-30: O01 6/186.000/9/23, O02 1/0/9(6)/6, O03 3/160.000/5/5
PASS TC-8.2     Month: 3.953.000 + 3.851.000 + 4.109.000 = 11.913.000
PASS TC-8.3     Top services: S0001 123, S0002 35, S0003 35
PASS TC-8.4     Washer cuci1: only the O01 queue tiles (9 in process, 23 ready), no "Rp" on the page
PASS TC-8.5     Cashier kasir1: only the O01 tile
PASS TC-9.1     Monthly report: 12 rows, 2025-10 171/8362000/7686000, totals 2953/144213000/142684000
PASS TC-9.2     Outlets: O01 1312 66.282.000 46.0 %, O02 996 45.450.000 31.5 %, O03 645 32.481.000 22.5 %
PASS TC-9.3     Services: S0001 first, 1368 bills, 8531 units, 67.632.000, 46.9 %
PASS TC-9.4     Employees: E0004 788 received 39.768.000; E0006 649 finished
PASS TC-9.5     Receivables: 38 bills, the first 6 collected, B0340 Rp 35.000 first
PASS TC-9.6     Monthly CSV: text/csv, header line, 2025-10 line, file name
PASS TC-9.7     Cashier opens reports -> 403
PASS TC-9.8     Report numbers equal database/queries.sql Q03, Q04, Q06, Q07, Q09, Q10, Q16
PASS TC-11.1    L1 endpoints answer as in api.md (B2938, B0014, bills list, customers, outlets, employees, me)
PASS TC-11.2    GET /api/bills?outlet=O03&status=queued: total 5, B2940 (balance 18000) then B2941
PASS TC-11.6    per_page=1000 is capped at 100
PASS TC-11.7    Types: payment is a number, times are "YYYY-MM-DD HH:MM:SS"
PASS TC-5.6     B0014 history: queued Kevin Tanoto, finished + complaint Siti Nurhaliza Rahman, departed Rizky Pratama
PASS TC-6.7     B0006: 18000 cash + 18000 cash, paid 36000, balance 0
PASS TC-5.11    Queue O02 at "now": 9 in process (6 overdue, oldest B2909 100.9 h), 6 ready
           /                      best of 3: 5 ms
           /queue?outlet=O02      best of 3: 1 ms
           /reports/monthly       best of 3: 3 ms
           /customers?q=wijaya    best of 3: 1 ms
           /bills/B0014           best of 3: 1 ms
           30 queries.sql queries, slowest Q28: 5 ms
PASS TC-NF-11   Performance: dashboard, queue, monthly report < 1 s; every queries.sql query < 200 ms
PASS TC-4.9     Free-rack suggestion at O03: lowest free rack is A03 (A01, A02 in use)
PASS TC-4.3     unit 0, -2, 4.3, "4.3", 1000, "abc" -> 422 "Enter a whole number from 1 to 999", nothing saved
PASS TC-4.4     Rack A01 at O03 (in use by B2944) -> 422 "Rack A01 is in use"
PASS TC-4.8     Unknown customer C9999 / service S0099 -> 422
PASS TC-4.7     HTTP: pay_now method "gopay" -> 422, no Bill/StatusLog/Payment saved
PASS TC-4.1     kasir5 (O03): C0151, S0001, 5, A03 -> B2954, O03, E0012, 40000, queued, arrived now, 1 log, 0 payments
PASS TC-4.2     A body with "payment": 1 -> payment is still 40000
PASS TC-6.2     B2938: 50000 -> 422 "The balance is only Rp 40.000"
PASS TC-6.3     QRIS without reference; cash with reference -> 422 both
PASS TC-6.8     Amount 0, -5, 1000.5, "1000.5" -> 422
PASS TC-5.3     kasir5 hands over B2938 (balance 40000) without allow_unpaid -> 409 "Balance Rp 40.000 open"
PASS TC-6.1     B2938: cash 40000 -> payment id 3320, reference NULL, balance 0
PASS TC-5.2     Mark B2938 (already finished) finished again -> 409 "B2938 is already finished", nothing changed
PASS TC-5.4     Hand over B2938 (paid) -> departed now + log; rack A05 free again
PASS TC-4.5     Express S0002 unit 3 -> 36000
PASS TC-4.6     pay_now cash 20000 -> 1 payment 20000, balance 20000, one transaction
PASS TC-5.12    Supervisor spv2 (O02) finishes B2941 (O03) -> 403
PASS TC-5.1     Washer cuci5 (O03) marks B2941 finished -> finished now, log by E0013
PASS TC-5.5     Hand over B2941 with an open balance and allow_unpaid -> departed, in receivables as collected
PASS TC-5.7     spv3 cancels B2944 (O03, queued, no payments) -> departed, finished NULL, payment 0, log cancelled
PASS TC-7.4     Receipt of the cancelled B2944 shows "CANCELLED"
PASS TC-6.6     Payment on the cancelled B2944 -> 409
PASS TC-5.8     Cancel B2952 (paid 20000) -> 409 "has payments"; B2938 (collected) -> 409; as cashier -> 403
PASS TC-5.9     Owner complaint on B2919 -> log complaint, status unchanged; on a queued bill -> 409
PASS TC-6.5     Credit 120000 for C0089 (deposit 109000) -> 422 "Deposit Rp 109.000 is not enough", deposit unchanged
PASS TC-6.4     C0089: S0001 x 4 = 32000 paid by credit -> reference DEPOSIT, deposit 77000
PASS TC-3.3     Owner: S0001 -> 8500: B2938 keeps 40000, a new 5 kg bill costs 42500
PASS TC-11.3    A logged-in POST without X-CSRF-Token -> 403 csrf_failed
PASS TC-NF-04   New-bill form without the token, or with a token of another session -> 403, nothing saved
PASS TC-NF-01   Invalid data sent directly (no browser checks) -> 422, nothing saved
PASS TC-NF-02   SQL injection text in search, login, status check and ?outlet= -> no 500, no extra rows, tables intact
PASS TC-NF-03   Customer named <script>alert(1)</script> is shown as text on S08, S10, S04
PASS TC-NF-05   UC-16 set password: pbkdf2_sha256$600000$, 90 chars, password *****, new salt, verified by the Python tool
PASS TC-NF-06   Role table of api.md 1.2, called by every role
PASS TC-NF-08   Data minimisation: the status response has exactly the FR-10 fields
PASS TC-NF-13   A new bill's arrived equals the server clock (WIB), no "T", no fractions
PASS TC-10.6    11 status checks within one minute from one IP -> the 11th gets 429
PASS TC-1.9     5 wrong passwords for kasir1, then the correct one -> "Too many attempts" (429)
PASS TC-11.4    Every API error response has error.code and error.message
PASS TC-11.5    No API response contains a password or password_hash field (or a hash)
PASS TC-NF-07   A server error (database missing) -> friendly page / JSON, no stack trace; details in the log
PASS TC-DB-2    The integrity checks (database/tools/check_integrity.py) pass on the test copy after all changes
MAN  TC-NF-10   A new student follows the README on clean Windows, macOS and Linux
      (not possible in the build container)
MAN  TC-NF-12   Pages at 360 px: no horizontal scrolling, tappable buttons
      (needs a browser; the CSS has a 720 px breakpoint with stacked cards)

======================================================================
PASS 100   FAIL 0   MANUAL 3   SKIP 0   (total 103)
