From 9f1dd7a914dde9d7aea255f61a354825a8378869 Mon Sep 17 00:00:00 2001 From: archeious Date: Fri, 17 Apr 2026 11:51:55 -0600 Subject: [PATCH] test: cover backup-db.sh exit paths and slug sanitisation Asserts soft exit when the source DB is missing, a successful backup round-trips sqlite rows, a non-file sqlite URL is rejected, the reason slug defaults to "manual", and a messy reason is sanitised to a safe filename fragment. Refs #5 Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/test_backup_script.py | 97 +++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) create mode 100644 tests/test_backup_script.py diff --git a/tests/test_backup_script.py b/tests/test_backup_script.py new file mode 100644 index 0000000..bf0078c --- /dev/null +++ b/tests/test_backup_script.py @@ -0,0 +1,97 @@ +from __future__ import annotations + +import os +import shutil +import sqlite3 +import subprocess +from pathlib import Path + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "scripts" / "backup-db.sh" + + +def _run(args: list[str], env_overrides: dict[str, str], cwd: Path): + env = os.environ.copy() + env.update(env_overrides) + return subprocess.run( + [str(SCRIPT), *args], + check=False, + capture_output=True, + text=True, + cwd=str(cwd), + env=env, + ) + + +def test_backup_exit_zero_when_db_missing(tmp_path): + fake_db = tmp_path / "ghost.db" + result = _run( + [], + {"QUARTERMASTER_DB_URL": f"sqlite:///{fake_db}"}, + tmp_path, + ) + assert result.returncode == 0, result.stderr + assert "nothing to back up" in result.stdout + + +def test_backup_creates_file(tmp_path): + # seed a real sqlite DB + db_path = tmp_path / "qm.db" + conn = sqlite3.connect(str(db_path)) + conn.execute("CREATE TABLE t (x INTEGER)") + conn.execute("INSERT INTO t VALUES (1), (2), (3)") + conn.commit() + conn.close() + + result = _run( + ["unit-test"], + {"QUARTERMASTER_DB_URL": f"sqlite:///{db_path}"}, + tmp_path, + ) + assert result.returncode == 0, result.stderr + backup_dir = tmp_path / "backups" + assert backup_dir.is_dir() + backups = list(backup_dir.glob("quartermaster-*-unit-test.db")) + assert len(backups) == 1 + # restored backup reads the same rows + restored = sqlite3.connect(str(backups[0])) + rows = list(restored.execute("SELECT x FROM t ORDER BY x")) + restored.close() + assert rows == [(1,), (2,), (3,)] + + +def test_backup_rejects_non_sqlite_url(tmp_path): + result = _run( + [], + {"QUARTERMASTER_DB_URL": "postgres://example"}, + tmp_path, + ) + assert result.returncode == 1 + assert "not a sqlite URL" in result.stderr + + +def test_backup_slug_defaults_to_manual(tmp_path): + db_path = tmp_path / "qm.db" + sqlite3.connect(str(db_path)).close() + result = _run( + [], + {"QUARTERMASTER_DB_URL": f"sqlite:///{db_path}"}, + tmp_path, + ) + assert result.returncode == 0, result.stderr + backups = list((tmp_path / "backups").glob("*manual*.db")) + assert len(backups) == 1 + + +def test_backup_slug_sanitizes_reason(tmp_path): + db_path = tmp_path / "qm.db" + sqlite3.connect(str(db_path)).close() + result = _run( + ["alembic upgrade!! head"], + {"QUARTERMASTER_DB_URL": f"sqlite:///{db_path}"}, + tmp_path, + ) + assert result.returncode == 0, result.stderr + backups = list((tmp_path / "backups").glob("*.db")) + assert len(backups) == 1 + assert "alembic-upgrade-head" in backups[0].name