test(19.5): gate the bm callback view test

- detect_vitest takes REQUIRE_FILES (REQUIRE_FILE stays a one-entry
  fallback); run_bm requires src/lib/oauth.test.ts and
  src/views/OAuthCallbackView.test.ts to pass by name
- self-test: the vitest cases run on both files and a planted run without
  the view test exits 5 (missing-view-file)
This commit is contained in:
Jakub Zych
2026-10-21 14:15:51 +02:00
parent c2c8c6a2f9
commit 7e4387311e

View File

@@ -27,8 +27,9 @@
# -short . with its social login tests by required name.
# bm the student application: go build ./..., go vet ., go test
# -short . and of plugins/jz/bm by required name, then the
# frontend's vitest run (oauth.test.ts must pass) and its
# typecheck.
# frontend's vitest run (src/lib/oauth.test.ts and, from plan
# 05, the callback view test src/views/OAuthCallbackView.test.ts
# must pass, D-28) and its typecheck.
# fonoteka the application workspace that carries the user plugin:
# go vet ./... and go test -short ./..., and its summer.yaml
# does not mount golem15.sociallogin.
@@ -48,7 +49,7 @@
# that fails, skips a test (a subtest included), matches zero tests, prints
# "no tests to run" or misses a required name (a subtest pass never counts
# for its parent's name). The vitest detector refuses a run without a
# passing oauth.test.ts or with any skipped or failed test. --self-test
# passing line for every required file or with any skipped or failed test. --self-test
# proves the detectors on planted output and the docker NOT RUN rule on a
# faked PATH without a working docker.
#
@@ -233,13 +234,14 @@ PY
}
# detect_vitest reads a vitest run's output. Exit 1 failed or skipped
# tests, 3 no summary, 5 the required file (REQUIRE_FILE) did not pass.
# tests, 3 no summary, 5 a required file did not pass. REQUIRE_FILES is a
# space-separated list; REQUIRE_FILE is read as a one-entry fallback.
detect_vitest() {
python3 - "$1" <<'PY'
import os, re, sys
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
need = os.environ.get("REQUIRE_FILE", "")
need = os.environ.get("REQUIRE_FILES", "").split() or os.environ.get("REQUIRE_FILE", "").split()
if re.search(r"\bfailed\b|\bskipped\b|\btodo\b|\bFAIL\b", text):
print("refuse: vitest reports failed, skipped or todo tests", file=sys.stderr)
sys.exit(1)
@@ -247,8 +249,9 @@ m = re.search(r"^\s*Tests\s+(\d+) passed \((\d+)\)\s*$", text, re.M)
if not m or m.group(1) != m.group(2) or m.group(1) == "0":
print("refuse: no all-passing vitest summary", file=sys.stderr)
sys.exit(3)
if need and not re.search(r"^\s*✓ " + re.escape(need) + r" \(\d+ tests?\)", text, re.M):
print(f"refuse: {need} did not pass", file=sys.stderr)
missing = [f for f in need if not re.search(r"^\s*✓ " + re.escape(f) + r" \(\d+ tests?\)", text, re.M)]
if missing:
print("refuse: did not pass: " + ", ".join(missing), file=sys.stderr)
sys.exit(5)
PY
}
@@ -421,16 +424,20 @@ run_self_test() {
'{"Action":"pass","Package":"p","Test":"TestOuter/TestLink"}
{"Action":"pass","Package":"p","Test":"TestOther"}'
local vitest_ok vitest_skip vitest_fail vitest_missing
vitest_ok=$' ✓ src/lib/oauth.test.ts (10 tests) 14ms\n ✓ src/pwa/pwa.test.ts (11 tests) 17ms\n\n Test Files 2 passed (2)\n Tests 21 passed (21)'
vitest_skip=$' ✓ src/lib/oauth.test.ts (10 tests | 1 skipped) 14ms\n\n Test Files 1 passed (1)\n Tests 9 passed | 1 skipped (10)'
vitest_fail=$' ❯ src/lib/oauth.test.ts (10 tests | 1 failed) 14ms\n\n Test Files 1 failed (1)\n Tests 1 failed | 9 passed (10)'
local vitest_ok vitest_skip vitest_fail vitest_missing vitest_no_view
local vitest_files="src/lib/oauth.test.ts src/views/OAuthCallbackView.test.ts"
vitest_ok=$' ✓ src/lib/oauth.test.ts (10 tests) 14ms\n ✓ src/views/OAuthCallbackView.test.ts (5 tests) 204ms\n ✓ src/pwa/pwa.test.ts (11 tests) 17ms\n\n Test Files 3 passed (3)\n Tests 26 passed (26)'
vitest_skip=$' ✓ src/lib/oauth.test.ts (10 tests | 1 skipped) 14ms\n ✓ src/views/OAuthCallbackView.test.ts (5 tests) 204ms\n\n Test Files 2 passed (2)\n Tests 14 passed | 1 skipped (15)'
vitest_fail=$' ✓ src/lib/oauth.test.ts (10 tests) 14ms\n ❯ src/views/OAuthCallbackView.test.ts (5 tests | 1 failed) 204ms\n\n Test Files 1 failed | 1 passed (2)\n Tests 1 failed | 14 passed (15)'
vitest_missing=$' ✓ src/pwa/pwa.test.ts (11 tests) 17ms\n\n Test Files 1 passed (1)\n Tests 11 passed (11)'
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest pass 0 "$vitest_ok"
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest skip 1 "$vitest_skip"
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest fail 1 "$vitest_fail"
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest missing-file 5 "$vitest_missing"
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest no-summary 3 ' ✓ src/lib/oauth.test.ts (10 tests) 14ms'
vitest_no_view=$' ✓ src/lib/oauth.test.ts (10 tests) 14ms\n ✓ src/pwa/pwa.test.ts (11 tests) 17ms\n\n Test Files 2 passed (2)\n Tests 21 passed (21)'
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest pass 0 "$vitest_ok"
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest skip 1 "$vitest_skip"
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest fail 1 "$vitest_fail"
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest missing-file 5 "$vitest_missing"
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest no-summary 3 ' ✓ src/lib/oauth.test.ts (10 tests) 14ms'
REQUIRE_FILES="$vitest_files" expect_detector detect_vitest missing-view-file 5 "$vitest_no_view"
REQUIRE_FILE="src/lib/oauth.test.ts" expect_detector detect_vitest single-file-fallback 5 "$vitest_missing"
# The NOT RUN rule, on a faked host: a docker binary that never answers.
local tmp
@@ -628,7 +635,7 @@ run_bm() {
local log rc=0 dc=0
log="$(mktemp)"
npm --prefix "$BM_APP/frontend" run test >"$log" 2>&1 || rc=$?
REQUIRE_FILE="src/lib/oauth.test.ts" detect_vitest "$log" || dc=$?
REQUIRE_FILES="src/lib/oauth.test.ts src/views/OAuthCallbackView.test.ts" detect_vitest "$log" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
tail -n 40 "$log" >&2
rm -f "$log"