test(14.2-04): check-phase14.2.sh docs, parity, contracts, guard and e2e stages
- --go: vet and test ./... in summercms.go, the fonoteka.go root module and the fonoteka plugin module, then the named console, plugin and surf tests through the skip-intolerant detector - --parity requires the 175/175 TestParityCorpus summary - --contracts blocks on the phase's Nuxt contract test and reports the whole suite (missing-catalog.test.mjs fails on HEAD before this phase) - --guard plants guard-*.conf env files (:8422, unset internal origin, mismatched origins, plytarium.com, a fake PHP answer, another backend's marker) and requires a refusal for each, plus a SummerCMS control case - --e2e brings the stack up when it is down, runs the Go-gate specs through e2e-verdict, the D-15 specs report-only, and scans go.log for a search engine failure or a 5xx record - --self-test plants Playwright reports, Go logs and TAP summaries
This commit is contained in:
@@ -1,19 +1,47 @@
|
||||
#!/usr/bin/env bash
|
||||
# Phase 14.2 fail-closed gate (the local fonoteka frontend on the local
|
||||
# SummerCMS backend). Every stage exits non-zero on a failing command, a go
|
||||
# test run that fails, skips, matches zero tests, prints "no tests to run",
|
||||
# a named required test that did not pass or a data race.
|
||||
# Phase 14.2 fail-closed gate: the local vue-fonoteka-app on the local
|
||||
# fonoteka.go SummerCMS backend. Every stage exits non-zero on a failing
|
||||
# command, a go test run that fails, skips, matches zero tests, prints "no
|
||||
# tests to run", a named required test that did not pass or a data race.
|
||||
#
|
||||
# Modes:
|
||||
# --self-test proves the go test detector fails closed on planted inputs
|
||||
# --go the phase's named Go tests through the detector
|
||||
# --all every stage in order; the last line is "Phase 14.2 gate passed"
|
||||
# --self-test the go test detector, the Playwright verdict
|
||||
# (dev-stack.sh e2e-verdict), the go.log scan and the summary
|
||||
# checks fail closed on planted inputs; every mode exists
|
||||
# --go go vet and go test ./... in summercms.go, in the fonoteka.go
|
||||
# workspace root module and in the fonoteka plugin module,
|
||||
# then the phase's named tests through the detector
|
||||
# --docs TestDocsTree and summer docs:build --check
|
||||
# --parity TestParityCorpus with the 175/175 summary
|
||||
# --contracts the phase's Nuxt contract test (blocking) and the whole
|
||||
# pnpm test:contracts suite (counts reported, not blocking:
|
||||
# missing-catalog.test.mjs fails on HEAD before this phase,
|
||||
# see the phase's deferred-items.md)
|
||||
# --guard planted env files fed to dev-stack.sh guard: every case
|
||||
# must print "refuse:" and exit non-zero; a fake SummerCMS
|
||||
# marker must pass (control case)
|
||||
# --e2e the running local stack (started with up --detach when it
|
||||
# is not up), the guard on the real env file, the Go-gate
|
||||
# Playwright specs through dev-stack.sh e2e (any skip,
|
||||
# unexpected, flaky or zero expected fails), the D-15
|
||||
# report-only specs (counts printed, never blocking) and the
|
||||
# Go log scan (search engine failure or a 5xx record fails)
|
||||
# --all every stage in that order; the last line is
|
||||
# "Phase 14.2 gate passed"
|
||||
#
|
||||
# The fonoteka.go workspace is invoked with `go -C`. Postgres integration
|
||||
# runs through testcontainers; an unavailable Docker daemon fails the gate,
|
||||
# and -short is never final evidence. Go runs with TMPDIR on the
|
||||
# workspace's device (tmp/dev-stack/go-tmp), because a tmpfs /tmp breaks
|
||||
# the build cache and gocloud fileblob renames with a cross-device link.
|
||||
# Paths: PHASE142_ROOT (this checkout), PHASE142_APP (default
|
||||
# $ROOT/../fonoteka.go), PHASE142_NUXT (the vue-fonoteka-app checkout).
|
||||
# Postgres integration runs through testcontainers: an unavailable Docker
|
||||
# daemon fails the gate, and -short is never final evidence. A missing host
|
||||
# service (Postgres, Typesense, Centrifugo) fails --e2e through the
|
||||
# dev-stack.sh refusal; nothing is downgraded to a skip.
|
||||
#
|
||||
# TMPDIR: the gate leaves it as set by the caller (PHASE142_TMPDIR
|
||||
# overrides). It must not point inside a go.work tree such as fonoteka.go:
|
||||
# the scaffold tests in internal/build then edit that workspace's go.work,
|
||||
# and `go test ./...` there picks up the build directories as packages.
|
||||
# dev-stack.sh gives the Go processes and Playwright their own TMPDIR on the
|
||||
# app's device (the file bucket renames across devices otherwise).
|
||||
set -euo pipefail
|
||||
|
||||
unset FORCE_COLOR
|
||||
@@ -21,17 +49,39 @@ unset FORCE_COLOR
|
||||
ROOT="${PHASE142_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
||||
APP="${PHASE142_APP:-$ROOT/../fonoteka.go}"
|
||||
NUXT="${PHASE142_NUXT:-/media/nvme/dev/golem15/fonoteka/vue-fonoteka-app}"
|
||||
DEV_STACK="$APP/scripts/dev-stack.sh"
|
||||
PLUGIN_DIR="$APP/plugins/golem15/fonoteka"
|
||||
GO_LOG="$APP/tmp/dev-stack/go.log"
|
||||
|
||||
export TMPDIR="${PHASE142_TMPDIR:-$APP/tmp/dev-stack/go-tmp}"
|
||||
mkdir -p "$TMPDIR"
|
||||
if [[ -n "${PHASE142_TMPDIR:-}" ]]; then
|
||||
mkdir -p "$PHASE142_TMPDIR"
|
||||
export TMPDIR="$PHASE142_TMPDIR"
|
||||
fi
|
||||
|
||||
CONSOLE_REQUIRE=(TestDevSeedRefusesProduction)
|
||||
CONSOLE_REQUIRE=(TestDevSeedRefusesProduction TestDevSeedShape TestDevSeedIdempotent TestReindexCommand)
|
||||
APP_REQUIRE=(TestDevStackSmoke TestDevSeedOpensDatabase TestReindexOpensDatabase TestCommandsOpenDatabase TestDevMarkerRoute)
|
||||
FRAMEWORK_REQUIRE=(TestUploadsMount TestAccessLog TestAccessLogSmoke)
|
||||
GATE_SPECS=(
|
||||
auth.setup.ts login.spec.ts smoke.spec.ts fonoteka-item.spec.ts public-share.spec.ts
|
||||
realtime.spec.ts i18n.spec.ts album-edit-rate-cover.spec.ts album-search.spec.ts wishlist.spec.ts
|
||||
)
|
||||
REPORT_SPECS=(
|
||||
fonoteka-ai-assist.spec.ts fonoteka-bulk-add.spec.ts offline.spec.ts
|
||||
registration-invitation.spec.ts responsive.spec.ts landing.spec.ts
|
||||
)
|
||||
PHASE_CONTRACTS=(tests/contracts/e2e-backend-switch.test.mjs)
|
||||
PARITY_SUMMARY="recorded 175/175 passing 175 failing 0 unrecorded 0 pending 0"
|
||||
|
||||
usage() {
|
||||
cat >&2 <<'EOF'
|
||||
usage:
|
||||
check-phase14.2.sh --self-test
|
||||
check-phase14.2.sh --go
|
||||
check-phase14.2.sh --docs
|
||||
check-phase14.2.sh --parity
|
||||
check-phase14.2.sh --contracts
|
||||
check-phase14.2.sh --guard
|
||||
check-phase14.2.sh --e2e
|
||||
check-phase14.2.sh --all
|
||||
EOF
|
||||
exit 2
|
||||
@@ -91,6 +141,8 @@ if missing:
|
||||
PY
|
||||
}
|
||||
|
||||
# go_json DIR ARGS... runs go test -json in DIR through detect. With
|
||||
# GO_JSON_KEEP set the raw log is kept at that path.
|
||||
go_json() {
|
||||
local dir="$1"
|
||||
shift
|
||||
@@ -106,7 +158,12 @@ go_json() {
|
||||
echo "refuse: go test $* in $dir (test=$rc detect=$dc)" >&2
|
||||
return 1
|
||||
fi
|
||||
rm -f "$log" "$err"
|
||||
if [[ -n "${GO_JSON_KEEP:-}" ]]; then
|
||||
mv "$log" "$GO_JSON_KEEP"
|
||||
else
|
||||
rm -f "$log"
|
||||
fi
|
||||
rm -f "$err"
|
||||
}
|
||||
|
||||
expect_detect() {
|
||||
@@ -127,8 +184,83 @@ run_regex() {
|
||||
printf '^(%s)$' "$*"
|
||||
}
|
||||
|
||||
# scan_go_log FILE refuses a silent search fallback or a 5xx request record
|
||||
# (research G8, D-12). A 499 (client closed the request) is not a 5xx.
|
||||
scan_go_log() {
|
||||
local file="$1" hits
|
||||
[[ -f "$file" ]] || {
|
||||
echo "refuse: Go log $file not found" >&2
|
||||
return 1
|
||||
}
|
||||
hits="$(grep -nE 'album search engine failed|status=5[0-9][0-9]([^0-9]|$)' "$file" || true)"
|
||||
if [[ -n "$hits" ]]; then
|
||||
printf '%s\n' "$hits" | tail -n 20 >&2
|
||||
echo "refuse: $file has a search engine failure or a 5xx record (rotate the log only after reading it)" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# tap_clean FILE refuses a node --test TAP summary unless it has tests > 0
|
||||
# and fail, cancelled, skipped and todo all 0.
|
||||
tap_clean() {
|
||||
python3 - "$1" <<'PY'
|
||||
import re, sys
|
||||
text = open(sys.argv[1], encoding="utf-8", errors="replace").read()
|
||||
counts = {}
|
||||
for key in ("tests", "pass", "fail", "cancelled", "skipped", "todo"):
|
||||
found = re.findall(rf"^# {key} (\d+)$", text, re.M)
|
||||
if not found:
|
||||
print(f"refuse: TAP summary has no '# {key}' line", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
counts[key] = int(found[-1])
|
||||
if counts["tests"] == 0 or counts["pass"] != counts["tests"]:
|
||||
print(f"refuse: TAP summary {counts}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
for key in ("fail", "cancelled", "skipped", "todo"):
|
||||
if counts[key] != 0:
|
||||
print(f"refuse: TAP summary {counts}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print("tests={tests} pass={pass} fail={fail} skipped={skipped}".format(**counts))
|
||||
PY
|
||||
}
|
||||
|
||||
expect_status() {
|
||||
local name="$1" want="$2" got=0
|
||||
shift 2
|
||||
"$@" >/dev/null 2>&1 || got=$?
|
||||
if { [[ "$want" == 0 ]] && [[ "$got" -ne 0 ]]; } || { [[ "$want" != 0 ]] && [[ "$got" -eq 0 ]]; }; then
|
||||
echo "refuse: self-test $name: exit $got, want $([[ "$want" == 0 ]] && echo 0 || echo non-zero)" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# expect_verdict NAME WANT JSON: dev-stack.sh e2e-verdict on a planted
|
||||
# report must refuse (WANT=refuse, non-zero and a "refuse:" line) or pass.
|
||||
expect_verdict() {
|
||||
local name="$1" want="$2" payload="$3" dir out rc=0
|
||||
dir="$(mktemp -d)"
|
||||
printf '%s\n' "$payload" >"$dir/report.json"
|
||||
out="$(env -i PATH="$PATH" HOME="$HOME" bash "$DEV_STACK" e2e-verdict "$dir/report.json" 2>&1)" || rc=$?
|
||||
rm -rf "$dir"
|
||||
if [[ "$want" == refuse ]]; then
|
||||
if [[ "$rc" -eq 0 ]] || ! grep -q '^refuse:' <<<"$out"; then
|
||||
echo "refuse: self-test verdict $name was not refused (exit $rc): $out" >&2
|
||||
return 1
|
||||
fi
|
||||
elif [[ "$rc" -ne 0 ]] || ! grep -q '^e2e-verdict: passed' <<<"$out"; then
|
||||
echo "refuse: self-test verdict $name was refused (exit $rc): $out" >&2
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
run_self_test() {
|
||||
bash -n "${BASH_SOURCE[0]}"
|
||||
[[ -f "$DEV_STACK" ]] || {
|
||||
echo "refuse: $DEV_STACK not found" >&2
|
||||
return 1
|
||||
}
|
||||
bash -n "$DEV_STACK"
|
||||
|
||||
expect_detect pass 0 '{"Action":"pass","Package":"p","Test":"TestDevSeedRefusesProduction"}'
|
||||
expect_detect fail 1 '{"Action":"pass","Package":"p","Test":"TestA"}
|
||||
{"Action":"fail","Package":"p","Test":"TestDevSeedShape"}'
|
||||
@@ -136,6 +268,9 @@ run_self_test() {
|
||||
{"Action":"fail","Package":"p"}'
|
||||
expect_detect build 1 '{"Action":"build-fail","ImportPath":"p"}'
|
||||
expect_detect skip 2 '{"Action":"skip","Package":"p","Test":"TestUploadsMount"}'
|
||||
expect_detect subtest-skip 2 '{"Action":"pass","Package":"p","Test":"TestUploadsMount/off"}
|
||||
{"Action":"skip","Package":"p","Test":"TestUploadsMount/db"}
|
||||
{"Action":"pass","Package":"p","Test":"TestUploadsMount"}'
|
||||
expect_detect zero 3 '{"Action":"pass","Package":"p"}'
|
||||
expect_detect no-tests 3 '{"Action":"output","Package":"p","Output":"testing: warning: no tests to run\n"}
|
||||
{"Action":"pass","Package":"p"}'
|
||||
@@ -144,8 +279,47 @@ run_self_test() {
|
||||
{"Action":"pass","Package":"p","Test":"TestA"}'
|
||||
REQUIRE_TESTS="TestDevSeedRefusesProduction TestDevMarkerRoute" expect_detect missing-named 5 \
|
||||
'{"Action":"pass","Package":"p","Test":"TestDevSeedRefusesProduction"}'
|
||||
|
||||
# Playwright verdicts (T-14.2-17).
|
||||
expect_verdict clean pass '{"stats":{"expected":17,"unexpected":0,"skipped":0,"flaky":0}}'
|
||||
expect_verdict skipped refuse '{"stats":{"expected":16,"unexpected":0,"skipped":1,"flaky":0}}'
|
||||
expect_verdict unexpected refuse '{"stats":{"expected":16,"unexpected":1,"skipped":0,"flaky":0}}'
|
||||
expect_verdict flaky refuse '{"stats":{"expected":16,"unexpected":0,"skipped":0,"flaky":1}}'
|
||||
expect_verdict zero-expected refuse '{"stats":{"expected":0,"unexpected":0,"skipped":0,"flaky":0}}'
|
||||
expect_verdict malformed refuse '{"stats":{"expected":17,'
|
||||
expect_verdict no-stats refuse '{"suites":[]}'
|
||||
local out rc=0
|
||||
out="$(env -i PATH="$PATH" HOME="$HOME" bash "$DEV_STACK" e2e-verdict "$(mktemp -u)" 2>&1)" || rc=$?
|
||||
if [[ "$rc" -eq 0 ]] || ! grep -q '^refuse:' <<<"$out"; then
|
||||
echo "refuse: self-test verdict missing-report was not refused" >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Go log scan (T-14.2-20) and summaries.
|
||||
local dir
|
||||
dir="$(mktemp -d)"
|
||||
printf '%s\n' '[go] level=INFO msg="http request" method=GET pattern="GET /a" status=200 duration_ms=1' \
|
||||
'[go] level=INFO msg="http request" method=GET pattern="GET /b" status=499 duration_ms=3' >"$dir/clean.log"
|
||||
printf '%s\n' '[go] level=INFO msg="http request" method=GET pattern="GET /a" status=503 duration_ms=1' >"$dir/5xx.log"
|
||||
printf '%s\n' '[go] level=WARN msg="album search engine failed" err=timeout' >"$dir/search.log"
|
||||
expect_status go-log-clean 0 scan_go_log "$dir/clean.log"
|
||||
expect_status go-log-5xx 1 scan_go_log "$dir/5xx.log"
|
||||
expect_status go-log-search 1 scan_go_log "$dir/search.log"
|
||||
expect_status go-log-missing 1 scan_go_log "$dir/absent.log"
|
||||
printf '# tests 9\n# pass 9\n# fail 0\n# cancelled 0\n# skipped 0\n# todo 0\n' >"$dir/tap-clean"
|
||||
printf '# tests 9\n# pass 8\n# fail 1\n# cancelled 0\n# skipped 0\n# todo 0\n' >"$dir/tap-fail"
|
||||
printf '# tests 9\n# pass 8\n# fail 0\n# cancelled 0\n# skipped 1\n# todo 0\n' >"$dir/tap-skip"
|
||||
printf '# tests 0\n# pass 0\n# fail 0\n# cancelled 0\n# skipped 0\n# todo 0\n' >"$dir/tap-zero"
|
||||
printf 'ok 1 - x\n' >"$dir/tap-none"
|
||||
expect_status tap-clean 0 tap_clean "$dir/tap-clean"
|
||||
expect_status tap-fail 1 tap_clean "$dir/tap-fail"
|
||||
expect_status tap-skip 1 tap_clean "$dir/tap-skip"
|
||||
expect_status tap-zero 1 tap_clean "$dir/tap-zero"
|
||||
expect_status tap-none 1 tap_clean "$dir/tap-none"
|
||||
rm -rf "$dir"
|
||||
|
||||
local flag
|
||||
for flag in --self-test --go --all; do
|
||||
for flag in --self-test --go --docs --parity --contracts --guard --e2e --all; do
|
||||
grep -q -- "^ $flag)" "${BASH_SOURCE[0]}" || {
|
||||
echo "refuse: missing mode $flag" >&2
|
||||
return 1
|
||||
@@ -155,19 +329,213 @@ run_self_test() {
|
||||
}
|
||||
|
||||
run_go() {
|
||||
[[ -d "$APP" ]] || {
|
||||
[[ -d "$APP" && -d "$PLUGIN_DIR" ]] || {
|
||||
echo "refuse: fonoteka.go workspace $APP not found" >&2
|
||||
return 1
|
||||
}
|
||||
go -C "$APP" vet ./plugins/golem15/fonoteka/...
|
||||
(cd "$ROOT" && go vet ./... && go test ./... -count=1 -timeout 60m)
|
||||
go -C "$APP" vet ./...
|
||||
go -C "$APP" test ./... -count=1 -timeout 60m
|
||||
go -C "$PLUGIN_DIR" vet ./...
|
||||
go -C "$PLUGIN_DIR" test ./... -count=1 -timeout 60m
|
||||
REQUIRE_TESTS="${CONSOLE_REQUIRE[*]}" go_json "$APP" ./plugins/golem15/fonoteka/console \
|
||||
-count=1 -timeout 20m -run "$(run_regex "${CONSOLE_REQUIRE[@]}")"
|
||||
REQUIRE_TESTS="${APP_REQUIRE[*]}" go_json "$APP" ./plugins/golem15/fonoteka \
|
||||
-count=1 -timeout 20m -run "$(run_regex "${APP_REQUIRE[@]}")"
|
||||
REQUIRE_TESTS="${FRAMEWORK_REQUIRE[*]}" go_json "$ROOT" ./modules/surf \
|
||||
-count=1 -timeout 20m -run "$(run_regex "${FRAMEWORK_REQUIRE[@]}")"
|
||||
echo "phase14.2 go passed"
|
||||
}
|
||||
|
||||
run_docs() {
|
||||
REQUIRE_TESTS="TestDocsTree" go_json "$ROOT" ./cmd/summer -count=1 -run '^TestDocsTree$'
|
||||
local out
|
||||
out="$(cd "$ROOT" && go run ./cmd/summer docs:build --check 2>&1)" || {
|
||||
echo "$out" >&2
|
||||
echo "refuse: docs:build --check failed" >&2
|
||||
return 1
|
||||
}
|
||||
echo "phase14.2 docs passed"
|
||||
}
|
||||
|
||||
run_parity() {
|
||||
local log
|
||||
log="$(mktemp)"
|
||||
GO_JSON_KEEP="$log" REQUIRE_TESTS="TestParityCorpus" go_json "$APP" ./parity \
|
||||
-count=1 -run '^TestParityCorpus$' -timeout 30m -v
|
||||
if ! grep -qF "$PARITY_SUMMARY" "$log"; then
|
||||
grep -oE 'recorded [0-9]+/[0-9]+[^"\\]*' "$log" | tail -n 3 >&2 || true
|
||||
rm -f "$log"
|
||||
echo "refuse: TestParityCorpus did not report \"$PARITY_SUMMARY\"" >&2
|
||||
return 1
|
||||
fi
|
||||
rm -f "$log"
|
||||
echo "phase14.2 parity passed ($PARITY_SUMMARY)"
|
||||
}
|
||||
|
||||
run_contracts() {
|
||||
[[ -f "$NUXT/package.json" ]] || {
|
||||
echo "refuse: Nuxt checkout $NUXT not found" >&2
|
||||
return 1
|
||||
}
|
||||
local log rc=0 counts
|
||||
log="$(mktemp)"
|
||||
(cd "$NUXT" && node --test "${PHASE_CONTRACTS[@]}") >"$log" 2>&1 || rc=$?
|
||||
if [[ "$rc" -ne 0 ]] || ! counts="$(tap_clean "$log")"; then
|
||||
grep -E '^not ok|^# ' "$log" >&2 || true
|
||||
rm -f "$log"
|
||||
echo "refuse: phase contract tests ${PHASE_CONTRACTS[*]} (exit $rc)" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "contracts (blocking) ${PHASE_CONTRACTS[*]}: $counts"
|
||||
# The whole suite, reported only: missing-catalog.test.mjs fails on
|
||||
# vue-fonoteka-app HEAD from before this phase (deferred-items.md).
|
||||
rc=0
|
||||
pnpm --dir "$NUXT" test:contracts >"$log" 2>&1 || rc=$?
|
||||
echo "contracts (report only) pnpm test:contracts exit $rc: $(grep -E '^# (tests|pass|fail|skipped) ' "$log" | paste -sd ' ')"
|
||||
grep -E '^not ok' "$log" | sed 's/^/contracts (report only) /' || true
|
||||
rm -f "$log"
|
||||
echo "phase14.2 contracts passed"
|
||||
}
|
||||
|
||||
# guard_case NAME WANT FILE [ENV...]: dev-stack.sh guard on a planted env
|
||||
# file in a clean environment. WANT is the reason the refusal must name.
|
||||
guard_case() {
|
||||
local name="$1" want="$2" file="$3" out rc=0
|
||||
shift 3
|
||||
out="$(env -i PATH="$PATH" HOME="$HOME" DEV_STACK_ENV_FILE="$file" "$@" bash "$DEV_STACK" guard 2>&1)" || rc=$?
|
||||
if [[ "$rc" -eq 0 ]] || ! grep -q '^refuse:' <<<"$out" || ! grep -qF -- "$want" <<<"$out"; then
|
||||
echo "$out" >&2
|
||||
echo "refuse: guard case $name was not refused for \"$want\" (exit $rc)" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "guard $name: $(grep '^refuse:' <<<"$out" | head -n 1)"
|
||||
}
|
||||
|
||||
GUARD_PIDS=()
|
||||
stop_fakes() {
|
||||
local pid
|
||||
for pid in "${GUARD_PIDS[@]}"; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
wait "$pid" 2>/dev/null || true
|
||||
done
|
||||
GUARD_PIDS=()
|
||||
}
|
||||
|
||||
# fake_backend DIR STATUS BODY [HEADER]: a python3 http.server on a free
|
||||
# loopback port, recorded in GUARD_PIDS; its port goes to FAKE_PORT.
|
||||
FAKE_PORT=""
|
||||
fake_backend() {
|
||||
local dir="$1" status="$2" body="$3" header="${4:-}" portfile i
|
||||
portfile="$dir/port-$RANDOM-$RANDOM"
|
||||
python3 - "$portfile" "$status" "$body" "$header" >/dev/null 2>&1 <<'PY' &
|
||||
import http.server, sys
|
||||
portfile, status, body, header = sys.argv[1], int(sys.argv[2]), sys.argv[3].encode(), sys.argv[4]
|
||||
class H(http.server.BaseHTTPRequestHandler):
|
||||
def do_GET(self):
|
||||
self.send_response(status)
|
||||
if header:
|
||||
k, v = header.split(":", 1)
|
||||
self.send_header(k.strip(), v.strip())
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", str(len(body)))
|
||||
self.end_headers()
|
||||
self.wfile.write(body)
|
||||
def log_message(self, *args):
|
||||
pass
|
||||
srv = http.server.HTTPServer(("127.0.0.1", 0), H)
|
||||
with open(portfile + ".tmp", "w") as fh:
|
||||
fh.write(str(srv.server_address[1]))
|
||||
import os
|
||||
os.rename(portfile + ".tmp", portfile)
|
||||
srv.serve_forever()
|
||||
PY
|
||||
GUARD_PIDS+=("$!")
|
||||
for ((i = 0; i < 50; i++)); do
|
||||
[[ -s "$portfile" ]] && break
|
||||
sleep 0.1
|
||||
done
|
||||
[[ -s "$portfile" ]] || {
|
||||
echo "refuse: fake backend did not start" >&2
|
||||
return 1
|
||||
}
|
||||
FAKE_PORT="$(cat "$portfile")"
|
||||
}
|
||||
|
||||
run_guard() {
|
||||
[[ -f "$DEV_STACK" ]] || {
|
||||
echo "refuse: $DEV_STACK not found" >&2
|
||||
return 1
|
||||
}
|
||||
local dir port
|
||||
dir="$(mktemp -d)"
|
||||
GUARD_DIR="$dir"
|
||||
trap 'stop_fakes; rm -rf "$GUARD_DIR"' EXIT
|
||||
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:8422\nNUXT_INTERNAL_API_ORIGIN=http://127.0.0.1:8422\n' >"$dir/guard-php-port.conf"
|
||||
guard_case php-port "port 8422" "$dir/guard-php-port.conf"
|
||||
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:8423\n' >"$dir/guard-internal-unset.conf"
|
||||
guard_case internal-unset "NUXT_INTERNAL_API_ORIGIN is unset" "$dir/guard-internal-unset.conf"
|
||||
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:8423\nNUXT_INTERNAL_API_ORIGIN=http://localhost:8423\n' >"$dir/guard-mismatch.conf"
|
||||
guard_case mismatch "differ" "$dir/guard-mismatch.conf"
|
||||
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=https://plytarium.com\nNUXT_INTERNAL_API_ORIGIN=https://plytarium.com\n' >"$dir/guard-production.conf"
|
||||
guard_case production "not loopback" "$dir/guard-production.conf"
|
||||
|
||||
fake_backend "$dir" 404 '{}' 'X-Powered-By: PHP/8.5.10'
|
||||
port="$FAKE_PORT"
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:%s\nNUXT_INTERNAL_API_ORIGIN=http://127.0.0.1:%s\n' "$port" "$port" >"$dir/guard-fake-php.conf"
|
||||
guard_case fake-php "X-Powered-By: PHP" "$dir/guard-fake-php.conf" DEV_STACK_GO_ADDR="127.0.0.1:$port"
|
||||
|
||||
fake_backend "$dir" 200 '{"backend":"winter","app":"fonoteka","env":"local"}'
|
||||
port="$FAKE_PORT"
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:%s\nNUXT_INTERNAL_API_ORIGIN=http://127.0.0.1:%s\n' "$port" "$port" >"$dir/guard-other-marker.conf"
|
||||
guard_case other-marker 'did not answer "backend":"summercms"' "$dir/guard-other-marker.conf" DEV_STACK_GO_ADDR="127.0.0.1:$port"
|
||||
|
||||
# Control: the same rules accept a SummerCMS marker, so the refusals
|
||||
# above are the cases', not a guard that refuses everything.
|
||||
fake_backend "$dir" 200 '{"backend":"summercms","app":"fonoteka","env":"local"}'
|
||||
port="$FAKE_PORT"
|
||||
printf 'NUXT_DEV_BACKEND_ORIGIN=http://127.0.0.1:%s\nNUXT_INTERNAL_API_ORIGIN=http://127.0.0.1:%s\n' "$port" "$port" >"$dir/guard-control.conf"
|
||||
local out rc=0
|
||||
out="$(env -i PATH="$PATH" HOME="$HOME" DEV_STACK_ENV_FILE="$dir/guard-control.conf" DEV_STACK_GO_ADDR="127.0.0.1:$port" bash "$DEV_STACK" guard 2>&1)" || rc=$?
|
||||
if [[ "$rc" -ne 0 ]] || grep -q '^refuse:' <<<"$out"; then
|
||||
echo "$out" >&2
|
||||
echo "refuse: guard control case (a SummerCMS marker) was refused" >&2
|
||||
return 1
|
||||
fi
|
||||
echo "guard control: $out"
|
||||
echo "phase14.2 guard passed"
|
||||
}
|
||||
|
||||
run_e2e() {
|
||||
[[ -f "$DEV_STACK" ]] || {
|
||||
echo "refuse: $DEV_STACK not found" >&2
|
||||
return 1
|
||||
}
|
||||
local was_up=0 rc=0
|
||||
if bash "$DEV_STACK" status >/dev/null 2>&1; then
|
||||
was_up=1
|
||||
echo "e2e: the local stack is already up; it is left running"
|
||||
fi
|
||||
if ((was_up == 0)); then
|
||||
trap 'bash "$DEV_STACK" down || true' EXIT
|
||||
fi
|
||||
bash "$DEV_STACK" up --detach
|
||||
bash "$DEV_STACK" guard
|
||||
bash "$DEV_STACK" e2e "${GATE_SPECS[@]}"
|
||||
echo "e2e (report only, D-15): ${REPORT_SPECS[*]}"
|
||||
bash "$DEV_STACK" e2e --report-only "${REPORT_SPECS[@]}" 2>&1 | sed 's/^/e2e (report only) /' || rc=$?
|
||||
((rc == 0)) || echo "e2e (report only) run exited $rc (not blocking)"
|
||||
scan_go_log "$GO_LOG"
|
||||
echo "phase14.2 e2e passed"
|
||||
}
|
||||
|
||||
run_all() {
|
||||
local stage
|
||||
for stage in self-test go; do
|
||||
for stage in self-test go docs parity contracts guard e2e; do
|
||||
if bash "${BASH_SOURCE[0]}" "--$stage"; then
|
||||
echo "PASS $stage"
|
||||
else
|
||||
@@ -181,6 +549,11 @@ run_all() {
|
||||
case "${1:---all}" in
|
||||
--self-test) run_self_test ;;
|
||||
--go) run_go ;;
|
||||
--docs) run_docs ;;
|
||||
--parity) run_parity ;;
|
||||
--contracts) run_contracts ;;
|
||||
--guard) run_guard ;;
|
||||
--e2e) run_e2e ;;
|
||||
--all) run_all ;;
|
||||
*) usage ;;
|
||||
esac
|
||||
|
||||
Reference in New Issue
Block a user