344b1701dd
Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
207 lines
20 KiB
Bash
207 lines
20 KiB
Bash
#!/usr/bin/env bash
|
|
# ThreadCount update-wave smoke test against a local dev server. Exercises every new op end-to-end.
|
|
set -u
|
|
B=${BASE:-http://127.0.0.1:3111}
|
|
# Refuses early, with the fix, when the server under test is in production mode with
|
|
# Turnstile refusing every auth route — otherwise the first signup fails and every check
|
|
# after it reports a security-check error instead of what it was testing.
|
|
. "$(dirname "$0")/e2e-preflight.sh"; e2e_preflight "$B"
|
|
J=${TMP:-/tmp}/tc-cj.txt; rm -f "$J"
|
|
PASS=0; FAIL=0
|
|
ok() { PASS=$((PASS+1)); echo " ✓ $1"; }
|
|
fail() { FAIL=$((FAIL+1)); echo " ✗ $1 :: $2"; }
|
|
mut() { curl -s -b "$J" -c "$J" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"$1\",\"payload\":$2}"; }
|
|
check(){ local name=$1 out=$2 pat=$3; if echo "$out" | grep -q "$pat"; then ok "$name"; else fail "$name" "$out"; fi; }
|
|
jget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(eval('d$1'))"; }
|
|
|
|
TS=$(date +%s); EMAIL="e2e$TS@example.com"
|
|
echo "== signup"
|
|
R=$(curl -s -c "$J" -X POST "$B/api/auth/signup" -H 'content-type: application/json' -H "x-forwarded-for: 10.9.$((RANDOM%250)).$((RANDOM%250))" -d "{\"first\":\"Test\",\"last\":\"Admin\",\"facility\":\"E2E Hospital $TS\",\"email\":\"$EMAIL\",\"password\":\"password123\"}")
|
|
check "signup" "$R" '"ok":true'
|
|
check "the facility names its staff groups" "$(e2e_groups "$B" "$J")" '"ok":true'
|
|
|
|
echo "== setup: depts, suppliers, catalog, staff, opening"
|
|
check "dept A" "$(mut dept.save '{"name":"Willow Ward","cc":"RGH-3010"}')" '"ok":true'
|
|
check "dept B" "$(mut dept.save '{"name":"Security","cc":"RGH-5090"}')" '"ok":true'
|
|
check "dept dup rejected" "$(mut dept.save '{"name":"willow ward","cc":"1"}')" 'already'
|
|
R=$(mut supplier.add '{"name":"Northline Workwear"}'); check "supplier add" "$R" '"id"'; SUP=$(echo "$R" | jget '["result"]["id"]')
|
|
check "supplier update lead" "$(mut supplier.update "{\"id\":\"$SUP\",\"lead\":\"10\",\"contact\":\"Dana\",\"account\":\"ACC-1\"}")" '"ok":true'
|
|
check "supplier dup rejected" "$(mut supplier.add '{"name":"northline workwear"}')" 'already'
|
|
# Scrub Pant is for every group. Sam, in Security, has a pair ordered in further down to prove an
|
|
# order-in takes the product's own supplier; tagged for nurses only, it would be refused at the counter
|
|
# as outside his staff group before that was ever looked at.
|
|
R=$(mut import.rows '{"kind":"catalog","rows":[{"item":"RN Scrub Top","gender":"Unisex","sku":"TOP1","supplier":"Northline Workwear","cost":"30","group":"Registered Nurse","sizes":"S|M|L"},{"item":"Scrub Pant","gender":"Unisex","sku":"PANT1","supplier":"Harbour Embroidery","cost":"25","group":"All","sizes":"S|M|L"},{"item":"Security Shirt","gender":"Male","sku":"SEC1","supplier":"Northline Workwear","cost":"40","group":"Security","sizes":"M|L"}]}')
|
|
check "catalog import 3" "$R" '"created":3'
|
|
check "supplier auto-created from import" "$(mut supplier.add '{"name":"Harbour Embroidery"}')" 'already'
|
|
R=$(mut import.rows '{"kind":"staff","rows":[{"num":"1001","first":"Nina","last":"Nurse","group":"Registered Nurse","dept":"Willow Ward","top":"M","pants":"M"},{"num":"2002","first":"Sam","last":"Guard","group":"Security","dept":"Security","ent":"2","top":"L","pants":"L"}]}')
|
|
check "staff import 2" "$R" '"created":2'
|
|
check "opening import" "$(mut import.rows '{"kind":"opening","rows":[{"sku":"TOP1","size":"M","opening":"10","reorder":"3"},{"sku":"PANT1","size":"M","opening":"10"},{"sku":"SEC1","size":"L","opening":"1","reorder":"2"},{"sku":"TOP1","size":"S","opening":"0","reorder":"2"}]}')" '"created":4'
|
|
# Footwear is not issued in this linen room, so a shoe column on the staff template would have wards
|
|
# filling in sizes for something nobody can ever hand them. Read the template's own header row out of
|
|
# csv.ts and look at the columns themselves: pinning one long literal header string, as this used to,
|
|
# stopped matching anything the day a manager column was added between cc and top, and from then on
|
|
# the check passed whatever the file said.
|
|
STAFF_HDR=$(sed -n 's/^[[:space:]]*staff:.*headers: "\([^"]*\)".*$/\1/p' "$(dirname "$0")/../lib/csv.ts")
|
|
check "staff CSV template header row found" "$STAFF_HDR" '^num,'
|
|
check "staff CSV template has no shoe column" "$(echo ",$STAFF_HDR," | grep -c ',[^,]*[Ss]hoe[^,]*,' || true)" '^0$'
|
|
|
|
echo "== fetch ids via backup"
|
|
BK=$(curl -s -b "$J" "$B/api/backup")
|
|
check "backup v2" "$BK" 'threadcount-backup-v2'
|
|
NINA=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([s['id'] for s in d['staff'] if s['num']=='1001'][0])")
|
|
SAM=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([s['id'] for s in d['staff'] if s['num']=='2002'][0])")
|
|
TOP=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([i['id'] for i in d['items'] if i['sku']=='TOP1'][0])")
|
|
PANT=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([i['id'] for i in d['items'] if i['sku']=='PANT1'][0])")
|
|
SEC=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([i['id'] for i in d['items'] if i['sku']=='SEC1'][0])")
|
|
check "lastBackup stamped" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; print(json.load(sys.stdin)['facility']['lastBackup'])")" '^20'
|
|
|
|
echo "== manager approvals + nursing issue"
|
|
R=$(mut approval.add "{\"staffId\":\"$NINA\",\"by\":\"J. Barnes, Ward 3A\",\"sets\":\"3\",\"fte\":\"1.0\"}"); check "approval add" "$R" '"id"'; AP=$(echo "$R" | jget '["result"]["id"]')
|
|
check "approval needs by" "$(mut approval.add "{\"staffId\":\"$NINA\",\"by\":\"\",\"sets\":\"3\"}")" 'required'
|
|
R=$(mut issue.create "{\"staffId\":\"$NINA\",\"apDeduct\":2,\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"qty\":2,\"src\":\"stock\"},{\"itemId\":\"$PANT\",\"si\":1,\"qty\":2,\"src\":\"stock\"}]}")
|
|
check "nursing issue (no limit, 4 items)" "$R" '"stock":4'
|
|
check "approval deducted 2, 1 remaining" "$R" '"apDeducted":2,"apRemaining":1'
|
|
check "second approval" "$(mut approval.add "{\"staffId\":\"$NINA\",\"by\":\"K. Lee\",\"sets\":\"2\"}")" '"id"'
|
|
# Ask for fewer sets than are open — 1 left on Barnes, 2 on Lee — so the split shows. Draining both
|
|
# in one go proves nothing about order; taking 1+1 does, and both approvals were raised the same day,
|
|
# so the only thing deciding which one moves first is the order they were entered in.
|
|
R=$(mut issue.create "{\"staffId\":\"$NINA\",\"apDeduct\":2,\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}")
|
|
check "deduct of 2 rolls across approvals (1+1)" "$R" '"apDeducted":2,"apRemaining":1'
|
|
check "oldest approval drained first" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print(' '.join(a['byName']+'='+str(a['used']) for a in sorted(d['approvals'], key=lambda a: a['byName'])))")" '^J\. Barnes, Ward 3A=3 K\. Lee=1$'
|
|
# Asking for more sets than the manager has signed for takes what is open and stops there.
|
|
R=$(mut issue.create "{\"staffId\":\"$NINA\",\"apDeduct\":5,\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}")
|
|
check "deduct clamped to the 1 set still open" "$R" '"apDeducted":1,"apRemaining":0'
|
|
R=$(mut issue.create "{\"staffId\":\"$NINA\",\"apDeduct\":1,\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}")
|
|
check "no open approval -> 0 deducted" "$R" '"apDeducted":0'
|
|
|
|
echo "== the six-set ceiling + order-in supplier from product"
|
|
# Seven shirts ordered in for somebody holding nothing is one past the six tops anyone may hold.
|
|
# Sam's own figure of 2 on the register is a yearly reporting number now, so it is the ceiling that
|
|
# has to refuse this — and refusing it leaves no order behind to move the reorder figures below.
|
|
R=$(mut issue.create "{\"staffId\":\"$SAM\",\"lines\":[{\"itemId\":\"$SEC\",\"si\":0,\"qty\":7,\"src\":\"order\"}]}")
|
|
check "server refuses a seventh top without an override (six sets held is the ceiling)" "$R" 'the most anyone holds is 6 sets'
|
|
# One Security Shirt L on the shelf and two cart lines asking for one each: either line would pass on
|
|
# its own, so only a check that adds the lines up first refuses this. Asking 2+2, as this used to, is
|
|
# refused just as fast by a naive per-line check, which left the rule the label names untested.
|
|
R=$(mut issue.create "{\"staffId\":\"$SAM\",\"lines\":[{\"itemId\":\"$SEC\",\"si\":1,\"qty\":1,\"src\":\"stock\"},{\"itemId\":\"$SEC\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}")
|
|
check "cumulative shelf check (1 on hand, 1+1 asked)" "$R" 'Not enough on the shelf'
|
|
R=$(mut issue.create "{\"staffId\":\"$SAM\",\"lines\":[{\"itemId\":\"$SEC\",\"si\":1,\"qty\":1,\"src\":\"stock\"},{\"itemId\":\"$PANT\",\"si\":0,\"qty\":1,\"src\":\"order\",\"supplier\":\"WRONG\"}]}")
|
|
check "issue stock + order-in" "$R" '"stock":1,"ordered":1'
|
|
check "order-in cc uses staff dept (no override)" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print([o['cc'] for o in d['orders'] if o['orderFor']=='Staff Member'][0])")" 'Security'
|
|
BK=$(curl -s -b "$J" "$B/api/backup")
|
|
check "order-in used product supplier (Harbour), not client value" "$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([o['supplier'] for o in d['orders'] if o['orderFor']=='Staff Member'])")" 'Harbour'
|
|
check "replenish drafts per supplier" "$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print(sorted(o['supplier'] for o in d['orders'] if o['replenish']))")" "Harbour Embroidery', 'Northline Workwear"
|
|
|
|
echo "== order flagged (2x reorder, net of on-order) + duplicate"
|
|
R=$(mut stock.orderFlagged '{}'); check "order flagged added lines" "$R" '"added":[1-9]'
|
|
# "added" is the count of need lines the merge looked at, not the count it changed, so it reads the
|
|
# same on the second run whether the merge tops a line up or piles onto it. The draft's own line count
|
|
# and total are the only things that tell those two apart.
|
|
qn(){ curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['replenish'] and o['supplier']=='Northline Workwear'][0]; print(len(o['lines']), sum(l['qty'] for l in o['lines']))"; }
|
|
Q1=$(qn)
|
|
check "the draft the merge writes into can be read" "$Q1" '^[0-9][0-9]* [0-9][0-9]*$'
|
|
mut stock.orderFlagged '{}' >/dev/null
|
|
check "order flagged idempotent (max merge)" "$(qn)" "^$Q1\$"
|
|
BK=$(curl -s -b "$J" "$B/api/backup")
|
|
ORD=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print([o['id'] for o in d['orders'] if o['replenish'] and o['supplier']=='Northline Workwear'][0])")
|
|
SECQ=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['replenish'] and o['supplier']=='Northline Workwear'][0]; print([l['qty'] for l in o['lines'] if l['itemId']=='$SEC' and l['size']=='L'][0])")
|
|
check "SEC L topped to 2x reorder (draft line itself not netted: 4-0=4)" "$SECQ" '^4$'
|
|
R=$(mut order.duplicate "{\"id\":\"$ORD\"}"); check "duplicate order" "$R" '"code":"ORD-'
|
|
DUP=$(echo "$R" | jget '["result"]["id"]')
|
|
check "receive on a draft rejected" "$(mut order.receive "{\"id\":\"$DUP\",\"lines\":[]}")" 'Mark the order as ordered'
|
|
check "dup is Draft with notes" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['id']=='$DUP'][0]; print(o['status'], o['notes'])")" 'Draft Duplicated'
|
|
|
|
echo "== staff patch / alterations / catalog addSize / discontinue"
|
|
check "staff deactivate" "$(mut staff.patch "{\"id\":\"$SAM\",\"inactive\":true}")" '"ok":true'
|
|
# ok:true only says a row was written. The flag is there to stop the next issue at the counter, so ask
|
|
# for one — without this, dropping the write to `inactive` leaves the section green.
|
|
check "an inactive staff member can't be issued to" "$(mut issue.create "{\"staffId\":\"$SAM\",\"lines\":[{\"itemId\":\"$SEC\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}")" 'inactive'
|
|
check "staff cc override" "$(mut staff.patch "{\"id\":\"$NINA\",\"ccOverride\":\"RGH-5090\"}")" '"ok":true'
|
|
check "cc override recorded on the staff row" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print([s['ccOverride'] for s in d['staff'] if s['num']=='1001'][0])")" '^RGH-5090$'
|
|
check "staff num immutable" "$(mut staff.save "{\"id\":\"$NINA\",\"num\":\"9999\",\"first\":\"Nina\",\"last\":\"Nurse\"}")" "can't be changed"
|
|
R=$(mut alteration.add "{\"staffId\":\"$NINA\",\"garment\":\"Scrub pants (M)\",\"desc\":\"hem 4cm\"}"); check "alteration add" "$R" '"id"'; ALT=$(echo "$R" | jget '["result"]["id"]')
|
|
check "alteration advance" "$(mut alteration.advance "{\"id\":\"$ALT\"}")" '"ok":true'
|
|
check "alteration at tailor" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['alterations'][0]['status'])")" 'At tailor'
|
|
check "add size" "$(mut catalog.update "{\"id\":\"$TOP\",\"addSize\":\"XL\"}")" '"ok":true'
|
|
check "dup size rejected" "$(mut catalog.update "{\"id\":\"$TOP\",\"addSize\":\"XL\"}")" 'already'
|
|
check "discontinue" "$(mut catalog.update "{\"id\":\"$SEC\",\"archived\":true}")" '"ok":true'
|
|
check "delete item with history blocked" "$(mut catalog.delete "{\"id\":\"$SEC\"}")" 'Discontinue'
|
|
|
|
echo "== settings + logo"
|
|
check "settings finance" "$(mut settings.update '{"glAccount":"631020","journalDesc":"Uniform issues","exceptionHigh":"3"}')" '"ok":true'
|
|
check "logo reject non-image" "$(mut settings.update '{"logoData":"data:text/html;base64,PGI+"}')" 'must be'
|
|
check "logo rejects svg" "$(mut settings.update '{"logoData":"data:image/svg+xml;base64,PHN2Zz48L3N2Zz4="}')" 'must be'
|
|
check "logo accept png" "$(mut settings.update '{"logoData":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="}')" '"ok":true'
|
|
check "logo route serves png" "$(curl -s -b "$J" -o /dev/null -w '%{content_type}' "$B/api/logo")" 'image/png'
|
|
check "supplier remove blocked (in use)" "$(mut supplier.remove "{\"id\":\"$SUP\"}")" "can't be removed"
|
|
|
|
echo "== stocktake"
|
|
R=$(mut stocktake.apply "{\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"counted\":3},{\"itemId\":\"$PANT\",\"si\":1,\"counted\":8}]}")
|
|
check "stocktake apply: 2 counted, 1 variance filed" "$R" '"counted":2,"variances":1'
|
|
# The tally above is paperwork. Ten RN tops came in and five have been issued, so the shelf believes 5
|
|
# and the counter found 3 — unless the count writes that gap onto the level, the same two garments go
|
|
# missing again next month. -2 is the adjustment the count wrote, not the number counted.
|
|
check "stocktake moved the shelf (5 on hand, counted 3)" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print([s['adj'] for s in d['stock'] if s['itemId']=='$TOP' and s['sizeIndex']==1][0])")" '^-2$'
|
|
R=$(mut stocktake.apply "{\"lines\":[{\"itemId\":\"$PANT\",\"si\":1,\"counted\":8}]}")
|
|
check "clean count can be filed (0 variances)" "$R" '"counted":1,"variances":0'
|
|
|
|
echo "== pages render (200)"
|
|
for p in /app /app/stock "/app/stock/$TOP" /app/issue /app/stocktake /app/orders "/app/orders/$ORD" /app/report /app/staff "/app/staff/$NINA" /app/settings "/print?type=collection&staffName=Nina"; do
|
|
C=$(curl -s -b "$J" -o /dev/null -w '%{http_code}' "$B$p"); [ "$C" = "200" ] && ok "GET $p" || fail "GET $p" "$C"
|
|
done
|
|
check "slip prints logo img" "$(curl -s -b "$J" "$B/print?type=collection")" 'data:image/png'
|
|
|
|
echo "== cost at issue, lead time, back-order parent, admin gates"
|
|
BK=$(curl -s -b "$J" "$B/api/backup")
|
|
check "issues carry cost at issue (30.0 for TOP)" "$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); print(sorted(set(i['cost'] for i in d['issues'] if i['itemId']=='$TOP')))")" '\[30'
|
|
check "replenish draft expected = today+10 (Northline lead 10)" "$(echo "$BK" | python3 -c "import sys,json,datetime; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['replenish'] and o['supplier']=='Northline Workwear'][0]; print((datetime.date.fromisoformat(o['expected'])-datetime.date.fromisoformat(o['date'])).days)")" '^10$'
|
|
# The other branch, read off the order it belongs to: Harbour was created by the catalogue import and
|
|
# nobody has given it a lead time, so the order-in raised for a staff member falls back to a fortnight.
|
|
check "order-in expected = today+14 (Harbour has no lead)" "$(echo "$BK" | python3 -c "import sys,json,datetime; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['orderFor']=='Staff Member'][0]; print((datetime.date.fromisoformat(o['expected'])-datetime.date.fromisoformat(o['date'])).days)")" '^14$'
|
|
check "mark dup ordered" "$(mut order.status "{\"id\":\"$DUP\",\"status\":\"Ordered\"}")" '"ok":true'
|
|
LINE=$(echo "$BK" | python3 -c "import sys,json; d=json.load(sys.stdin); o=[o for o in d['orders'] if o['id']=='$DUP'][0]; l=o['lines'][0]; print(l['id'], l['qty'])")
|
|
LID=${LINE% *}; LQ=${LINE#* }
|
|
R=$(mut order.receive "{\"id\":\"$DUP\",\"lines\":[{\"lineId\":\"$LID\",\"arrived\":1,\"dest\":\"shelf\"}]}")
|
|
check "short receive ok" "$R" '"ok":true'
|
|
check "back order links to parent via parentId" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print([o['parentId']=='$DUP' for o in d['orders'] if o['status']=='Back Order'])")" 'True'
|
|
check "second receive rejected" "$(mut order.receive "{\"id\":\"$DUP\",\"lines\":[{\"lineId\":\"$LID\",\"arrived\":1}]}")" 'receiv'
|
|
R=$(mut users.add "{\"email\":\"issuer$TS@example.com\",\"password\":\"password123\",\"first\":\"Iss\",\"last\":\"Uer\",\"role\":\"ISSUER\"}"); check "add issuer" "$R" '"id"'
|
|
J2=${TMP:-/tmp}/tc-cj2.txt; rm -f "$J2"
|
|
curl -s -c "$J2" -X POST "$B/api/auth/login" -H 'content-type: application/json' -d "{\"email\":\"issuer$TS@example.com\",\"password\":\"password123\"}" >/dev/null
|
|
check "issuer cannot set reorder" "$(curl -s -b "$J2" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"stock.reorder\",\"payload\":{\"itemId\":\"$TOP\",\"si\":0,\"reorder\":9}}")" 'Admin only'
|
|
check "issuer cannot bind barcode" "$(curl -s -b "$J2" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"barcode.bind\",\"payload\":{\"code\":\"123\",\"itemId\":\"$TOP\",\"si\":0}}")" 'Admin only'
|
|
check "issuer can still issue" "$(curl -s -b "$J2" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"issue.create\",\"payload\":{\"staffId\":\"$NINA\",\"lines\":[{\"itemId\":\"$TOP\",\"si\":1,\"qty\":1,\"src\":\"stock\"}]}}")" '"stock":1'
|
|
|
|
echo "== backup restore round-trip"
|
|
BK=$(curl -s -b "$J" "$B/api/backup")
|
|
R=$(curl -s -b "$J" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"backup.restore\",\"payload\":$BK}")
|
|
check "restore ok" "$R" '"ok":true'
|
|
BK2=$(curl -s -b "$J" "$B/api/backup")
|
|
# Both approvals are fully spent by now. A row count still passes if the restore drops `used`, and a
|
|
# facility restored that way hands the wearer five manager-approved sets they have already been given —
|
|
# so read the balances back, not the tally.
|
|
check "restore kept suppliers/approvals/alterations" "$(echo "$BK2" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['suppliers']), sorted((a['sets'], a['used']) for a in d['approvals']), len(d['alterations']), [s['lead'] for s in d['suppliers'] if s['name']=='Northline Workwear'][0])")" '^2 \[(2, 2), (3, 3)\] 1 10$'
|
|
|
|
echo "== the revision the screens watch"
|
|
# Every open screen polls /api/rev and reloads only when the number moves, so two things have to
|
|
# hold: a mutation must move it, and the mutation must hand back the number it produced — without
|
|
# that, the screen that made the change refreshes itself a second time when it next polls.
|
|
REV1=$(curl -s -b "$J" "$B/api/rev" | jget '["rev"]')
|
|
check "a signed-in coordinator can read it" "$REV1" '^[0-9][0-9]*$'
|
|
R=$(mut dept.save '{"name":"Rev Ward","cc":"RGH-3030"}')
|
|
check "a mutation hands back the new revision" "$R" '"rev":[0-9]'
|
|
REV2=$(curl -s -b "$J" "$B/api/rev" | jget '["rev"]')
|
|
check "and the number it hands back is the one on the facility" "$(echo "$R" | jget '["rev"]')" "^$REV2\$"
|
|
check "which has moved" "$(python3 -c "print('up' if $REV2 > $REV1 else 'stuck')")" '^up$'
|
|
# A read is not a change: polling must not make the poll look busy.
|
|
curl -s -b "$J" "$B/api/backup" > /dev/null
|
|
check "reading changes nothing" "$(curl -s -b "$J" "$B/api/rev" | jget '["rev"]')" "^$REV2\$"
|
|
check "and nobody signed in gets a number at all" "$(curl -s "$B/api/rev")" 'Not signed in'
|
|
|
|
echo "== wipe"
|
|
check "wipe" "$(mut data.wipeActivity '{"confirm":"WIPE"}')" '"ok":true'
|
|
check "wipe cleared approvals" "$(curl -s -b "$J" "$B/api/backup" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d['approvals']), len(d['issues']))")" '^0 0$'
|
|
|
|
echo; echo "PASS=$PASS FAIL=$FAIL"
|
|
[ $FAIL -eq 0 ]
|