test(deploy): add opt-in display-device scenarios to the split smoke
CI / verify (pull_request) Successful in 53s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 20s
CI / deploy-smoke (same-origin) (pull_request) Successful in 48s
CI / deploy-smoke (split) (pull_request) Successful in 55s
CI / verify (pull_request) Successful in 53s
CI / infrastructure-smoke (pull_request) Successful in 13s
CI / migration-smoke (pull_request) Successful in 1m48s
CI / image-build (pull_request) Successful in 20s
CI / deploy-smoke (same-origin) (pull_request) Successful in 48s
CI / deploy-smoke (split) (pull_request) Successful in 55s
Reproduction harness for what the first production Signage deployment found. With EKKLESIA_SMOKE_DISPLAY_DEVICE=1, the split smoke starts a second headless Chromium (the "kiosk") whose profile directory is a Docker volume. It pairs that kiosk through the real flow, closes it cleanly, restarts it on the same profile, and then runs injected-failure scenarios: two pages on one credential, a connectivity blip, one refused viewer-session request, one refused realtime connection, a cross-origin image-caching probe, a full realtime recycle, and revocation. Every DevTools command and page-side request has its own deadline, and each step has a budget. A step that overruns prints the exact call it was waiting for. The proxy's "aborting with incomplete response" lines are listed afterwards by UTC time and path only. Off by default: several scenarios expect Signage and fail on today's v0.1.1, by design. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
b2efb4b029
commit
ff8a3e8097
@@ -15,6 +15,12 @@
|
||||
# With EKKLESIA_SMOKE_IMAGE, the given image is used as is in either topology; a split run then
|
||||
# exercises whichever of its modules publishes UI and a display.
|
||||
#
|
||||
# Opt-in, split topology only: EKKLESIA_SMOKE_DISPLAY_DEVICE=1 adds the display-device scenarios in
|
||||
# scripts/lib/deploy-smoke-display.mjs (pairing a kiosk browser with a persistent profile, a clean
|
||||
# restart, two pages, injected network failures, revocation), each with its own time budget.
|
||||
# EKKLESIA_SMOKE_DISPLAY_SCENARIOS selects a subset; EKKLESIA_SMOKE_WATCH_MS and
|
||||
# EKKLESIA_SMOKE_RECYCLE_WATCH_MS change how long they watch. Several scenarios expect Signage.
|
||||
#
|
||||
# TEST-ONLY: the proxy runs with Caddy's internal CA (`local_certs`) instead of ACME, because a test
|
||||
# run has no public hostname. Never use that option in an installation.
|
||||
#
|
||||
@@ -141,6 +147,12 @@ cleanup() {
|
||||
if [[ -n "$browser_container" ]]; then
|
||||
docker rm --force "$browser_container" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ -n "${kiosk_container:-}" ]]; then
|
||||
docker ps --all --quiet --filter "name=${project}-display-" | xargs -r docker rm --force \
|
||||
>/dev/null 2>&1 || true
|
||||
docker rm --force "$kiosk_container" >/dev/null 2>&1 || true
|
||||
docker volume rm --force "$kiosk_volume" >/dev/null 2>&1 || true
|
||||
fi
|
||||
if [[ -f "$work/compose.env" ]]; then
|
||||
"${compose[@]}" --profile ops down --volumes --remove-orphans --timeout 10 >/dev/null 2>&1 || status=1
|
||||
fi
|
||||
@@ -404,6 +416,81 @@ if [[ "$topology" == split ]]; then
|
||||
-e SMOKE_OWNER_EMAIL="$owner_email" -e SMOKE_OWNER_PASSWORD="$owner_password" \
|
||||
-v "$daemon_work/browser.mjs:/smoke/browser.mjs:ro" \
|
||||
--entrypoint node "$image" /smoke/browser.mjs || fail "browser checks failed."
|
||||
|
||||
if [[ -n "${EKKLESIA_SMOKE_DISPLAY_DEVICE:-}" ]]; then
|
||||
# A kiosk: a second browser whose profile directory is a volume, so it outlives the container
|
||||
# exactly as a kiosk's --user-data-dir outlives a browser restart.
|
||||
kiosk_container="${project}-kiosk"
|
||||
kiosk_volume="${project}-kiosk-profile"
|
||||
kiosk_address="10.249.${octet}.250"
|
||||
docker volume create "$kiosk_volume" >/dev/null
|
||||
start_kiosk() {
|
||||
docker run --detach --rm --name "$kiosk_container" \
|
||||
--cap-drop ALL --security-opt no-new-privileges --tmpfs /tmp \
|
||||
--network "$edge_network" --ip "$kiosk_address" "${host_mappings[@]}" \
|
||||
-v "${kiosk_volume}:/profile" \
|
||||
"$browser_image" \
|
||||
"--ignore-certificate-errors-spki-list=${spki}" \
|
||||
--user-data-dir=/profile >/dev/null || fail "the kiosk browser did not start."
|
||||
}
|
||||
wait_kiosk_exit() {
|
||||
for _ in $(seq 1 60); do
|
||||
docker inspect "$kiosk_container" >/dev/null 2>&1 || return 0
|
||||
sleep 0.5
|
||||
done
|
||||
fail "the kiosk browser did not exit after a clean close."
|
||||
}
|
||||
cp scripts/lib/deploy-smoke-display.mjs "$work/display.mjs"
|
||||
stage display.mjs
|
||||
# display_driver <budget seconds> <step>: one scenario with a hard budget. The driver enforces it
|
||||
# itself and names whatever call was still waiting; `timeout` is the backstop if it cannot.
|
||||
display_driver() {
|
||||
local budget="$1" step_name="$2" driver="${project}-display-${2}"
|
||||
timeout --signal=TERM "$((budget + 30))" \
|
||||
docker run --rm --name "$driver" --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
--network "$edge_network" \
|
||||
-e SMOKE_CDP_ADDRESS="${browser_address}:9222" \
|
||||
-e SMOKE_KIOSK_CDP_ADDRESS="${kiosk_address}:9222" \
|
||||
-e SMOKE_STEP_BUDGET_MS="$((budget * 1000))" \
|
||||
-e SMOKE_WATCH_MS="${EKKLESIA_SMOKE_WATCH_MS:-120000}" \
|
||||
-e SMOKE_RECYCLE_WATCH_MS="${EKKLESIA_SMOKE_RECYCLE_WATCH_MS:-330000}" \
|
||||
-e SMOKE_HOSTNAME="$hostname" -e SMOKE_DASHBOARD_HOSTNAME="$dashboard_hostname" \
|
||||
-e SMOKE_OWNER_EMAIL="$owner_email" -e SMOKE_OWNER_PASSWORD="$owner_password" \
|
||||
-v "$daemon_work/display.mjs:/smoke/display.mjs:ro" \
|
||||
--entrypoint node "$image" /smoke/display.mjs "$step_name"
|
||||
local status=$?
|
||||
if ((status == 124)); then
|
||||
echo "FAIL ${step_name}: the driver did not stop within its budget plus 30s; removed it"
|
||||
docker rm --force "$driver" >/dev/null 2>&1 || true
|
||||
fi
|
||||
return "$status"
|
||||
}
|
||||
display_status=0
|
||||
step "Pairing a kiosk display, then closing its browser cleanly"
|
||||
start_kiosk
|
||||
display_driver 180 pair || display_status=1
|
||||
wait_kiosk_exit
|
||||
step "Restarting the kiosk browser on the same profile"
|
||||
start_kiosk
|
||||
display_driver 120 restart || display_status=1
|
||||
for scenario in ${EKKLESIA_SMOKE_DISPLAY_SCENARIOS:-two-pages connectivity-blip session-blip stream-failure caching-probe stream-recycle revoke}; do
|
||||
case "$scenario" in
|
||||
two-pages) budget=240 ;;
|
||||
stream-recycle) budget=420 ;;
|
||||
revoke) budget=210 ;;
|
||||
*) budget=150 ;;
|
||||
esac
|
||||
step "Display scenario: ${scenario} (budget ${budget}s)"
|
||||
display_driver "$budget" "$scenario" || display_status=1
|
||||
done
|
||||
step "Proxy: streams aborted with an incomplete response (UTC time, path only)"
|
||||
# Captured, never printed: only each line's time and path (no query, so no signature) leave it.
|
||||
proxy_logs="$("${compose[@]}" logs --no-color proxy 2>/dev/null)" || proxy_logs=""
|
||||
grep -o '{.*"aborting with incomplete response".*}' <<<"$proxy_logs" |
|
||||
sed -E 's/.*"ts":([0-9]+)[^,]*,.*"uri":"([^"?]*)[^"]*".*/\1 \2/' |
|
||||
while read -r ts uri; do echo " $(date -u -d "@${ts}" +%T) ${uri}"; done || true
|
||||
((display_status == 0)) || fail "display-device checks failed."
|
||||
fi
|
||||
fi
|
||||
|
||||
step "Proving the API's database connection is certificate-verified TLS"
|
||||
|
||||
Reference in New Issue
Block a user