ThreadCount Community edition

Uniform stock management for healthcare linen rooms. Licensed under the GNU AGPL v3.
This commit is contained in:
ThreadCount
2026-09-13 08:45:19 +10:00
commit 1bc2de655a
505 changed files with 56223 additions and 0 deletions
+165
View File
@@ -0,0 +1,165 @@
#!/usr/bin/env bash
# Plans, phase 1: the promise, the ceiling, read-only, and the console's billing desk.
#
# What it proves, in order:
# - with plans OFF (the default) a new facility is grandfathered: no staff ceiling at all;
# - an owner flips plans ON from the console, and a facility created after that is Hosted Small:
# the 61st staff record is refused, by the form and by the import, with the same words;
# - the console can set a room read-only: every write is refused with the one refusal, the
# backup and the plan ops still work, and "make writable" undoes it;
# - a trial and a payment record the dates, and "set free" clears them;
# - plans are turned OFF again at the end, so the next suite still gets grandfathered rooms.
#
# Needs a server in dev mode (Turnstile advisory) and DATABASE_URL in the environment for the
# operator seed, like e2e-ops.sh. Nothing here sends mail: the invoice request is a notice to the
# owner that swallows a missing SMTP.
set -u
B=${BASE:-http://127.0.0.1:3111}
. "$(dirname "$0")/e2e-preflight.sh"; e2e_preflight "$B"
T=${TMP:-/tmp}
OPS=ops.threadcount.tech
OJ="$T/tc-plan-op.txt"; J1="$T/tc-plan-a.txt"; J2="$T/tc-plan-b.txt"
rm -f "$OJ" "$J1" "$J2"
PASS=0; FAIL=0
ok() { PASS=$((PASS+1)); echo "$1"; }
fail() { FAIL=$((FAIL+1)); echo "$1 :: $(echo "$2" | head -c 240)"; }
check(){ local name=$1 out=$2 pat=$3; if echo "$out" | grep -q "$pat"; then ok "$name"; else fail "$name" "$out"; fi; }
no() { local name=$1 out=$2 pat=$3; if echo "$out" | grep -q "$pat"; then fail "$name" "$out"; else ok "$name"; fi; }
code() { curl -s -o /dev/null -w '%{http_code}' "$@"; }
mut() { local jar=$1 op=$2 payload=$3; curl -s -b "$jar" -c "$jar" -X POST "$B/api/mutate" -H 'content-type: application/json' -d "{\"op\":\"$op\",\"payload\":$payload}"; }
opost(){ curl -s -b "$OJ" -c "$OJ" -X POST -H "Host: $OPS" -H 'content-type: application/json' "$B$1" -d "$2"; }
control(){ opost /api/ops/controls "$1"; }
jget() { python3 -c "import sys,json; d=json.load(sys.stdin); print(eval('d$1'))"; }
signup(){ local jar=$1 name=$2 email=$3; curl -s -c "$jar" -X POST "$B/api/auth/signup" -H 'content-type: application/json' -H "x-forwarded-for: 10.7.$((RANDOM%250)).$((RANDOM%250))" -d "{\"first\":\"Plan\",\"last\":\"Tester\",\"facility\":\"$name\",\"email\":\"$email\",\"password\":\"password123\"}"; }
# N staff rows as an import payload: numbers 1..N.
rows() { python3 -c "import json,sys; n=int(sys.argv[1]); print(json.dumps({'kind':'staff','rows':[{'num':str(9000+i),'first':'P%d'%i,'last':'Row','group':'Kitchen','dept':'Kitchen'} for i in range(1,n+1)]}))" "$1"; }
TS=$(date +%s)
echo "== an owner at the console"
OE="plan-ops-$TS@example.com"
check "an operator is seeded" "$(OPERATOR_PASSWORD='e2e-operator-password-1' node scripts/create-operator.cjs "$OE" "Plan Ops" OWNER 2>&1)" "created operator $OE"
check "and signs in" "$(opost /api/ops/auth/login "{\"email\":\"$OE\",\"password\":\"e2e-operator-password-1\"}")" '"ok":true'
# Plans must start OFF for the first half; say so out loud if a previous run left them on.
check "plans are off to begin with" "$(control '{"action":"switch","key":"plansLive","value":false}')" '"ok":true'
echo "== plans off: the website still says free"
R=$(curl -s "$B/pricing")
check "pricing says that is the whole page" "$R" 'That is the whole page'
no " and nothing about a price" "$R" '>\$129<'
check "the home page states the price" "$(curl -s "$B/")" 'No licence, no per-device charge'
check "the footer chip says free to use" "$(curl -s "$B/")" 'Free to use</div>'
no "the sign-in page asks nothing about plans" "$(curl -s "$B/auth?mode=signup")" 'Hosted Facility'
check "the terms already carry the fees section" "$(curl -s "$B/terms")" '9. Fees'
echo "== plans off: a new facility is grandfathered"
check "signup A" "$(signup "$J1" "Plan Grandfathered $TS" "plan-a-$TS@example.com")" '"ok":true'
check " groups" "$(e2e_groups "$B" "$J1")" '"ok":true'
R=$(mut "$J1" import.rows "$(rows 65)")
check " 65 staff import with no ceiling" "$R" '"created":65'
no " and no refusal in the errors" "$R" 'register is full'
check " the 66th by the form" "$(mut "$J1" staff.save '{"num":"9066","first":"Sixty","last":"Six","group":"Kitchen","dept":"Kitchen"}')" '"id"'
echo "== plans on"
check "the owner turns plans on" "$(control '{"action":"switch","key":"plansLive","value":true}')" '"ok":true'
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/controls")
check " and the switches page says live" "$R" 'Live'
echo "== plans on: the website tells the plans story"
R=$(curl -s "$B/pricing")
check "pricing says free to run" "$R" 'Free to run'
check " and names the price" "$R" '>\$129<'
check " and the health service bundle" "$R" '\$4,990'
check " and keeps the founder's sentence" "$R" 'charging for it was never the point'
no " and no longer says that is the whole page" "$R" 'That is the whole page'
check " the title is still Pricing" "$R" '<title>Pricing'
R=$(curl -s "$B/")
check "the home page band changed" "$R" 'Paid to host'
no " and no longer says no licence" "$R" 'No licence, no per-device charge'
check " structured data carries the hosted offer" "$R" '"price":"1290"'
check "the footer chip says free to run" "$R" 'Free to run</div>'
R=$(curl -s "$B/faq")
check "the FAQ answer changed" "$R" 'The software is. Run it on your own server'
check "the about page's last line changed" "$(curl -s "$B/about")" 'Hosting it for you is what costs money'
no "the support heading no longer says free" "$(curl -s "$B/support")" 'is free and maintained'
check "the terms carry the fees section" "$(curl -s "$B/terms")" '9. Fees'
check " and the grandfathering" "$(curl -s "$B/terms")" 'is grandfathered: hosted free'
check "the sign-in page offers the plan choice" "$(curl -s "$B/auth?mode=signup")" 'Hosted Facility'
check "the phone sign-up offers it too" "$(curl -s "$B/m/signup")" 'Hosted Small'
echo "== plans on: a trial can be chosen at sign-up"
J4="$T/tc-plan-d.txt"; rm -f "$J4"
check "signup D on a Hosted Facility trial" "$(curl -s -c "$J4" -X POST "$B/api/auth/signup" -H 'content-type: application/json' -H "x-forwarded-for: 10.6.$((RANDOM%250)).$((RANDOM%250))" -d "{\"first\":\"Plan\",\"last\":\"Trial\",\"facility\":\"Plan Trial $TS\",\"email\":\"plan-d-$TS@example.com\",\"password\":\"password123\",\"plan\":\"hosted_facility\"}")" '"ok":true'
check " groups" "$(e2e_groups "$B" "$J4")" '"ok":true'
check " no ceiling on a trial" "$(mut "$J4" import.rows "$(rows 61)")" '"created":61'
FD=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities" | grep -o "href=\"/ops/facilities/[a-z0-9]\{20,40\}\"[^>]*>Plan Trial $TS<" | head -1 | sed 's/.*facilities\/\([a-z0-9]*\)".*/\1/')
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FD")
check " the console shows Hosted Facility" "$R" 'Hosted Facility'
check " on trial" "$R" '>Trial<'
check " a nonsense plan lands on Hosted Small" "$(J5="$T/tc-plan-e.txt"; rm -f "$J5"; curl -s -c "$J5" -X POST "$B/api/auth/signup" -H 'content-type: application/json' -H "x-forwarded-for: 10.5.$((RANDOM%250)).$((RANDOM%250))" -d "{\"first\":\"Plan\",\"last\":\"Odd\",\"facility\":\"Plan Odd $TS\",\"email\":\"plan-e-$TS@example.com\",\"password\":\"password123\",\"plan\":\"gold\"}" >/dev/null; e2e_groups "$B" "$J5" >/dev/null; mut "$J5" import.rows "$(rows 61)")" '"created":60'
echo "== plans on: a new facility is Hosted Small"
check "signup B" "$(signup "$J2" "Plan Small $TS" "plan-b-$TS@example.com")" '"ok":true'
check " groups" "$(e2e_groups "$B" "$J2")" '"ok":true'
R=$(mut "$J2" staff.save '{"num":"8001","first":"First","last":"Person","group":"Kitchen","dept":"Kitchen"}')
check " one record by the form" "$R" '"id"'; S1=$(echo "$R" | jget '["result"]["id"]')
R=$(mut "$J2" import.rows "$(rows 65)")
check " 65 rows import 59 more" "$R" '"created":59'
check " and say why once" "$R" 'register is full for this plan'
check " skipping the rest" "$R" '"skipped":6'
check " the 61st by the form is refused" "$(mut "$J2" staff.save '{"num":"9099","first":"Sixty","last":"One","group":"Kitchen","dept":"Kitchen"}')" 'register is full'
check " editing an existing record still works" "$(mut "$J2" staff.save "{\"id\":\"$S1\",\"num\":\"8001\",\"first\":\"First\",\"last\":\"Renamed\",\"group\":\"Kitchen\",\"dept\":\"Kitchen\"}")" "\"id\":\"$S1\""
check " a coordinator's other writes are untouched" "$(mut "$J2" dept.save '{"name":"Willow","cc":"CC-1"}')" '"ok":true'
check " facility A is still grandfathered" "$(mut "$J1" staff.save '{"num":"9067","first":"Sixty","last":"Seven","group":"Kitchen","dept":"Kitchen"}')" '"id"'
echo "== the console sees the plan"
FB=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities" | grep -o "href=\"/ops/facilities/[a-z0-9]\{20,40\}\"[^>]*>Plan Small $TS<" | head -1 | sed 's/.*facilities\/\([a-z0-9]*\)".*/\1/')
if [ -n "$FB" ]; then ok "facility B's id from the list"; else fail "facility B's id from the list" "not found"; fi
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FB")
check " its page says Hosted Small" "$R" 'Hosted Small'
check " and a ceiling of 60" "$R" '>60<'
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities")
check " the list says free" "$R" 'Hosted Small · free'
check " and grandfathered for A" "$R" 'Free · grandfathered'
echo "== read-only"
check "the console sets B read-only" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"readonly\",\"on\":true}")" '"ok":true'
check " a write is refused" "$(mut "$J2" dept.save '{"name":"Maple","cc":"CC-2"}')" 'Read-only'
check " with a 403" "$(curl -s -o /dev/null -w '%{http_code}' -b "$J2" -X POST "$B/api/mutate" -H 'content-type: application/json' -d '{"op":"dept.save","payload":{"name":"Oak","cc":"CC-3"}}')" '403'
check " the backup still downloads" "$(code -b "$J2" "$B/api/backup")" '200'
check " the billing contact can still be set" "$(mut "$J2" plan.billing '{"email":"accounts@example.com"}')" '"ok":true'
check " and an invoice requested" "$(mut "$J2" plan.invoice '{"email":"accounts@example.com","plan":"hosted_facility"}')" '"ok":true'
check " but not twice a day, four times" "$(mut "$J2" plan.invoice '{}' >/dev/null; mut "$J2" plan.invoice '{}' >/dev/null; mut "$J2" plan.invoice '{}')" 'already been requested'
check " the coordinator's own password op stays open" "$(mut "$J2" me.password '{"current":"password123","next":"password1234"}')" '"ok":true'
check " the list shows it in red" "$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities")" 'Hosted Small · read-only'
check "and makes it writable again" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"readonly\",\"on\":false}")" '"ok":true'
check " a write goes through" "$(mut "$J2" dept.save '{"name":"Maple","cc":"CC-2"}')" '"ok":true'
check " the ceiling is back too" "$(mut "$J2" staff.save '{"num":"9098","first":"Sixty","last":"One","group":"Kitchen","dept":"Kitchen"}')" 'register is full'
echo "== trial, payment, free"
check "a 60-day trial starts" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"trial\",\"days\":60}")" '"ok":true'
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FB")
check " the page says Hosted Facility" "$R" 'Hosted Facility'
check " on trial" "$R" '>Trial<'
check " the ceiling is lifted" "$(mut "$J2" staff.save '{"num":"9098","first":"Sixty","last":"One","group":"Kitchen","dept":"Kitchen"}')" '"id"'
check "a payment is recorded" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"paid\",\"months\":12}")" '"ok":true'
check " the page says Paid" "$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FB")" '>Paid<'
check "set free clears it" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"free\"}")" '"ok":true'
check " and the page says Free" "$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FB")" 'Recorded as</dt><dd[^>]*>free'
check "an owner sets Health Service and grandfathers" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"set\",\"plan\":\"health_service\",\"planNote\":\"PO 1\",\"grandfathered\":true}")" '"ok":true'
R=$(curl -s -b "$OJ" -H "Host: $OPS" "$B/ops/facilities/$FB")
check " the page says Health Service" "$R" 'Health Service'
check " and grandfathered" "$R" 'grandfathered'
check " the note is shown" "$R" 'PO 1'
check " a bad plan code is refused" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"set\",\"plan\":\"gold\",\"planNote\":\"\"}")" 'Not a plan'
check " a grandfathered room has no ceiling even on read-only's neighbour, free" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"set\",\"plan\":\"hosted_small\",\"planNote\":\"\",\"grandfathered\":true}" >/dev/null; mut "$J2" staff.save '{"num":"9097","first":"Sixty","last":"Two","group":"Kitchen","dept":"Kitchen"}')" '"id"'
check " taking the promise away brings the ceiling back" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"set\",\"plan\":\"hosted_small\",\"planNote\":\"\",\"grandfathered\":false}" >/dev/null; mut "$J2" staff.save '{"num":"9096","first":"Sixty","last":"Three","group":"Kitchen","dept":"Kitchen"}')" 'register is full'
check " read-only beats the promise" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"set\",\"plan\":\"hosted_small\",\"planNote\":\"\",\"grandfathered\":true}" >/dev/null; control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"readonly\",\"on\":true}" >/dev/null; mut "$J2" dept.save '{"name":"Elm","cc":"CC-5"}')" 'Read-only'
echo "== plans off again"
check "the owner turns plans off" "$(control '{"action":"switch","key":"plansLive","value":false}')" '"ok":true'
check " a facility created now is grandfathered" "$(J3="$T/tc-plan-c.txt"; rm -f "$J3"; signup "$J3" "Plan After $TS" "plan-c-$TS@example.com" >/dev/null; e2e_groups "$B" "$J3" >/dev/null; mut "$J3" import.rows "$(rows 61)")" '"created":61'
check " facility B is still read-only from the owner's last act" "$(mut "$J2" dept.save '{"name":"Birch","cc":"CC-4"}')" 'Read-only'
check " and made writable to finish" "$(control "{\"action\":\"plan\",\"facilityId\":\"$FB\",\"act\":\"readonly\",\"on\":false}")" '"ok":true'
echo; echo "plan: $PASS passed, $FAIL failed"
[ "$FAIL" -eq 0 ]