- _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
99 lines
5.3 KiB
Bash
Executable File
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 ==="
|