test: phase 33 gate, layer, consumer and admin stages

- layers: node --test runs checked with a TAP detector (self-tested), every
  vue-seo-layer smoke, guard:check and purge:check, all five journal
  expectation files (postTitleTag h1 and p), the journal purge checks and
  check-git-install.sh --self-test
- consumers: the 33-12 and 33-13 app patches pass git apply --check, and
  the 33-14 demo locale patches apply on top of the 33-12 ones in scratch
  worktrees
This commit is contained in:
Jakub Zych
2026-11-04 20:25:36 +01:00
parent eb8a0280ca
commit fbe9816a50

View File

@@ -4,7 +4,7 @@
# plugins, the four Nuxt layers and the applications that consume them.
#
# Stages:
# self-test the go test -json and vitest detectors on planted output, a
# self-test the go test -json, vitest and TAP detectors on planted output, a
# failing go test in a planted module, the marker rule on a
# planted repository, the missing-repository failure, the docker
# NOT RUN rule on a faked host, and the additive comparison on
@@ -18,12 +18,18 @@
# required name (*_UNIT_TESTS); npm run test (vitest) in
# sm-seo-plugin and sm-journal-plugin.
# layers vue-seo-layer, vue-journal-layer, vue-menu-layer and
# vue-faq-layer: npm run test, npm run build:playground and the
# layer's SSR smoke; node testing/guard-check.mjs in
# vue-seo-layer (the build-guard fixtures).
# vue-faq-layer: npm run test (node --test, an all-passing TAP
# summary), npm run build:playground and the layer's SSR
# smokes; in vue-seo-layer guard:check (the build-guard
# fixtures) and purge:check; in vue-journal-layer every
# expectation file (expect-acme-title-h1 and -title-p with
# NUXT_PUBLIC_JOURNAL_POST_TITLE_TAG), the purge checks and
# check-git-install.sh --self-test.
# consumers sm-demo-app, sm-grzybyfunkcjonalne-app, sm-summercmsio-app and
# sm-bm-app: the workspace check and go build ./...; npm run
# build in each Nuxt app (vue-*/ with a build script).
# build in each Nuxt app (vue-*/ with a build script); the
# 33-12 and 33-13 app patches pass git apply --check, and the
# 33-14 demo locale patches apply on top of the 33-12 ones.
# docker Docker host only: the Postgres suites (go test ./... without
# -short) of the three Go plugins with the phase's suites by
# required name (*_DOCKER_TESTS; TestChromeRenderer too when a
@@ -404,6 +410,46 @@ npm_vitest() {
rm -f "$log"
}
# detect_tap reads a node --test --test-reporter=tap run. Exit 1 a failed
# test ("not ok", "# fail" or "# cancelled" above 0), 2 skipped or todo
# tests, 3 no all-passing summary.
detect_tap() {
python3 - "$1" <<'PY'
import re, sys
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
text = re.sub(r"\x1b\[[0-9;]*m", "", text)
counts = {k: int(v) for k, v in re.findall(r"^# (tests|pass|fail|skipped|todo|cancelled) (\d+)\s*$", text, re.M)}
if re.search(r"^\s*not ok\b", text, re.M) or counts.get("fail", 0) > 0 or counts.get("cancelled", 0) > 0:
print("refuse: TAP reports failed tests", file=sys.stderr)
sys.exit(1)
if counts.get("skipped", 0) > 0 or counts.get("todo", 0) > 0 or re.search(r"^\s*ok .*# (SKIP|TODO)\b", text, re.M | re.I):
print("refuse: TAP reports skipped or todo tests", file=sys.stderr)
sys.exit(2)
if "fail" not in counts or counts.get("pass", 0) == 0 or counts.get("tests", -1) != counts.get("pass"):
print("refuse: no all-passing TAP summary", file=sys.stderr)
sys.exit(3)
PY
}
# node_tap DIR runs npm run test (node --test with the TAP reporter) in DIR
# and requires an all-passing TAP summary.
node_tap() {
local dir="$1" log rc=0 dc=0
require_script "$dir" test
log="$(mktemp)"
npm --prefix "$dir" run test >"$log" 2>&1 || rc=$?
detect_tap "$log" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
grep -E '^\s*not ok|# (fail|skipped|todo)|Error' "$log" | head -n 40 >&2 || true
tail -n 20 "$log" >&2
rm -f "$log"
echo "refuse: npm run test in $dir (test=$rc detect=$dc)" >&2
return 1
fi
echo "tap: $dir $(grep -E '^# (tests|pass|fail) ' "$log" | tr '\n' ' ')"
rm -f "$log"
}
# require_script DIR NAME refuses DIR without node_modules or without the
# npm script NAME in its package.json.
require_script() {
@@ -515,6 +561,25 @@ run_self_test() {
expect_detector detect_vitest failed 1 ' Test Files 1 failed | 1 passed (2)
Tests 1 failed | 16 passed (17)'
expect_detector detect_vitest no-summary 3 ' ✓ tests/a.test.ts (12 tests) 15ms'
local tap='TAP version 13
ok 1 - a
ok 2 - b
1..2
# tests 2
# suites 0
# pass 2
# fail 0
# cancelled 0
# skipped 0
# todo 0'
expect_detector detect_tap pass 0 "$tap"
expect_detector detect_tap not-ok 1 "${tap/ok 2 - b/not ok 2 - b}"
expect_detector detect_tap fail-count 1 "${tap/\# fail 0/\# fail 1}"
expect_detector detect_tap skipped 2 "${tap/\# skipped 0/\# skipped 1}"
expect_detector detect_tap skip-directive 2 "${tap/ok 2 - b/ok 2 - b # SKIP not here}"
expect_detector detect_tap no-summary 3 'TAP version 13
ok 1 - a'
expect_detector detect_tap zero 3 "$(printf '# tests 0\n# pass 0\n# fail 0')"
local tmp
tmp="$(mktemp -d)"
@@ -695,14 +760,29 @@ run_layers() {
local layer
require_all "Nuxt layer" "$SEO_LAYER" "$JOURNAL_LAYER" "$MENU_LAYER" "$FAQ_LAYER"
for layer in "$SEO_LAYER" "$JOURNAL_LAYER" "$MENU_LAYER" "$FAQ_LAYER"; do
npm_vitest "$layer"
node_tap "$layer"
npm_script "$layer" build:playground
done
# vue-seo-layer: every smoke expectation (the layer on, off, the CMS
# down, without the 404 token, with drift), the build-guard fixtures
# and the purge checks.
npm_script "$SEO_LAYER" smoke:ssr
node_run "$JOURNAL_LAYER" testing/smoke-ssr.mjs --output .playground/.output --fixtures testing/fixtures/acme --expect testing/expect-acme.txt
npm_script "$SEO_LAYER" guard:check
npm_script "$SEO_LAYER" purge:check
# vue-journal-layer: the acme expectations, the post title forced to h1
# and to p (postTitleTag), the link hint and the breadcrumbs off, the
# purge checks and the git-install lockfile reading.
local journal_smoke=(testing/smoke-ssr.mjs --output .playground/.output --fixtures testing/fixtures/acme)
node_run "$JOURNAL_LAYER" "${journal_smoke[@]}" --expect testing/expect-acme.txt
node_run "$JOURNAL_LAYER" "${journal_smoke[@]}" --expect testing/expect-acme-title-h1.txt --env NUXT_PUBLIC_JOURNAL_POST_TITLE_TAG=h1
node_run "$JOURNAL_LAYER" "${journal_smoke[@]}" --expect testing/expect-acme-title-p.txt --env NUXT_PUBLIC_JOURNAL_POST_TITLE_TAG=p
node_run "$JOURNAL_LAYER" "${journal_smoke[@]}" --expect testing/expect-acme-hint-off.txt --env NUXT_PUBLIC_SEO_LINKS_NEW_TAB_HINT=false
node_run "$JOURNAL_LAYER" "${journal_smoke[@]}" --expect testing/expect-acme-breadcrumbs-off.txt --env NUXT_PUBLIC_JOURNAL_BREADCRUMBS=false
node_run "$JOURNAL_LAYER" testing/purge-check.mjs --output .playground/.output --fixtures testing/fixtures/acme --kind post --slug hello-acme --path /blog/hello-acme
node_run "$JOURNAL_LAYER" testing/purge-token-check.mjs --output .playground/.output --fixtures testing/fixtures/acme --path /blog/hello-acme
(cd "$JOURNAL_LAYER" && bash scripts/check-git-install.sh --self-test)
npm_script "$MENU_LAYER" smoke:ssr
npm_script "$FAQ_LAYER" smoke:ssr
node_run "$SEO_LAYER" testing/guard-check.mjs
echo "phase33 layers passed"
}
@@ -718,9 +798,54 @@ run_consumers() {
npm_script "$(dirname "$pkg")" build
done
done
# The app patches of plans 33-12, 33-13 and 33-14 apply to the apps as
# committed; 33-14's demo locale patches apply on top of the 33-12 demo
# patches (checked in scratch worktrees).
patch_check "$DEMO_APP" 33-12-sm-demo-app.patch
patch_check "$DEMO_APP/vue-demo-app" 33-12-vue-demo-app.patch
patch_check "$SUMMERCMSIO_APP" 33-12-sm-summercmsio-app.patch
patch_check "$SUMMERCMSIO_APP/vue-summercmsio-app" 33-12-vue-summercmsio-app.patch
patch_check "$GRZYBY_APP" 33-13-sm-grzybyfunkcjonalne-app.patch
patch_check "$GRZYBY_APP/vue-grzybyfunkcjonalne-app" 33-13-vue-grzybyfunkcjonalne-app.patch
scratch_init
stacked_patch_check "$DEMO_APP/vue-demo-app" 33-12-vue-demo-app.patch 33-14-vue-demo-app-locale.patch
if [[ -f "$PHASE_DIR/33-14-sm-demo-app-locale.patch" ]]; then
stacked_patch_check "$DEMO_APP" 33-12-sm-demo-app.patch 33-14-sm-demo-app-locale.patch
fi
echo "phase33 consumers passed"
}
# PHASE_DIR holds the app patches.
PHASE_DIR="$ROOT/.planning/phases/33-seo-output-audit-and-content"
# patch_check REPO PATCH runs git apply --check of a phase patch in REPO
# (--allow-empty: an empty patch, such as summercms.io's Go side, passes).
patch_check() {
local repo="$1" patch="$PHASE_DIR/$2"
require_dir "$repo" "patch target"
require_file "$patch" "app patch"
if ! git -C "$repo" apply --check --allow-empty "$patch"; then
echo "refuse: $2 does not apply to $repo" >&2
return 1
fi
echo "patch: $2 applies to $repo"
}
# stacked_patch_check REPO BASE PATCH applies BASE in a scratch worktree of
# REPO's HEAD and runs git apply --check of PATCH on top of it.
stacked_patch_check() {
local repo="$1" base="$PHASE_DIR/$2" patch="$PHASE_DIR/$3" wt
require_file "$base" "app patch"
require_file "$patch" "app patch"
wt="$SCRATCH/stack-$(basename "$repo")-$RANDOM"
add_worktree "$repo" "$wt" HEAD
if ! git -C "$wt" apply --allow-empty "$base" || ! git -C "$wt" apply --check --allow-empty "$patch"; then
echo "refuse: $3 does not apply to $repo with $2" >&2
return 1
fi
echo "patch: $3 applies to $repo on top of $2"
}
docker_up() {
command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1
}