DC-040: repurpose post-deploy-patches.sh as a verifier (fail-loud, not patch-and-continue)
CI / Test & Lint (push) Has been cancelled
CI / Security audit (push) Has been cancelled

Empirically measured against all 4 release versions + origin/main: every
patch in the old script is a no-op against every current release. v1.14.4
(the version that originally needed patches) doesn't even ship src/ in the
tarball — the old script silently no-op'd on it because it couldn't find
files to patch, then the build crashed with MODULE_NOT_FOUND in production.

Repurposed as a verifier: 5 hard checks (server.js requires, license-manager
path, src/ tree presence, license-keygen.js at root, generic src/ require
path scan) + informational warnings. Exits 1 on ANY failure with a clear
'Build should be ABORTED' message naming the v1.14.4-class bug if relevant.
Old behaviour was 'patch and continue' (silently hid regressions); new
behaviour is 'fail loud' (every regression now produces a build abort).

Files changed:
- scripts/dashcaddy-post-deploy-patches.sh — rewritten as verifier (222→274
  lines, header explains the empirical evidence + behaviour change)
- dashcaddy-api/scripts/test-dashcaddy-post-deploy-verifier.sh — new
  regression test, 17 assertions across 10 scenarios (clean tree, missing
  files, broken requires, empty src/, missing app.js, absolute path, etc.)

Empirical measurements documented:
- origin/main: 5/5 checks pass
- v1.14.9 (latest): 5/5 checks pass (0 patches applied under old script)
- v1.14.8: 5/5 checks pass (0 patches applied under old script)
- v1.14.4: 2/5 checks FAIL under new verifier (src/ missing, license-manager
  in wrong location) — old script silently no-op'd on the same input

Tests: 1214/1214 Jest + 31 shell assertions. Lint: 150 warnings, all
pre-existing in untouched files (zero new warnings introduced).
This commit is contained in:
Hermes
2026-07-13 15:09:08 -07:00
parent b13960fa9a
commit cbe0c912fc
3 changed files with 335 additions and 174 deletions
@@ -0,0 +1,197 @@
#!/usr/bin/env bash
# Regression test for dashcaddy-post-deploy-patches.sh (the verifier).
# Run from the dashcaddy-api/scripts/ directory:
# bash test-dashcaddy-post-deploy-verifier.sh
# Exit 0 = all assertions pass, non-zero = failure.
#
# The verifier has FIVE checks:
# 1. server.js exists + uses './src/...' requires (not '../src/...')
# 2. license-manager.js exists in src/managers/ + uses '../../license-keygen'
# 3. src/ directory exists, non-empty, contains src/app.js
# 4. license-keygen.js exists at API root
# 5. src/ require paths — informational warnings only, does not fail build
#
# Test strategy: build synthetic API_DIR trees (clean, broken) and assert the
# right checks pass/fail. No network calls, no real tarballs required.
set -euo pipefail
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Resolve verifier script — prefer local copy, fall back to canonical /root/dashcaddy/scripts/
VERIFY_SCRIPT="${SCRIPT_DIR}/dashcaddy-post-deploy-patches.sh"
if [[ ! -f "$VERIFY_SCRIPT" ]]; then
ALT="$(cd "${SCRIPT_DIR}/../../scripts" 2>/dev/null && pwd)/dashcaddy-post-deploy-patches.sh"
[[ -f "$ALT" ]] && VERIFY_SCRIPT="$ALT"
fi
if [[ ! -f "$VERIFY_SCRIPT" ]]; then
echo "FAIL: dashcaddy-post-deploy-patches.sh not found (looked in ${SCRIPT_DIR} and ${SCRIPT_DIR}/../../scripts)"
exit 1
fi
pass=0
fail=0
assert_exit_0() {
local desc="$1"; shift
if "$@" >/dev/null 2>&1; then
echo " PASS: $desc"
pass=$(( pass + 1 ))
else
echo " FAIL: $desc — expected exit 0, got $?"
fail=$(( fail + 1 ))
fi
}
assert_exit_nonzero() {
local desc="$1"; shift
if "$@" >/dev/null 2>&1; then
echo " FAIL: $desc — expected non-zero exit, got 0"
fail=$(( fail + 1 ))
else
echo " PASS: $desc"
pass=$(( pass + 1 ))
fi
}
assert_output_contains() {
local desc="$1" needle="$2"; shift 2
local output
output=$("$@" 2>&1 || true)
if echo "$output" | grep -q "$needle"; then
echo " PASS: $desc"
pass=$(( pass + 1 ))
else
echo " FAIL: $desc — '$needle' not in output:"
echo "$output" | head -10 | sed 's/^/ /'
fail=$(( fail + 1 ))
fi
}
# Build a clean API_DIR tree — all 5 checks should pass.
build_clean_api_dir() {
local d="$1"
mkdir -p "$d/src/managers"
cat > "$d/server.js" << 'EOF'
const { createApp } = require('./src/app');
const platformPaths = require('./platform-paths');
EOF
cat > "$d/license-keygen.js" << 'EOF'
module.exports = { verifyCode: () => true };
EOF
cat > "$d/src/app.js" << 'EOF'
module.exports = { createApp: () => ({}) };
EOF
cat > "$d/src/managers/license-manager.js" << 'EOF'
const keygen = require('../../license-keygen');
module.exports = { load: () => keygen };
EOF
}
# ── Test 1: clean tree — verifier passes 5/5 ─────────────────────────────────
echo "=== Test 1: clean tree (all checks should pass) ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
assert_exit_0 "clean tree passes verifier" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports 5/5" "5/5 checks passed" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 2: missing server.js — fails check 1 ────────────────────────────────
echo "=== Test 2: server.js missing → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
rm "$TMP/api/server.js"
assert_exit_nonzero "missing server.js fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports server.js failure" "server.js: file missing" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 3: server.js with broken '../src/' requires — fails check 1 ────────
echo "=== Test 3: server.js with '../src/' requires → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
cat > "$TMP/api/server.js" << 'EOF'
const { createApp } = require('../src/app');
EOF
assert_exit_nonzero "broken server.js fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports '../src/' breakage" "../src/" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 4: src/ directory missing — fails check 3 (v1.14.4-class bug) ──────
echo "=== Test 4: src/ missing (v1.14.4-class bug) → fails loudly ==="
TMP=$(mktemp -d)
mkdir -p "$TMP/api"
cat > "$TMP/api/server.js" << 'EOF'
const { createApp } = require('./src/app');
EOF
cat > "$TMP/api/license-keygen.js" << 'EOF'
module.exports = {};
EOF
# No src/ at all
assert_exit_nonzero "missing src/ fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "names v1.14.4-class bug" "v1.14.4-class bug" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 5: license-keygen.js missing at root — fails check 4 ───────────────
echo "=== Test 5: license-keygen.js missing at root → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
rm "$TMP/api/license-keygen.js"
assert_exit_nonzero "missing license-keygen.js fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports license-keygen.js missing" "license-keygen.js: missing" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 6: license-manager.js with broken './license-keygen' — fails check 2
echo "=== Test 6: license-manager.js uses broken './license-keygen' → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
cat > "$TMP/api/src/managers/license-manager.js" << 'EOF'
const keygen = require('./license-keygen');
module.exports = {};
EOF
assert_exit_nonzero "broken license-manager.js fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports broken license-manager path" "broken './license-keygen'" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 7: empty src/ directory — fails check 3 ────────────────────────────
echo "=== Test 7: src/ exists but is empty → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
rm -rf "$TMP/api/src"
mkdir -p "$TMP/api/src"
assert_exit_nonzero "empty src/ fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports empty src/" "directory is empty" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 8: src/ exists but missing app.js — fails check 3 ──────────────────
echo "=== Test 8: src/ present but missing app.js → fails ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
rm "$TMP/api/src/app.js"
assert_exit_nonzero "missing src/app.js fails build" bash "$VERIFY_SCRIPT" "$TMP/api"
assert_output_contains "reports missing src/app.js" "src/app.js: missing" bash "$VERIFY_SCRIPT" "$TMP/api"
rm -rf "$TMP"
# ── Test 9: absolute path handling (verify cd doesn't break path resolution) ─
echo "=== Test 9: relative vs absolute API_DIR both work ==="
TMP=$(mktemp -d)
build_clean_api_dir "$TMP/api"
# Run from a DIFFERENT cwd to prove absolute path resolution
(cd /tmp && assert_exit_0 "absolute path works from different cwd" bash "$VERIFY_SCRIPT" "$TMP/api")
rm -rf "$TMP"
# ── Test 10: API_DIR doesn't exist → exits non-zero with clear error ────────
echo "=== Test 10: non-existent API_DIR → fails clearly ==="
assert_exit_nonzero "non-existent API_DIR fails" bash "$VERIFY_SCRIPT" "/tmp/does-not-exist-xyz-12345"
# ── Summary ──────────────────────────────────────────────────────────────────
echo
echo "═══════════════════════════════════════════"
echo " dashcaddy-post-deploy-patches.sh (verifier) test"
echo " PASS: $pass FAIL: $fail"
echo "═══════════════════════════════════════════"
if (( fail > 0 )); then
exit 1
fi
echo "All tests passed."