You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

143 lines
4.4 KiB

from datetime import datetime, timezone
from io import StringIO
from pathlib import Path
import pytest
from django.core.management import call_command
from django.core.management.base import CommandError
from core.backup import BackupError, BackupResult, VerificationResult
def backup_result():
return BackupResult(
name="synthetic.dump",
path=Path("/private/backup/synthetic.dump"),
manifest_path=Path("/private/backup/synthetic.dump.json"),
version=1,
created_at=datetime(2026, 9, 8, tzinfo=timezone.utc),
postgresql_client_version="pg_dump (PostgreSQL) 17.6",
row_counts={"finance_account": 2, "finance_transaction": 4},
)
def test_backup_database_prints_only_safe_summary(monkeypatch):
monkeypatch.setattr(
"core.management.commands.backup_database.create_backup",
lambda name: backup_result(),
)
stdout = StringIO()
call_command("backup_database", output="synthetic.dump", stdout=stdout)
assert stdout.getvalue().splitlines() == [
"backup=synthetic.dump format=1",
"table=finance_account rows=2",
"table=finance_transaction rows=4",
]
assert "/private/backup" not in stdout.getvalue()
def test_backup_database_converts_service_error_to_safe_code(monkeypatch):
def fail(_name):
raise BackupError("backup_failed")
monkeypatch.setattr("core.management.commands.backup_database.create_backup", fail)
with pytest.raises(CommandError, match=r"^backup_failed$"):
call_command("backup_database", output="synthetic.dump")
def test_verify_backup_uses_configured_password_and_prints_safe_summary(
monkeypatch, settings
):
monkeypatch.setitem(settings.DATABASES, "default", {
"HOST": "restore-db",
"PORT": "5432",
"NAME": "gizmoduck_restore",
"USER": "restore_user",
"PASSWORD": "synthetic-restore-secret",
})
captured = {}
def verify(name, target):
captured["name"] = name
captured["target"] = target
return VerificationResult(
name=name,
version=1,
row_counts={"django_migrations": 12, "finance_account": 2},
)
monkeypatch.setattr("core.management.commands.verify_backup.verify_backup", verify)
stdout = StringIO()
call_command(
"verify_backup",
input="synthetic.dump",
target_name="gizmoduck_restore",
target_host="restore-db",
target_user="restore_user",
stdout=stdout,
)
assert captured["name"] == "synthetic.dump"
assert captured["target"].password == "synthetic-restore-secret"
assert stdout.getvalue().splitlines() == [
"backup=synthetic.dump format=1 verified",
"table=django_migrations rows=12",
"table=finance_account rows=2",
]
assert "synthetic-restore-secret" not in stdout.getvalue()
def test_verify_backup_converts_service_error_to_safe_code(monkeypatch, settings):
monkeypatch.setitem(settings.DATABASES, "default", {
"HOST": "restore-db",
"PORT": "5432",
"NAME": "gizmoduck_restore",
"USER": "restore_user",
"PASSWORD": "synthetic-restore-secret",
})
def fail(_name, _target):
raise BackupError("target_not_empty")
monkeypatch.setattr("core.management.commands.verify_backup.verify_backup", fail)
with pytest.raises(CommandError, match=r"^target_not_empty$"):
call_command(
"verify_backup",
input="synthetic.dump",
target_name="gizmoduck_restore",
target_host="restore-db",
target_user="restore_user",
)
def test_backup_restore_acceptance_command_prints_only_safe_summary(monkeypatch):
calls = 0
def verify():
nonlocal calls
calls += 1
return VerificationResult(
name="synthetic-restore-check.dump",
version=1,
row_counts={"finance_account": 1, "finance_transaction": 1},
)
monkeypatch.setattr(
"core.management.commands.test_backup_restore.run_synthetic_restore_verification",
verify,
)
stdout = StringIO()
call_command("test_backup_restore", stdout=stdout)
assert calls == 2
assert stdout.getvalue().splitlines() == [
"backup=synthetic-restore-check.dump format=1 verified",
"table=finance_account rows=1",
"table=finance_transaction rows=1",
]