Files
dashcaddy/scripts/test-start-sh-sync.sh
Hermes Agent 56f1a001f2
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled
start.sh auto-sync dashboard bundle into /var/www/dashcaddy-status/
After every rebuild the freshly-baked dashboard bundle lives in
/opt/dashcaddy/status/dist/ + sw.js. DNS2 also serves files from
/var/www/dashcaddy-status/dist/ + sw.js (the original Windows-installer
mirror path). Without an explicit copy step between build and start.sh,
the served bundle stays on whatever hash was there before, while the API
responds with new code. That mismatch is what shows up in the dashboard
as "version unavailable" + "no data" widgets — saw it in the deploy
that followed DC-046/047 (fixed by a manual cp this time, never again).

Sync block runs before docker run:
  cp /opt/dashcaddy/status/dist/*.js /var/www/dashcaddy-status/dist/
  cp /opt/dashcaddy/status/sw.js   /var/www/dashcaddy-status/
  cp /opt/dashcaddy/status/index.html /var/www/dashcaddy-status/

All  guarded so set -e doesn't kill the container start on a
single per-file failure (e.g. read-only mount, missing dir). Missing
source dir is a WARN + no-op rather than a fatal — fresh installs
without status/dist/ don't get a stale-bundle problem, just a log line.

Test: scripts/test-start-sh-sync.sh — 7 assertions across 4 cases
(fresh-copy, missing-source, idempotent-re-sync, set-e-survives-permission-
denied). All pass.
2026-07-20 02:07:25 -07:00

152 lines
4.7 KiB
Bash
Executable File

#!/bin/bash
# Regression test: start.sh dashboard bundle sync step.
#
# Validates the auto-sync that runs before `docker run` in start.sh:
# 1. Bundles are coppied from /opt/dashcaddy/status/dist → /var/www/dashcaddy-status/dist
# 2. sw.js is copied to /var/www/dashcaddy-status/sw.js
# 3. index.html is copied to /var/www/dashcaddy-status/index.html
# 4. Missing source dir is handled gracefully (WARN, no exit)
# 5. Sync is idempotent — re-running doesn't clobber or duplicate
set -u
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
FAILURES=0
pass() { echo " ✓ $1"; }
fail() { echo " ✗ $1"; FAILURES=$((FAILURES + 1)); }
# --------------------------------------------------------------------------
# Extract just the sync block from start.sh. The first call to `docker run`
# marks the end of the sync section.
# --------------------------------------------------------------------------
extract_sync_block() {
awk '
/^# Sync the freshly-built dashboard bundle/ { capture=1 }
/^docker run -d --restart unless-stopped/ { capture=0 }
capture { print }
' "${SCRIPT_DIR}/../start.sh"
}
fresh_workdir() {
local d; d="$(mktemp -d /tmp/dashcaddy-sync-test.XXXXXX)"
mkdir -p "$d/src/dist"
mkdir -p "$d/dst/dist"
echo "$d"
}
cleanup() { rm -rf "$1"; }
run_sync() {
local workdir="$1"
# Use absolute replacement paths that don't collide with sed prefixes.
# Inject a unique sentinel into start.sh paths first, then replace that
# sentinel with the test-specific paths.
local src_sentinel="__SYNC_TEST_SRC__"
local dst_sentinel="__SYNC_TEST_DST__"
local SRC_DIST_DIR="${workdir}/src/dist"
local DST_STATUS_DIR="${workdir}/dst"
extract_sync_block | sed \
-e "s|/opt/dashcaddy/status/dist|${src_sentinel}|g" \
-e "s|/var/www/dashcaddy-status|${dst_sentinel}|g" \
-e "s|${src_sentinel}|${SRC_DIST_DIR}|g" \
-e "s|${dst_sentinel}|${DST_STATUS_DIR}|g" \
> /tmp/_sync_block.sh
# shellcheck disable=SC1091
source /tmp/_sync_block.sh
}
# Test 1: Bundles + sw.js + index.html all get synced from a real source tree
echo "Test 1: fresh source → files synced to status dir"
WORKDIR="$(fresh_workdir)"
SRC="${WORKDIR}/src/dist"
echo 'console.log("core");' > "${SRC}/core.js"
echo 'console.log("features");' > "${SRC}/features.js"
echo 'self.addEventListener("fetch", ...)' > "${SRC}/sw.js"
echo '<html><body>hi</body></html>' > "${SRC}/index.html"
run_sync "${WORKDIR}"
if [ -f "${WORKDIR}/dst/dist/core.js" ]; then
pass "core.js copied to status dir"
else
fail "core.js NOT copied"
fi
if [ -f "${WORKDIR}/dst/dist/features.js" ]; then
pass "features.js copied"
else
fail "features.js NOT copied"
fi
if [ -f "${WORKDIR}/dst/sw.js" ]; then
pass "sw.js copied (NOT under dist/)"
else
fail "sw.js NOT copied"
fi
if [ -f "${WORKDIR}/dst/index.html" ]; then
pass "index.html copied"
else
fail "index.html NOT copied"
fi
cleanup "${WORKDIR}"
# Test 2: Missing source dir → WARN, no exit, no crash, no destinations created
echo
echo "Test 2: source dir missing → WARN, no exit"
WORKDIR="$(fresh_workdir)"
# SRC intentionally NOT created
run_sync "${WORKDIR}"
# If we reach here without an exit code propagating, we passed
if [ -z "$(ls -A "${WORKDIR}/dst/dist" 2>/dev/null)" ]; then
pass "no files copied when source missing"
else
fail "files copied when source was missing — should be no-op"
fi
cleanup "${WORKDIR}"
# Test 3: Re-running syncs (idempotent — files overwritten with same content)
echo
echo "Test 3: idempotency (re-sync overwrites with same content)"
WORKDIR="$(fresh_workdir)"
SRC="${WORKDIR}/src/dist"
echo 'core-v1' > "${SRC}/core.js"
run_sync "${WORKDIR}"
echo 'core-v2' > "${SRC}/core.js" # mutate source
run_sync "${WORKDIR}"
if [ "$(cat "${WORKDIR}/dst/dist/core.js")" = "core-v2" ]; then
pass "second sync picked up the latest source"
else
fail "second sync did not update"
fi
cleanup "${WORKDIR}"
# Test 4: set -e propagation — a single file-level failure doesn't take down the script
echo
echo "Test 4: set -e does not kill container start when sync hits a permission error"
WORKDIR="$(fresh_workdir)"
SRC="${WORKDIR}/src/dist"
echo 'core' > "${SRC}/core.js"
DST="${WORKDIR}/dst/dist"
chmod 555 "${DST}" # write-protected dest → cp -f would fail
# shellcheck disable=SC1091
set -e
run_sync "${WORKDIR}"
EXIT=$?
set +e
chmod 755 "${DST}" 2>/dev/null
cleanup "${WORKDIR}"
if [ "$EXIT" -eq 0 ]; then
pass "sync step survives a permission-denied dest"
else
fail "set -e propagated a sync failure (exit ${EXIT}); would prevent container from starting"
fi
rm -f /tmp/_sync_block.sh
echo
if [ "$FAILURES" -eq 0 ]; then
echo "All sync regression tests passed."
exit 0
fi
echo "${FAILURES} test(s) failed."
exit 1