diff --git a/scripts/check-phase19.3.sh b/scripts/check-phase19.3.sh index 0f44da1..7f33629 100755 --- a/scripts/check-phase19.3.sh +++ b/scripts/check-phase19.3.sh @@ -117,6 +117,51 @@ DOCKER_COOLER_TESTS=( TestSiteCacheAfterCommitOnly TestSiteCacheFailureNeverFails ) +DOCKER_FAQ_TESTS=(TestFaqAdminPurgesSiteCache) +DOCKER_MENU_TESTS=(TestMenuAdminPurgesSiteCache) +DOCKER_JOURNAL_TESTS=( + TestCategoriesAdminPurgesSiteCache + TestPostsAdminPurgesSiteCache + TestCategoriesAdminFrameworkKeysWin +) + +# plugins stage: the purge helpers and the Journal's deprecated keys (-short). +FAQ_CONTROLLERS_TESTS=( + TestPurgeSiteCacheWithoutTx + TestPurgeSiteCacheWithoutPurger +) +MENU_CONTROLLERS_TESTS=("${FAQ_CONTROLLERS_TESTS[@]}") +JOURNAL_ROOT_TESTS=( + TestSiteCacheFrameworkKeysWin + TestSiteCacheAliasKeysDeprecated + TestSiteCacheNoKeys +) +JOURNAL_CLASSES_TESTS=(TestSiteCachePurgeConfig TestSiteCachePurgeRequest) + +# layers stage: the shared purge spec, byte-identical in the three layers. +LAYER_REQUIRE=( + "sitePurge: the shared files match the pinned hashes" + "sitePurge: a missing or short token disables the route" + "sitePurge: a proxied request is refused before the token is checked" + "sitePurge: authorize ok with the token and unauthorized otherwise" + "sitePurge: purge removes only nitro:routes: keys" + "sitePurge: purge event kinds and slugs" + "sitePurge: ok in constant time over sha256 digests, whatever the bearer length" + "sitePurge: purge works with a synchronous storage and skips the bare base key" + "sitePurge: purge event edge cases" + "sitePurge: route answers 404 when disabled or proxied and 401 for a wrong bearer" + "sitePurge: route purges, answers the count and logs kind and slug only" +) +# Files every layer ships byte for byte. +LAYER_SHARED=( + server/lib/siteCachePurge.ts + server/api/__site__/cache/purge.post.ts + tests/sitePurge.test.ts +) + +# docs stage: names of consuming applications that framework lines added by +# this phase must not carry (split so this file does not match itself). +APP_NAMES=("fono""teka" "p[lł]yta""rium" "grzy""by" "summercms""io" "wave""path" "stu""dia") usage() { cat >&2 <<'EOF' @@ -368,11 +413,6 @@ require_dir() { } } -not_wired() { - echo "refuse: stage $1 is not wired yet" >&2 - return 1 -} - run_go() { (cd "$ROOT" && go vet ./...) echo "vet: $ROOT" @@ -400,19 +440,115 @@ run_docker() { go_named "$ROOT" ./modules/cabana "-timeout 20m" "${DOCKER_CABANA_TESTS[@]}" go_named "$ROOT" ./modules/lagoon/attach "-timeout 20m" "${DOCKER_ATTACH_TESTS[@]}" go_named "$ROOT" ./modules/cooler "-timeout 20m" "${DOCKER_COOLER_TESTS[@]}" + require_dir "$FAQ_PLUGIN" "FAQ plugin" + require_dir "$MENU_PLUGIN" "menu plugin" + require_dir "$JOURNAL_PLUGIN" "Journal plugin" + go_named "$FAQ_PLUGIN" . "-timeout 20m" "${DOCKER_FAQ_TESTS[@]}" + go_named "$MENU_PLUGIN" . "-timeout 20m" "${DOCKER_MENU_TESTS[@]}" + go_named "$JOURNAL_PLUGIN" . "-timeout 20m" "${DOCKER_JOURNAL_TESTS[@]}" echo "phase19.3 docker passed" } run_plugins() { - not_wired plugins + require_dir "$FAQ_PLUGIN" "FAQ plugin" + require_dir "$MENU_PLUGIN" "menu plugin" + require_dir "$JOURNAL_PLUGIN" "Journal plugin" + local repo + for repo in "$FAQ_PLUGIN" "$MENU_PLUGIN" "$JOURNAL_PLUGIN"; do + go -C "$repo" vet ./... + echo "vet: $repo" + # The Docker suites skip here and run by name in the docker stage. + go_short "$repo" + done + go_named "$FAQ_PLUGIN" ./controllers -short "${FAQ_CONTROLLERS_TESTS[@]}" + go_named "$MENU_PLUGIN" ./controllers -short "${MENU_CONTROLLERS_TESTS[@]}" + go_named "$JOURNAL_PLUGIN" . -short "${JOURNAL_ROOT_TESTS[@]}" + go_named "$JOURNAL_PLUGIN" ./classes -short "${JOURNAL_CLASSES_TESTS[@]}" + echo "phase19.3 plugins passed" +} + +# pnpm_tap runs `pnpm -C test` (node --test, TAP) and checks the output +# with detect_tap. It never installs anything: when pnpm refuses to run +# because node_modules was installed against another store, it prints the +# offline, frozen-lockfile recovery and fails. +pnpm_tap() { + local dir="$1" log rc=0 dc=0 + log="$(mktemp)" + pnpm -C "$dir" test >"$log" 2>&1 || rc=$? + if grep -q 'ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY' "$log"; then + rm -f "$log" + cat >&2 < +then run this stage again. +MSG + return 1 + fi + detect_tap "$log" || dc=$? + if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then + grep -E '^\s*(not ok|# )|Error' "$log" | tail -n 40 >&2 || true + rm -f "$log" + echo "refuse: pnpm test in $dir (test=$rc detect=$dc)" >&2 + return 1 + fi + echo "tap: $(grep -E '^# (tests|pass|fail) ' "$log" | tr '\n' ' ')in $dir" + rm -f "$log" } run_layers() { - not_wired layers + require_dir "$FAQ_LAYER" "FAQ layer" + require_dir "$MENU_LAYER" "menu layer" + require_dir "$JOURNAL_LAYER" "Journal layer" + local file layer + for file in "${LAYER_SHARED[@]}"; do + for layer in "$MENU_LAYER" "$JOURNAL_LAYER"; do + if ! cmp -s "$FAQ_LAYER/$file" "$layer/$file"; then + echo "refuse: $file differs between $FAQ_LAYER and $layer (the shared files ship byte for byte)" >&2 + return 1 + fi + done + done + echo "cmp: ${#LAYER_SHARED[@]} shared files identical in the three layers" + for layer in "$FAQ_LAYER" "$MENU_LAYER" "$JOURNAL_LAYER"; do + REQUIRE_NAMES="$(printf '%s\n' "${LAYER_REQUIRE[@]}")" pnpm_tap "$layer" + done + echo "phase19.3 layers passed" +} + +# phase_base prints the parent of the phase's first commit; PHASE193_BASE +# overrides it. +phase_base() { + if [[ -n "${PHASE193_BASE:-}" ]]; then + echo "$PHASE193_BASE" + return + fi + local first + first="$(git -C "$ROOT" log --format=%H --reverse --grep='(19\.3' | head -n 1)" + if [[ -z "$first" ]]; then + echo "refuse: no commit of phase 19.3 found; set PHASE193_BASE" >&2 + return 1 + fi + echo "${first}^" } run_docs() { - not_wired docs + go_named "$ROOT" ./cmd/summer "" TestDocsTree + (cd "$ROOT" && go run ./cmd/summer docs:build --check) + local base pattern hits + base="$(phase_base)" + pattern="$(IFS='|'; echo "${APP_NAMES[*]}")" + # Lines this phase added to the framework (committed or not), planning + # docs excluded. + hits="$(git -C "$ROOT" diff "$base" -- . ':(exclude).planning' | + grep -E '^\+' | grep -vE '^\+\+\+ ' | grep -inE "$pattern" || true)" + if [[ -n "$hits" ]]; then + printf '%s\n' "$hits" | head -n 20 >&2 + echo "refuse: framework lines added since $base name a consuming application" >&2 + return 1 + fi + echo "names: no consuming application named in framework lines since $base" + echo "phase19.3 docs passed" } run_all() {