A per-court signed link from the desk opens a full-screen scoring page with big +/- buttons, undo, best-of sets, and a confirmed Finalize step; it follows whatever match is on the court and can never edit a finished result. Public court cards show the estimated finish time. Release 0.4.0. Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_01MB7nCCAscYsb3zzkT6LHZi
76 lines
7.1 KiB
Bash
Executable File
76 lines
7.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# End-to-end smoke test against a running server. Usage: BASE=http://localhost:3000 ADMIN_PASSWORD=... test/smoke.sh
|
|
set -euo pipefail
|
|
BASE=${BASE:-http://localhost:3123}
|
|
PW=${ADMIN_PASSWORD:-test}
|
|
J=$(mktemp); trap 'rm -f $J' EXIT
|
|
c() { curl -s -b "$J" -c "$J" "$@"; }
|
|
fail() { echo "FAIL: $*"; exit 1; }
|
|
|
|
echo "· login"
|
|
c -o /dev/null -w '%{http_code}' -X POST "$BASE/admin/login" --data-urlencode "password=$PW" --data-urlencode "next=/admin" | grep -q 303 || fail login
|
|
echo "· create tournament"
|
|
LOC=$(c -o /dev/null -w '%{redirect_url}' -X POST "$BASE/admin/new" --data-urlencode "name=Smoke Test 2s" --data-urlencode "courtCount=2" --data-urlencode "teamSize=2" --data-urlencode "pointsTo=21" --data-urlencode "winBy=2" --data-urlencode "cap=25" --data-urlencode "bestOf=1" --data-urlencode "stages=pool,bracket")
|
|
SLUG=$(echo "$LOC" | sed -E 's#.*/admin/t/([^?]+).*#\1#'); echo " slug=$SLUG"
|
|
echo "· public register 8 teams"
|
|
for n in "Net Gains" "Block Party" "Dig It" "Kiss My Ace" "Setting Ducks" "The Spikers" "Sandbaggers" "Beach Please"; do
|
|
code=$(curl -s -o /dev/null -w '%{http_code}' -X POST "$BASE/t/$SLUG/register" --data-urlencode "name=$n" --data-urlencode "captain=Cap" --data-urlencode "players=2")
|
|
[ "$code" = 303 ] || fail "register $n -> $code"
|
|
done
|
|
curl -s -o /dev/null -w '%{http_code}\n' -X POST "$BASE/t/$SLUG/register" --data-urlencode "name=net gains" --data-urlencode "players=2" | grep -q 400 || fail "duplicate accepted"
|
|
curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));if(s.teams.length!==8)process.exit(1);if(JSON.stringify(s).includes("phone"))process.exit(2)' || fail "state.json teams/privacy"
|
|
echo "· close, pools, live"
|
|
c -o /dev/null -X POST "$BASE/admin/t/$SLUG/phase" --data-urlencode "phase=closed"
|
|
c -o /dev/null -w '%{redirect_url}\n' -X POST "$BASE/admin/t/$SLUG/generate" --data-urlencode "what=pools" --data-urlencode "poolSize=4" | grep -q msg= || fail pools
|
|
c -o /dev/null -w '%{redirect_url}\n' -X POST "$BASE/admin/t/$SLUG/phase" --data-urlencode "phase=live" | grep -q msg= || fail live
|
|
S=$(curl -s "$BASE/t/$SLUG/state.json")
|
|
echo "$S" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));if(s.phase!=="live"||s.courts.filter(c=>c.match).length!==2)process.exit(1);console.log(" courts:",s.courts.map(c=>`${c.court}: ${c.match.a.name} v ${c.match.b.name}`).join(" | "));console.log(" up next:",s.upNext.map(u=>`${u.a.name} v ${u.b.name} C${u.court} ~${u.etaMin}m`).join("; "))' || fail "live state"
|
|
echo "· live score then final on court 1"
|
|
M=$(echo "$S" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));console.log(s.courts[0].match.id)')
|
|
c -o /dev/null -H 'content-type: application/json' -X POST "$BASE/admin/t/$SLUG/live" -d "{\"match\":\"$M\",\"a\":18,\"b\":14}"
|
|
curl -s "$BASE/t/$SLUG/state.json" | grep -q '"live":\[18,14\]' || fail "live score not on board"
|
|
c -H 'content-type: application/json' -X POST "$BASE/admin/t/$SLUG/score" -d "{\"match\":\"$M\",\"sets\":\"21-14\"}" | grep -q '"ok":true' || fail "final score"
|
|
c -H 'content-type: application/json' -X POST "$BASE/admin/t/$SLUG/score" -d "{\"match\":\"$M\",\"sets\":\"21-20\"}" | grep -q 'win by 2' || fail "bad score accepted"
|
|
echo "· score keeper link scores and finalizes court 2"
|
|
KURL=$(c "$BASE/admin/t/$SLUG" | grep -oE "/keeper/$SLUG/2\?k=[A-Za-z0-9_-]+" | head -1); [ -n "$KURL" ] || fail "no keeper link on desk"
|
|
K=${KURL#*k=}
|
|
M2=$(curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));console.log(s.courts[1].match.id)')
|
|
[ "$(curl -s -o /dev/null -w '%{http_code}' "$BASE$KURL")" = 200 ] || fail "keeper page"
|
|
[ "$(curl -s -o /dev/null -w '%{http_code}' "$BASE/keeper/$SLUG/2?k=bad")" = 403 ] || fail "bad keeper token accepted"
|
|
curl -s -H 'content-type: application/json' -X POST "$BASE/keeper/$SLUG/2/live" -d "{\"k\":\"$K\",\"match\":\"$M2\",\"a\":20,\"b\":19,\"sets\":[]}" | grep -q '"ok":true' || fail "keeper live"
|
|
curl -s "$BASE/t/$SLUG/state.json" | grep -q '"live":\[20,19\]' || fail "keeper live score not on board"
|
|
curl -s -H 'content-type: application/json' -X POST "$BASE/keeper/$SLUG/2/final" -d "{\"k\":\"$K\",\"match\":\"$M2\",\"sets\":[[21,20]]}" | grep -q 'win by 2' || fail "keeper accepted invalid final"
|
|
curl -s -H 'content-type: application/json' -X POST "$BASE/keeper/$SLUG/2/final" -d "{\"k\":\"$K\",\"match\":\"$M2\",\"sets\":[[22,20]]}" | grep -q '"ok":true' || fail "keeper final"
|
|
curl -s -H 'content-type: application/json' -X POST "$BASE/keeper/$SLUG/2/final" -d "{\"k\":\"$K\",\"match\":\"$M2\",\"sets\":[[21,10]]}" | grep -q '"ok":false' || fail "keeper could re-finalize"
|
|
echo "· play out all pool matches"
|
|
for i in $(seq 1 20); do
|
|
NEXT=$(curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));const c=s.courts.find(c=>c.match);console.log(c?c.match.id:"")')
|
|
[ -z "$NEXT" ] && break
|
|
c -o /dev/null -H 'content-type: application/json' -X POST "$BASE/admin/t/$SLUG/score" -d "{\"match\":\"$NEXT\",\"sets\":\"21-$((10+i%9))\"}"
|
|
done
|
|
echo "· withdraw a team, bracket, finish"
|
|
TID=$(curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));console.log(s.teams[7].id)')
|
|
c -o /dev/null -X POST "$BASE/admin/t/$SLUG/withdraw" --data-urlencode "id=$TID" --data-urlencode "mode=forfeit"
|
|
c -o /dev/null -w '%{redirect_url}\n' -X POST "$BASE/admin/t/$SLUG/generate" --data-urlencode "what=bracket" --data-urlencode "perPool=2" --data-urlencode "wildcards=0" --data-urlencode "type=single" --data-urlencode "thirdPlace=1" | grep -q msg= || fail bracket
|
|
for i in $(seq 1 12); do
|
|
NEXT=$(curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));const c=s.courts.find(c=>c.match);console.log(c?c.match.id:"")')
|
|
[ -z "$NEXT" ] && break
|
|
c -o /dev/null -H 'content-type: application/json' -X POST "$BASE/admin/t/$SLUG/score" -d "{\"match\":\"$NEXT\",\"sets\":\"21-$((12+i%8))\"}"
|
|
done
|
|
curl -s "$BASE/t/$SLUG/state.json" | node -e 'const s=JSON.parse(require("fs").readFileSync(0));if(s.phase!=="final"||!s.champion)process.exit(1);console.log(" champion:",s.champion,"| withdrawn:",s.withdrawn.map(t=>t.name).join(","))' || fail "did not reach final"
|
|
echo "· pages render"
|
|
for p in "/" "/t/$SLUG" "/t/$SLUG/display" "/admin/t/$SLUG" "/admin/t/$SLUG/qr"; do
|
|
code=$(c -o /dev/null -w '%{http_code}' "$BASE$p"); [ "$code" = 200 ] || fail "$p -> $code"
|
|
done
|
|
CODE=$(c "$BASE/admin/t/$SLUG" | grep -oE '/team/[A-Z2-9]{4}' | head -1)
|
|
[ "$(curl -s -o /dev/null -w '%{http_code}' "$BASE/t/$SLUG$CODE")" = 200 ] || fail "team page"
|
|
echo "· websocket delivers state"
|
|
node -e '
|
|
const WebSocket=require("ws");const ws=new WebSocket(process.argv[1].replace("http","ws")+"/ws/"+process.argv[2]);
|
|
ws.on("message",m=>{const s=JSON.parse(m);console.log(" ws ok, phase",s.phase);ws.close();process.exit(0);});
|
|
ws.on("error",e=>{console.log("ws error",e.message);process.exit(1)});setTimeout(()=>process.exit(1),3000)' "$BASE" "$SLUG"
|
|
echo "· delete"
|
|
c -o /dev/null -X POST "$BASE/admin/t/$SLUG/delete" --data-urlencode "confirm=yes"
|
|
[ "$(curl -s -o /dev/null -w '%{http_code}' "$BASE/t/$SLUG")" = 404 ] || fail "not deleted"
|
|
echo "SMOKE OK"
|