Files
summercms/scripts/check-phase16.2.sh
Jakub Zych 3da2fd65b0 test(16.2): phase gate
Fail-closed stages go, admin, editor, layer, docs and postgres with go,
vitest-JSON and TAP detectors, the editor corpus copy comparison and a
self-test that proves each fails on planted output. --all runs postgres
only when a Docker daemon answers and otherwise prints a loud NOT RUN
notice; PHASE162_REQUIRE_POSTGRES=1 makes that a failure.
2026-10-10 16:12:19 +02:00

558 lines
23 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# Phase 16.2 fail-closed gate: plugin-provided admin field types (summercms.go
# pact and cabana, the admin SPA) and the Journal Blog Editor
# (sm-journal-plugin Go and editor element, vue-journal-layer embeds).
#
# Stages:
# go go vet in both Go repositories; the framework's field type tests
# and the Journal classes and controllers tests (including the
# editor-corpus policy cross-check); the 16.1 policy test files
# must be unchanged since c60abc6.
# admin the admin SPA typecheck and full vitest suite.
# editor the Blog Editor typecheck and full vitest suite (smoke, corpus,
# preview, element, schema, serializer, embed).
# layer the vue-journal-layer node --test suite and the editor corpus
# copy, which must equal the plugin's byte for byte (cmp).
# docs TestDocsTree, the admin OpenAPI and dist drift gates and the
# plugin's assets/dist drift gate.
# postgres Docker host only: the testcontainers suites (cabana, including
# TestFieldTypeSavePostgres; the Journal admin harness, including
# TestPostsAdminResaveKeepsHTML, TestPostsAdminSaveSanitizesHTML,
# TestCategoriesAdminContent and TestCategoriesAdminBlogEditor).
# Without a reachable Docker daemon this stage fails.
#
# Every stage exits non-zero on a failing command; a go test -json run that
# fails, skips, matches zero tests, prints "no tests to run" or misses a named
# required test; a vitest JSON report with a failed, skipped or todo test, a
# failed file, no passing test or a missing required file or test; or a TAP
# run with "not ok", a non-zero "# fail", a skip, no pass or a missing named
# test. --self-test proves the detectors and the corpus comparison fail on
# planted output.
#
# --all runs self-test, go, admin, editor, layer and docs, then postgres when
# a Docker daemon answers. When none does, --all says so loudly (NOT RUN
# postgres) and the Docker suites stay a manual step for a Docker host:
# bash scripts/check-phase16.2.sh postgres
# Set PHASE162_REQUIRE_POSTGRES=1 to make --all fail instead.
#
# Environment overrides: PHASE162_ROOT (this repository), JOURNAL (default
# ../sm-journal-plugin), LAYER (default ../vue-journal-layer).
set -euo pipefail
unset FORCE_COLOR
export NO_COLOR=1
ROOT="${PHASE162_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
JOURNAL="${JOURNAL:-$ROOT/../sm-journal-plugin}"
LAYER="${LAYER:-$ROOT/../vue-journal-layer}"
POLICY_BASE="c60abc6"
# Framework tests (modules/pact, modules/cabana) that must pass by name.
FRAMEWORK_RUN='^(TestFormFieldErrorMessage|TestFormFieldTypeContract|TestFieldTypeRegistry|TestFieldTypeOptions|TestFieldTypeCompile|TestFieldTypeDecode|TestFieldTypeNotices|TestFieldTypeUntouched|TestFieldTypeLoad|TestFieldTypeListValue|TestFieldTypeWrites|TestFieldTypesRoute|TestFieldUpload|TestFieldTypeTracerSmoke|TestFieldTypeExpansionSmoke|TestPhase10CSRF|TestPhase09ContractInventory|TestPhase09PermissionMatrix|ExampleCompileFormWithTypes)$'
FRAMEWORK_REQUIRE=(
TestFormFieldErrorMessage TestFormFieldTypeContract
TestFieldTypeRegistry TestFieldTypeOptions TestFieldTypeCompile TestFieldTypeDecode
TestFieldTypeNotices TestFieldTypeUntouched TestFieldTypeLoad TestFieldTypeListValue
TestFieldTypeWrites TestFieldTypesRoute TestFieldUpload
TestFieldTypeTracerSmoke TestFieldTypeExpansionSmoke
TestPhase10CSRF TestPhase09ContractInventory TestPhase09PermissionMatrix
ExampleCompileFormWithTypes
)
# Journal classes and controllers tests that must pass by name (the packages
# run whole; these are the phase's and the 16.1 policy's).
JOURNAL_REQUIRE=(
TestBlogEditorDecode TestBlogEditorNotices TestBlogEditorLoad TestLooksLikeHTML
TestBlogEditorOptions TestBlogEditorListValue TestBlogEditorUpload TestStoreMedia
TestEditorOutputPassesPolicy TestSanitizeHTMLReport
TestFormatHTMLSanitizesUnsafeHTML TestFormatHTMLDuplicateAttributes TestCheckHTML TestSanitizeHTML
TestMediaObjectPath TestMediaFolderPatternRejectsDotDot
TestPostsEditorHooks TestBlogEditorTracerSmoke TestBlogEditorUploadSmoke
TestWriteMediaStoreError
)
# Docker-host suites.
PG_FRAMEWORK_REQUIRE=(TestFieldTypeSavePostgres TestListRelationSearchPostgres)
PG_JOURNAL_REQUIRE=(
TestPostsFormCompiles TestPostsAdminCreateSmoke TestPostsAdminResaveKeepsHTML
TestPostsAdminSaveSanitizesHTML TestCategoriesAdminContent TestCategoriesAdminBlogEditor
TestJournalSVGEmbed
)
ADMIN_FILES=(
tests/form/PluginField.test.ts tests/form/FormField.test.ts tests/form/registry.test.ts
tests/form/formState.test.ts tests/app/fieldTypes.test.ts tests/app/pluginAssets.test.ts
tests/list/ListView.test.ts tests/list/CellValue.test.ts tests/shell/AppShell.test.ts
)
ADMIN_REQUIRE=(
"untouched value (D-03) keeps the served HTML identical when the element echoes it on connect"
"PluginField does not write back the value the element just emitted"
"loadFieldTypes loads each script URL once, even across two runs and two types"
"loadFieldTypes keeps field type styles enabled on every screen"
"View site link (Phase 16.2 quick follow-up) opens the site root in a new tab without an opener or referrer"
"plugin field type columns (Phase 16.2) renders the row value of a column type it does not know as plain text"
)
EDITOR_FILES=(
tests/blogeditor/smoke.test.ts tests/blogeditor/corpus.test.ts tests/blogeditor/preview.test.ts
tests/blogeditor/element.test.ts tests/blogeditor/schema.test.ts tests/blogeditor/serializer.test.ts
tests/blogeditor/embed.test.ts
)
EDITOR_REQUIRE=(
"untouched value (D-03) returns the imported HTML post unchanged after a Source tab round trip"
"block reuse keeps every other block, separator and comment byte for byte after one edit"
"editor corpus (D-05) matches the committed editor-corpus.json"
"isPolicyUrl refuses \"java\\tscript:alert(1)\""
)
LAYER_REQUIRE=(
"the sink turns a stored placeholder into the iframe"
"a raw iframe in stored HTML is stripped; only the placeholder becomes an iframe"
"the sinks expand after sanitizing and the payload transforms never expand"
"the Go policy negatives are stripped the same way here"
"the editor corpus has the entries the mirror rules are for"
)
usage() {
cat >&2 <<'EOF'
usage:
check-phase16.2.sh --self-test
check-phase16.2.sh go | admin | editor | layer | docs | postgres
check-phase16.2.sh --go | --admin | --editor | --layer | --docs | --postgres
check-phase16.2.sh --all
EOF
exit 2
}
# detect_go reads go test -json. Exit 1 fail/build, 2 skip, 3 zero/no-tests,
# 4 non-JSON, 5 missing required name.
detect_go() {
python3 - "$1" <<'PY'
import json, os, sys
path = sys.argv[1]
require = [n for n in os.environ.get("REQUIRE_TESTS", "").split() if n]
passed = set()
failed = []
with open(path, encoding="utf-8", errors="replace") as fh:
for raw in fh:
line = raw.strip()
if not line.startswith("{"):
continue
try:
ev = json.loads(line)
except json.JSONDecodeError:
print("refuse: non-json test output", file=sys.stderr)
sys.exit(4)
action = ev.get("Action")
test = ev.get("Test") or ""
pkg = ev.get("Package") or ev.get("ImportPath") or ""
if action == "build-fail" or (action == "fail" and ev.get("FailedBuild")):
print(f"refuse: build failed {pkg}", file=sys.stderr)
sys.exit(1)
text = ev.get("Output") or ""
if action == "output" and "no tests to run" in text:
print(f"refuse: no tests to run in {pkg}", file=sys.stderr)
sys.exit(3)
if action == "skip" and test:
print(f"refuse: skipped {pkg} {test}", file=sys.stderr)
sys.exit(2)
if action == "fail":
failed.append(f"{pkg} {test}".strip())
if action == "pass" and test:
passed.add(test)
if failed:
print("refuse: failed " + ", ".join(failed), file=sys.stderr)
sys.exit(1)
if not passed:
print("refuse: zero tests", file=sys.stderr)
sys.exit(3)
missing = [n for n in require if n not in passed]
if missing:
print("refuse: required tests did not pass: " + ", ".join(missing), file=sys.stderr)
sys.exit(5)
PY
}
# detect_tap reads node --test --test-reporter=tap output. Exit 1 "not ok" or
# a fail/cancelled count, 2 skip/todo, 3 no pass, 5 missing required name.
# REQUIRE_NAMES holds one test name per line.
detect_tap() {
python3 - "$1" <<'PY'
import os, re, sys
path = sys.argv[1]
require = [n for n in os.environ.get("REQUIRE_NAMES", "").split("\n") if n.strip()]
passed = set()
counts = {}
with open(path, encoding="utf-8", errors="replace") as fh:
for raw in fh:
line = raw.rstrip("\n")
body = line.lstrip()
if body.startswith("not ok"):
print(f"refuse: {body}", file=sys.stderr)
sys.exit(1)
m = re.match(r"ok \d+ - (.*?)(\s+#\s*(SKIP|TODO)\b.*)?$", body, re.I)
if m:
if m.group(2):
print(f"refuse: {body}", file=sys.stderr)
sys.exit(2)
passed.add(m.group(1))
continue
m = re.match(r"# (tests|suites|pass|fail|cancelled|skipped|todo) (\d+)$", line)
if m:
counts[m.group(1)] = int(m.group(2))
for key in ("fail", "cancelled"):
if counts.get(key, 0) > 0:
print(f"refuse: {counts[key]} {key}", file=sys.stderr)
sys.exit(1)
for key in ("skipped", "todo"):
if counts.get(key, 0) > 0:
print(f"refuse: {counts[key]} {key}", file=sys.stderr)
sys.exit(2)
if counts.get("pass", 0) < 1 or not passed:
print("refuse: no passing tests", file=sys.stderr)
sys.exit(3)
missing = [n for n in require if n not in passed]
if missing:
print("refuse: required tests did not pass: " + "; ".join(missing), file=sys.stderr)
sys.exit(5)
PY
}
# detect_vitest reads a vitest --reporter=json report. Exit 1 a failed test
# or file or an unsuccessful run, 2 a skipped or todo test, 3 no passing
# test, 4 an unreadable report, 5 a missing required file or test. Required
# files (REQUIRE_FILES, one path suffix per line) must each have a passing
# test; required names (REQUIRE_NAMES, one full test name per line) must
# pass.
detect_vitest() {
python3 - "$1" <<'PY'
import json, os, sys
path = sys.argv[1]
files = [n for n in os.environ.get("REQUIRE_FILES", "").split("\n") if n.strip()]
names = [n for n in os.environ.get("REQUIRE_NAMES", "").split("\n") if n.strip()]
try:
with open(path, encoding="utf-8") as fh:
report = json.load(fh)
except (OSError, ValueError) as err:
print(f"refuse: unreadable vitest report: {err}", file=sys.stderr)
sys.exit(4)
results = report.get("testResults") or []
bad = [r.get("name", "?") for r in results if r.get("status") != "passed"]
if bad or report.get("numFailedTests", 0) or report.get("numFailedTestSuites", 0) or report.get("success") is not True:
print("refuse: failed " + ", ".join(bad or ["run"]), file=sys.stderr)
sys.exit(1)
if report.get("numPendingTests", 0) or report.get("numTodoTests", 0):
print(f"refuse: {report.get('numPendingTests', 0)} skipped, {report.get('numTodoTests', 0)} todo", file=sys.stderr)
sys.exit(2)
passed = set()
by_file = {}
for r in results:
for a in r.get("assertionResults") or []:
status = a.get("status")
if status != "passed":
print(f"refuse: {status} {a.get('fullName')}", file=sys.stderr)
sys.exit(2 if status in ("skipped", "pending", "todo") else 1)
passed.add(a.get("fullName", ""))
by_file[r.get("name", "")] = by_file.get(r.get("name", ""), 0) + 1
if report.get("numPassedTests", 0) < 1 or not passed:
print("refuse: no passing tests", file=sys.stderr)
sys.exit(3)
missing = [f for f in files if not any(name.endswith("/" + f) and count > 0 for name, count in by_file.items())]
missing += [n for n in names if n not in passed]
if missing:
print("refuse: required files or tests did not pass: " + "; ".join(missing), file=sys.stderr)
sys.exit(5)
print(f"vitest: {report.get('numPassedTests')} passed in {len(results)} files")
PY
}
go_json() {
local dir="$1"
shift
local log err rc=0 dc=0
log="$(mktemp)"
err="$(mktemp)"
(cd "$dir" && go test -json "$@") >"$log" 2>"$err" || rc=$?
detect_go "$log" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
cat "$err" >&2 || true
grep -v '^{' "$log" | tail -n 40 >&2 || true
python3 -c 'import json,sys
for raw in open(sys.argv[1], errors="replace"):
try: ev = json.loads(raw)
except ValueError: continue
if ev.get("Action") == "output" and ("FAIL" in ev.get("Output", "") or "_test.go:" in ev.get("Output", "")):
sys.stderr.write(ev["Output"])' "$log" 2>&1 | tail -n 40 >&2 || true
rm -f "$log" "$err"
echo "refuse: go test $* in $dir (test=$rc detect=$dc)" >&2
return 1
fi
echo "go: $(grep -c '"Action":"pass","Package":"[^"]*","Test":"[^"/]*","Elapsed"' "$log") top-level tests passed in $dir ($*)"
rm -f "$log" "$err"
}
# vitest_json runs a repository's vitest with the JSON reporter and checks it.
vitest_json() {
local dir="$1" report out rc=0 dc=0
report="$(mktemp)"
out="$(mktemp)"
(cd "$dir" && ./node_modules/.bin/vitest run --reporter=json --outputFile="$report") >"$out" 2>&1 || rc=$?
detect_vitest "$report" || dc=$?
if [[ "$rc" -ne 0 || "$dc" -ne 0 ]]; then
grep -E 'FAIL|Error|×' "$out" | tail -n 40 >&2 || true
rm -f "$report" "$out"
echo "refuse: vitest in $dir (test=$rc detect=$dc)" >&2
return 1
fi
rm -f "$report" "$out"
}
# node_tap runs `npm --prefix <dir> test` (node --test, TAP) and checks it.
node_tap() {
local dir="$1" log rc=0 dc=0
log="$(mktemp)"
npm --prefix "$dir" test >"$log" 2>&1 || rc=$?
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: npm test in $dir (test=$rc detect=$dc)" >&2
return 1
fi
grep -E '^# (tests|pass|fail) ' "$log"
rm -f "$log"
}
# same_corpus fails unless the two editor corpus copies are byte-identical.
same_corpus() {
local plugin_copy="$1" layer_copy="$2"
if [[ ! -s "$plugin_copy" || ! -s "$layer_copy" ]]; then
echo "refuse: editor corpus missing or empty: $plugin_copy $layer_copy" >&2
return 1
fi
if ! cmp -s "$plugin_copy" "$layer_copy"; then
echo "refuse: the layer's editor corpus differs from the plugin's; copy $plugin_copy to $layer_copy" >&2
return 1
fi
}
expect_detector() {
local detector="$1" name="$2" want="$3" payload="$4" log dc=0
log="$(mktemp)"
printf '%s\n' "$payload" >"$log"
"$detector" "$log" >/dev/null 2>&1 || dc=$?
rm -f "$log"
if [[ "$dc" -ne "$want" ]]; then
echo "refuse: self-test $detector $name: exit $dc, want $want" >&2
return 1
fi
}
run_self_test() {
bash -n "${BASH_SOURCE[0]}"
expect_detector detect_go pass 0 '{"Action":"pass","Package":"p","Test":"TestFieldTypeUntouched"}'
expect_detector detect_go fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p","Test":"TestFieldUpload"}'
expect_detector detect_go package-fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"fail","Package":"p"}'
expect_detector detect_go build 1 '{"Action":"build-fail","ImportPath":"p"}'
expect_detector detect_go skip 2 '{"Action":"pass","Package":"p","Test":"TestA"}
{"Action":"skip","Package":"p","Test":"TestPostsAdminResaveKeepsHTML"}'
expect_detector detect_go zero 3 '{"Action":"pass","Package":"p"}'
expect_detector detect_go no-tests 3 '{"Action":"output","Package":"p","Output":"testing: warning: no tests to run\n"}
{"Action":"pass","Package":"p"}'
expect_detector detect_go nonjson 4 '{"Action":"pass",'
REQUIRE_TESTS="TestFieldTypeUntouched TestBlogEditorLoad" expect_detector detect_go missing-named 5 \
'{"Action":"pass","Package":"p","Test":"TestFieldTypeUntouched"}'
REQUIRE_TESTS="TestFieldType" expect_detector detect_go prefix-is-not-the-test 5 \
'{"Action":"pass","Package":"p","Test":"TestFieldTypeUntouched"}'
local ok='TAP version 13
ok 1 - the sink turns a stored placeholder into the iframe
1..1
# tests 1
# pass 1
# fail 0
# cancelled 0
# skipped 0
# todo 0'
expect_detector detect_tap pass 0 "$ok"
expect_detector detect_tap not-ok 1 'ok 1 - a
not ok 2 - b
# pass 1
# fail 1'
expect_detector detect_tap nested-not-ok 1 ' not ok 1 - sub
ok 1 - a
# pass 1
# fail 0'
expect_detector detect_tap fail-count 1 'ok 1 - a
# pass 1
# fail 1'
expect_detector detect_tap skip 2 'ok 1 - a # SKIP later
# pass 1'
expect_detector detect_tap todo-count 2 'ok 1 - a
# pass 1
# todo 1'
expect_detector detect_tap zero 3 '# tests 0
# pass 0
# fail 0'
REQUIRE_NAMES=$'the sink turns a stored placeholder into the iframe\nmissing' expect_detector detect_tap missing-named 5 "$ok"
local vok='{"success":true,"numPassedTests":2,"numFailedTests":0,"numFailedTestSuites":0,"numPendingTests":0,"numTodoTests":0,"testResults":[{"name":"/r/tests/form/PluginField.test.ts","status":"passed","assertionResults":[{"fullName":"PluginField a","status":"passed"},{"fullName":"PluginField b","status":"passed"}]}]}'
expect_detector detect_vitest pass 0 "$vok"
REQUIRE_FILES='tests/form/PluginField.test.ts' REQUIRE_NAMES='PluginField a' expect_detector detect_vitest named 0 "$vok"
expect_detector detect_vitest failed-test 1 '{"success":false,"numPassedTests":1,"numFailedTests":1,"testResults":[{"name":"/r/a.test.ts","status":"failed","assertionResults":[{"fullName":"x","status":"failed"}]}]}'
expect_detector detect_vitest failed-file 1 '{"success":false,"numPassedTests":0,"numFailedTests":0,"numFailedTestSuites":1,"testResults":[{"name":"/r/a.test.ts","status":"failed","assertionResults":[]}]}'
expect_detector detect_vitest unsuccessful 1 '{"success":false,"numPassedTests":1,"numFailedTests":0,"testResults":[{"name":"/r/a.test.ts","status":"passed","assertionResults":[{"fullName":"x","status":"passed"}]}]}'
expect_detector detect_vitest skipped 2 '{"success":true,"numPassedTests":1,"numPendingTests":1,"testResults":[{"name":"/r/a.test.ts","status":"passed","assertionResults":[{"fullName":"x","status":"passed"},{"fullName":"y","status":"skipped"}]}]}'
expect_detector detect_vitest todo 2 '{"success":true,"numPassedTests":1,"numTodoTests":1,"testResults":[{"name":"/r/a.test.ts","status":"passed","assertionResults":[{"fullName":"x","status":"passed"}]}]}'
expect_detector detect_vitest zero 3 '{"success":true,"numPassedTests":0,"testResults":[]}'
expect_detector detect_vitest unreadable 4 'not json'
REQUIRE_FILES='tests/blogeditor/element.test.ts' expect_detector detect_vitest missing-file 5 "$vok"
REQUIRE_NAMES='PluginField c' expect_detector detect_vitest missing-name 5 "$vok"
# The corpus comparison fails on a planted difference and on a missing copy.
local a b
a="$(mktemp)"
b="$(mktemp)"
printf '[{"name":"x","html":"<p>a</p>"}]\n' >"$a"
cp "$a" "$b"
same_corpus "$a" "$b" || {
echo "refuse: self-test corpus: identical copies refused" >&2
rm -f "$a" "$b"
return 1
}
printf '[{"name":"x","html":"<p>b</p>"}]\n' >"$b"
if same_corpus "$a" "$b" 2>/dev/null; then
echo "refuse: self-test corpus: a planted difference passed" >&2
rm -f "$a" "$b"
return 1
fi
: >"$b"
if same_corpus "$a" "$b" 2>/dev/null; then
echo "refuse: self-test corpus: an empty copy passed" >&2
rm -f "$a" "$b"
return 1
fi
rm -f "$a" "$b"
local mode
for mode in self-test go admin editor layer docs postgres all; do
grep -q -- "^ --$mode" "${BASH_SOURCE[0]}" || {
echo "refuse: missing mode --$mode" >&2
return 1
}
done
echo "phase16.2 self-test passed"
}
require_dir() {
[[ -d "$1" ]] || {
echo "refuse: $2 $1 not found" >&2
return 1
}
}
run_go() {
require_dir "$JOURNAL" "Journal plugin"
(cd "$ROOT" && go vet ./...)
REQUIRE_TESTS="${FRAMEWORK_REQUIRE[*]}" go_json "$ROOT" -count=1 -run "$FRAMEWORK_RUN" ./modules/pact ./modules/cabana
go -C "$JOURNAL" vet ./...
REQUIRE_TESTS="${JOURNAL_REQUIRE[*]}" go_json "$JOURNAL" -count=1 ./classes/ ./controllers/...
if ! git -C "$JOURNAL" diff --quiet "$POLICY_BASE" -- classes/format_html_test.go classes/check_html_test.go classes/format_translation_test.go; then
echo "refuse: the 16.1 policy tests changed since $POLICY_BASE" >&2
return 1
fi
echo "phase16.2 go passed"
}
run_admin() {
(cd "$ROOT/admin" && ./node_modules/.bin/vue-tsc --noEmit)
REQUIRE_FILES="$(printf '%s\n' "${ADMIN_FILES[@]}")" REQUIRE_NAMES="$(printf '%s\n' "${ADMIN_REQUIRE[@]}")" vitest_json "$ROOT/admin"
echo "phase16.2 admin passed"
}
run_editor() {
require_dir "$JOURNAL" "Journal plugin"
(cd "$JOURNAL" && ./node_modules/.bin/tsc --noEmit)
REQUIRE_FILES="$(printf '%s\n' "${EDITOR_FILES[@]}")" REQUIRE_NAMES="$(printf '%s\n' "${EDITOR_REQUIRE[@]}")" vitest_json "$JOURNAL"
echo "phase16.2 editor passed"
}
run_layer() {
require_dir "$LAYER" "layer repository"
REQUIRE_NAMES="$(printf '%s\n' "${LAYER_REQUIRE[@]}")" node_tap "$LAYER"
same_corpus "$JOURNAL/tests/blogeditor/fixtures/editor-corpus.json" "$LAYER/tests/fixtures/editor-corpus.json"
echo "corpus: the layer copy equals the plugin's"
echo "phase16.2 layer passed"
}
run_docs() {
require_dir "$JOURNAL" "Journal plugin"
REQUIRE_TESTS="TestDocsTree" go_json "$ROOT" -count=1 -run '^TestDocsTree$' ./cmd/summer
bash "$ROOT/scripts/check-admin-openapi.sh" --check
bash "$ROOT/scripts/check-admin-dist.sh"
bash "$JOURNAL/scripts/check-dist.sh"
echo "phase16.2 docs passed"
}
docker_up() {
command -v docker >/dev/null 2>&1 && docker info >/dev/null 2>&1
}
run_postgres() {
require_dir "$JOURNAL" "Journal plugin"
if ! docker_up; then
echo "refuse: the postgres stage needs a reachable Docker daemon (testcontainers); run it on a Docker host" >&2
return 1
fi
REQUIRE_TESTS="${PG_FRAMEWORK_REQUIRE[*]}" go_json "$ROOT" -count=1 -timeout 15m ./modules/cabana
REQUIRE_TESTS="${PG_JOURNAL_REQUIRE[*]}" go_json "$JOURNAL" -count=1 -timeout 20m ./...
echo "phase16.2 postgres passed"
}
run_all() {
local stage
for stage in self-test go admin editor layer docs; do
if bash "${BASH_SOURCE[0]}" "--$stage"; then
echo "PASS $stage"
else
echo "FAIL $stage"
exit 1
fi
done
if docker_up; then
if bash "${BASH_SOURCE[0]}" --postgres; then
echo "PASS postgres"
else
echo "FAIL postgres"
exit 1
fi
elif [[ "${PHASE162_REQUIRE_POSTGRES:-0}" == "1" ]]; then
echo "FAIL postgres: no reachable Docker daemon and PHASE162_REQUIRE_POSTGRES=1"
exit 1
else
cat >&2 <<'EOF'
************************************************************************
NOT RUN postgres: no reachable Docker daemon on this host.
The testcontainers suites (TestFieldTypeSavePostgres, the Journal admin
harness incl. TestPostsAdminResaveKeepsHTML, TestPostsAdminSaveSanitizesHTML,
TestCategoriesAdminContent, TestCategoriesAdminBlogEditor) did NOT run.
Run on a Docker host before /msd-verify-work:
bash scripts/check-phase16.2.sh postgres
************************************************************************
EOF
echo "NOT RUN postgres (Docker-host only; see the notice above)"
fi
echo "Phase 16.2 gate passed"
}
case "${1:---all}" in
--self-test | self-test) run_self_test ;;
--go | go) run_go ;;
--admin | admin) run_admin ;;
--editor | editor) run_editor ;;
--layer | layer) run_layer ;;
--docs | docs) run_docs ;;
--postgres | postgres) run_postgres ;;
--all | all) run_all ;;
*) usage ;;
esac