Files
dashcaddy/scripts/test-frontend-cycle.sh
T
DashCaddy Polish Loop 70e252c8a5
CI / Test & Lint (push) Canceled after 0s
CI / Security audit (push) Canceled after 0s
[grade=A urn:ump:seyasvbntxsjibq5jiaqmfowc6xgchwe4jvcc54jd6355ujgm75q] DC-122: self-updater hardening + auto-update on + docker disk discipline (v1.16.0)
- _isNewer: same-version releases are never 'newer' (commit labels are
  opaque stamps) — kills the same-version auto-apply regression loop
- _autoCheckAndApply: identical version@sha256 never re-applied
- dashcaddy-update.sh: truthful rollback verdicts (failed rebuild/health
  = exit 1 + failure result), exact frontend snapshot/restore (incl.
  update-introduced owned subtrees), contents-copy cp fallbacks with
  manifest reconciliation, JSON-encoded results/meta/stamp, prune on
  every exit path
- self-updater: frontend-only Linux releases fail loudly (no more silent
  no-op success stuck in 'applying')
- start.sh: DASHCADDY_UPDATE_ENABLED=true (Sami 2026-09-13), json-file
  log caps 10M x3, source->webroot sync via update-stamp contract
- tests: _isNewer regression suite, functional cycle + json/escape suites
  (scripts/test-frontend-cycle.sh, scripts/test-json-escape.sh)

15 judge rounds: C,C,D,D,C,C,D,C,C,C,D,C,A
2026-09-13 05:32:14 -07:00

99 lines
5.3 KiB
Bash
Executable File

#!/usr/bin/env bash
# DC-122 full-cycle functional test: backup → deploy → rollback on a CUSTOM
# frontend target, proving (a) rollback restores the custom target, (b) an
# unrelated sibling directory is never touched, (c) invalid targets are
# refused. Sources the real functions from the real script.
set -euo pipefail
# Updater script path: $1 overrides; default = adjacent to this test file,
# falling back to the installed location (works in repo, worktree, and prod).
SCRIPT="${1:-}"
if [[ -z "$SCRIPT" ]]; then
# Worktree layouts prefix files with numbers (2_dashcaddy-update.sh);
# repo layout is unprefixed. Resolve whichever exists, adjacent first.
local_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
for cand in "$local_dir/dashcaddy-update.sh" \
"$local_dir"/*dashcaddy-update.sh \
/opt/dashcaddy/scripts/dashcaddy-update.sh; do
[[ -f "$cand" ]] && SCRIPT="$cand" && break
done
fi
[[ -f "$SCRIPT" ]] || { echo "updater script not found"; exit 1; }
# Trap-covered workspace for ALL temp artifacts (collision-safe).
WORK=$(mktemp -d) || { echo "mktemp failed"; exit 1; }
trap 'rm -rf "$WORK"' EXIT
# Extract just the functions + constants we need (no main execution).
sed -n '/^FRONTEND_SUBDIRS=/p;/^json_escape()/,/^}/p;/^backup_frontend_dir()/,/^}/p;/^restore_frontend_dir()/,/^}/p;/^validate_frontend_target()/,/^}/p' "$SCRIPT" > "$WORK/dcfuncs.sh"
log() { echo "[t] $*"; }
source "$WORK/dcfuncs.sh"
T="$WORK/tree"
mkdir -p "$T"
CUSTOM_TARGET="$T/custom-webroot"
VICTIM="$T/custom-webroot-sibling"
mkdir -p "$CUSTOM_TARGET/dist" "$CUSTOM_TARGET/js" "$VICTIM"
# Pre-update state
echo v1 > "$CUSTOM_TARGET/index.html"
echo v1 > "$CUSTOM_TARGET/sw.js"
echo v1 > "$CUSTOM_TARGET/dist/core.js"
echo v1 > "$CUSTOM_TARGET/js/app.js"
echo secret > "$VICTIM/precious.txt"
BACKUP="$T/backup/v1.15.0"
mkdir -p "$BACKUP"
# 1. backup (as the update flow does, with the custom target)
backup_frontend_dir "$BACKUP" "$CUSTOM_TARGET"
[[ -f "$BACKUP/frontend.meta" ]] && [[ -f "$BACKUP/frontend.manifest" ]] || { echo "FAIL: snapshot incomplete"; exit 1; }
grep -q "$CUSTOM_TARGET" "$BACKUP/frontend.meta" || { echo "FAIL: meta lost target"; exit 1; }
echo "PASS backup: meta + manifest written, target recorded"
# 2. "deploy" a new version (simulates what the update does)
echo v2 > "$CUSTOM_TARGET/index.html"
echo v2 > "$CUSTOM_TARGET/dist/core.js"
echo new > "$CUSTOM_TARGET/dist/newfile.js" # update-introduced file
mkdir -p "$CUSTOM_TARGET/dist/newdir" && echo x > "$CUSTOM_TARGET/dist/newdir/f.js"
printf '{"version":"v2"}' > "$CUSTOM_TARGET/update-stamp.json"
# 3. rollback
restore_frontend_dir "$BACKUP" || { echo "FAIL: restore errored"; exit 1; }
[[ "$(cat "$CUSTOM_TARGET/index.html")" == v1 ]] && echo "PASS: index.html rolled back" || { echo "FAIL: index.html"; exit 1; }
[[ "$(cat "$CUSTOM_TARGET/dist/core.js")" == v1 ]] && echo "PASS: dist/core.js rolled back" || { echo "FAIL: core.js"; exit 1; }
[[ ! -e "$CUSTOM_TARGET/dist/newfile.js" ]] && echo "PASS: update-introduced file removed" || { echo "FAIL: newfile.js survived"; exit 1; }
[[ ! -e "$CUSTOM_TARGET/dist/newdir" ]] && echo "PASS: update-introduced dir removed" || { echo "FAIL: newdir survived"; exit 1; }
[[ ! -f "$CUSTOM_TARGET/update-stamp.json" ]] && echo "PASS: stamp cleared on rollback" || { echo "FAIL: stamp survived"; exit 1; }
[[ "$(cat "$VICTIM/precious.txt")" == secret ]] && echo "PASS: sibling directory untouched" || { echo "FAIL: VICTIM MODIFIED"; exit 1; }
# 4. invalid targets are refused outright (relative path = traversal risk class)
BACKUP2="$T/backup2"; mkdir -p "$BACKUP2/frontend"
printf '{"target":"relative/not-absolute","indexExisted":false,"swExisted":false}\n' > "$BACKUP2/frontend.meta"
( cd "$BACKUP2/frontend" && find . -type f -printf '%P\n' | sort ) > "$BACKUP2/frontend.manifest"
if restore_frontend_dir "$BACKUP2" 2>/dev/null; then
echo "FAIL: relative target was accepted"; exit 1
fi
echo "PASS: invalid (relative) target refused"
# 5. a validated custom target round-trips: snapshot→destroy→restore
CUSTOM2="$T/custom2-webroot"; mkdir -p "$CUSTOM2/js"
echo orig > "$CUSTOM2/js/only.js"
BACKUP3="$T/backup3"; mkdir -p "$BACKUP3"
backup_frontend_dir "$BACKUP3" "$CUSTOM2"
rm -rf "$CUSTOM2/js" && mkdir -p "$CUSTOM2/js" && echo clobbered > "$CUSTOM2/js/only.js"
restore_frontend_dir "$BACKUP3" >/dev/null 2>&1 || { echo "FAIL: custom-target restore errored"; exit 1; }
[[ "$(cat "$CUSTOM2/js/only.js")" == orig ]] && echo "PASS: validated custom target restored" || { echo "FAIL: custom restore"; exit 1; }
# 6. update-introduced OWNED subtree (css/ absent at backup, created by
# "deploy") must be REMOVED by rollback — not left behind.
CUSTOM3="$T/custom3-webroot"; mkdir -p "$CUSTOM3/dist" # note: NO css/ yet
echo v1 > "$CUSTOM3/dist/core.js"
BACKUP4="$T/backup4"; mkdir -p "$BACKUP4"
backup_frontend_dir "$BACKUP4" "$CUSTOM3"
mkdir -p "$CUSTOM3/css" && echo new > "$CUSTOM3/css/introduced.css" # update creates css/
restore_frontend_dir "$BACKUP4" >/dev/null 2>&1 || { echo "FAIL: introduced-subtree restore errored"; exit 1; }
[[ ! -e "$CUSTOM3/css" ]] && echo "PASS: update-introduced owned subtree removed" || { echo "FAIL: css/ survived rollback"; exit 1; }
[[ "$(cat "$CUSTOM3/dist/core.js")" == v1 ]] && echo "PASS: pre-existing subtree intact" || { echo "FAIL: dist broken"; exit 1; }
echo "=== ALL FULL-CYCLE TESTS PASSED ==="