Add stub-based test for backup and restore scripts

Runs backup.sh/restore.sh against a logging docker stub and checks
archive content, retention, data flow, .env recovery and the order of
stopping/starting the app.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Kyle Dammann
2026-10-30 22:03:31 +01:00
co-authored by Claude Opus 5.5
parent aec254518c
commit 74e11845de
2 changed files with 51 additions and 0 deletions
+1
View File
@@ -54,6 +54,7 @@ no JS build step: plain forms with small progressive enhancements in `static/js/
# Local (Windows dev box: venv lives at C:\Users\kyled\.venvs\kochbuch due to path-length issues)
python -m pytest # tests (SQLite)
python -m ruff check . # lint (config in pyproject.toml)
sh tests/shell/test_backup_restore.sh # backup/restore logic with a docker stub
# Postgres without Docker (dev box): pip install pgserver; start it, then
# POSTGRES_HOST=127.0.0.1 POSTGRES_PORT=<port> POSTGRES_USER=postgres POSTGRES_DB=postgres python -m pytest
# (the Windows pgserver build lacks tz data: copy tzdata/zoneinfo to pginstall/share/postgresql/timezone)
+50
View File
@@ -0,0 +1,50 @@
#!/bin/sh
# Tests the logic of scripts/backup.sh and scripts/restore.sh without Docker:
# a stub `docker` on PATH logs every call and emulates the compose commands used.
# sh tests/shell/test_backup_restore.sh
set -eu
ROOT=$(cd "$(dirname "$0")/../.." && pwd)
SP=$(mktemp -d)
trap 'rm -rf "$SP"' EXIT
mkdir -p "$SP/proj/scripts" "$SP/bin" "$SP/state/media/recipes"
cp "$ROOT/scripts/backup.sh" "$ROOT/scripts/restore.sh" "$SP/proj/scripts/"
echo "SECRET_KEY=abc" > "$SP/proj/.env"
echo "foto" > "$SP/state/media/recipes/a.webp"
cat > "$SP/bin/docker" <<STUB
#!/bin/sh
echo "\$*" >> "$SP/state/calls.log"
case "\$*" in
*"exec -T db sh -c"*pg_dump*) printf 'PGDUMP-CONTENT' ;;
*"exec -T app tar -C /data -czf - media"*) tar -C "$SP/state" -czf - media ;;
*"exec -T db sh -c"*pg_isready*) exit 0 ;;
*"exec -T db sh -c"*pg_restore*) cat > "$SP/state/restored.dump" ;;
*"run --rm --no-deps -T --entrypoint sh app -c"*) mkdir -p "$SP/state/restored" && tar -C "$SP/state/restored" -xzf - ;;
*) : ;;
esac
STUB
chmod +x "$SP/bin/docker"
PATH="$SP/bin:$PATH"
cd "$SP/proj"
fail() { echo "FAIL: $*" >&2; exit 1; }
mkdir -p backups
touch -d "2026-01-01" backups/kochbuch-20260101-000000.tar.gz
touch -d "2026-01-02" backups/kochbuch-20260102-000000.tar.gz
KEEP=2 sh scripts/backup.sh > /dev/null
[ "$(ls backups | wc -l)" -eq 2 ] || fail "retention should keep 2 archives"
[ ! -e backups/kochbuch-20260101-000000.tar.gz ] || fail "oldest archive should be deleted"
NEW=$(ls -1t backups/kochbuch-*.tar.gz | head -1)
[ "$(tar -tzf "$NEW" | sort | tr '\n' ' ')" = "db.dump env.backup media.tar.gz " ] || fail "archive content"
rm .env
sh scripts/restore.sh "$NEW" --yes > /dev/null
[ "$(cat "$SP/state/restored.dump")" = "PGDUMP-CONTENT" ] || fail "database not restored"
[ "$(cat "$SP/state/restored/media/recipes/a.webp")" = "foto" ] || fail "photos not restored"
[ "$(cat .env)" = "SECRET_KEY=abc" ] || fail ".env not restored"
grep -q "stop app" "$SP/state/calls.log" || fail "app must be stopped during restore"
[ "$(tail -1 "$SP/state/calls.log")" = "compose up -d app" ] || fail "app must be started last"
echo "OK: backup/restore scripts"