#!/usr/bin/env bash # # run-panel-tests.sh — the admin-panel half of the release test gate. # # panel/package.json has declared a `test` script since the SPA was scaffolded # and nothing had ever called it: not release.yml, not build-shaterd.sh (which # only runs `npm ci` + `npm run build`). This script is what CI calls, and it # does the one thing `npm test` alone cannot: prove that tests actually RAN. # # `node --test src/*.test.ts` with no matching file leaves the glob unexpanded; # node then reports `pass 0` and exits 0 — a green CI step that ran nothing, # which is the exact failure class this whole change is about. So: the test # files are counted BEFORE the run (a rename to *.spec.ts is named as such # rather than showing up as a mystery), and the pass count is asserted > 0 and # the fail count 0 after it. # # KNOWN LIMIT: node --test counts a *.test.ts file that declares no cases at # all as one passing "test" (the module loaded). So an emptied-out file still # reads as pass 1 here. Deleting, renaming or breaking the file is caught; # gutting its contents while keeping the name is not. # # NODE VERSION: >= 22.6. The tests are TypeScript executed directly by # `node --test`; type stripping does not exist before then, so on node 20 the # run dies with a syntax error. CI pins node 24 for this step (the SPA *build* # still uses node 20 — that one goes through vite/tsc and does not care). # # Usage: scripts/run-panel-tests.sh set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" REPO="$(cd "$SCRIPT_DIR/.." && pwd)" cd "$REPO/panel" node_major="$(node -p 'process.versions.node.split(".")[0]')" node_minor="$(node -p 'process.versions.node.split(".")[1]')" if [ "$node_major" -lt 22 ] || { [ "$node_major" -eq 22 ] && [ "$node_minor" -lt 6 ]; }; then echo " ERROR: panel tests are TypeScript under \`node --test\` and need node >= 22.6" >&2 echo " (got $(node --version)). Type stripping does not exist before that." >&2 exit 1 fi # The glob `npm test` itself uses. Counted here so "somebody renamed the tests" # is reported as that, instead of as a suspiciously fast green step. shopt -s nullglob files=(src/*.test.ts) shopt -u nullglob if [ "${#files[@]}" -eq 0 ]; then echo " ERROR: no panel/src/*.test.ts — panel/package.json's \`test\` script" >&2 echo " would match nothing and still exit 0. Fix the glob or the files." >&2 exit 1 fi echo "== panel tests (node $(node --version)), ${#files[@]} file(s) ==" if [ ! -d node_modules ]; then npm ci fi out="" rc=0 set +e out="$(npm test --silent 2>&1)" rc=$? set -e sed 's/^/ /' <<<"$out" if [ "$rc" -ne 0 ]; then echo " FAILED: npm test exited $rc" >&2 exit 1 fi # node --test's summary is `ℹ pass N` (spec reporter) or `# pass N` (tap). passed="$(sed -n 's/.*[[:space:]]pass[[:space:]]\{1,\}\([0-9]\{1,\}\).*/\1/p' <<<"$out" | tail -1)" failed="$(sed -n 's/.*[[:space:]]fail[[:space:]]\{1,\}\([0-9]\{1,\}\).*/\1/p' <<<"$out" | tail -1)" if [ -z "$passed" ]; then echo " FAILED: could not find a pass count in node --test output — the gate" >&2 echo " cannot tell a green run from an empty one." >&2 exit 1 fi if [ "$passed" -lt 1 ]; then echo " FAILED: 0 panel tests ran. \`npm test\` returned success having done nothing." >&2 exit 1 fi if [ -n "$failed" ] && [ "$failed" -gt 0 ]; then echo " FAILED: $failed panel test(s) failed." >&2 exit 1 fi echo " OK: $passed panel test(s) passed."