Files

99 lines
3.8 KiB
Python
Raw Permalink Normal View History

"""The backup-check job: the newest dump and the markers under BACKUP_DIR."""
from __future__ import annotations
import os
import time
from datetime import UTC, datetime
from pathlib import Path
from typing import Any
import pytest
from pshelpers import make_settings, needs_pg
from packetstation.settings import Settings
from packetstation.worker import jobs, runs
from packetstation.worker.jobs import backup_check
from packetstation.worker.scheduler import Scheduler
HOUR = 3600
def put(folder: Path, name: str, data: bytes = b"PGDMP dump", hours_ago: float = 1.0) -> Path:
p = folder / name
p.write_bytes(data)
t = time.time() - hours_ago * HOUR
os.utime(p, (t, t))
return p
def test_fresh_dump_is_fine(tmp_path: Path) -> None:
put(tmp_path, "packetstation-test-20261020.dump", hours_ago=30)
put(tmp_path, "packetstation-test-20261021.dump", data=b"x" * 2048, hours_ago=2)
put(tmp_path, "last-success", b"", hours_ago=2)
put(tmp_path, "last-failure", b"", hours_ago=50)
put(tmp_path, "notes.txt", hours_ago=0)
counts, problems = backup_check.inspect(tmp_path)
assert problems == []
assert counts["dumps"] == 2 and counts["newest"] == "packetstation-test-20261021.dump"
assert counts["size"] == 2048 and 1.9 <= counts["age_hours"] <= 2.1
@pytest.mark.parametrize(
"setup,expect",
[
(lambda d: None, "No backup dump"),
(lambda d: put(d, "a.dump", hours_ago=27), "27 hours old"),
(lambda d: put(d, "a.dump", b"", hours_ago=1), "is empty"),
(
lambda d: (put(d, "a.dump"), put(d, "last-success", b"", 5), put(d, "last-failure", b"", 0.5)),
"last backup run failed",
),
(lambda d: (put(d, "a.dump"), put(d, "last-failure", b"", 0.5)), "last backup run failed"),
],
)
def test_problems(tmp_path: Path, setup: Any, expect: str) -> None:
setup(tmp_path)
_, problems = backup_check.inspect(tmp_path)
assert any(expect in p for p in problems), problems
def test_missing_folder(tmp_path: Path) -> None:
counts, problems = backup_check.inspect(tmp_path / "nope")
assert counts["dumps"] == 0 and problems and "not there" in problems[0]
def test_age_uses_the_given_clock(tmp_path: Path) -> None:
p = put(tmp_path, "a.dump", hours_ago=0)
at = datetime.fromtimestamp(p.stat().st_mtime, UTC)
_, problems = backup_check.inspect(tmp_path, now=at.replace(year=at.year + 1))
assert problems and "hours old" in problems[0]
@needs_pg
def test_job_raises_then_resolves(pg_settings: Settings, tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
calls: list[tuple[str, str, dict[str, Any]]] = []
monkeypatch.setattr(jobs, "raise_event", lambda conn, t, **kw: calls.append(("raise", t, kw)))
monkeypatch.setattr(jobs, "resolve_event", lambda conn, t, **kw: calls.append(("resolve", t, kw)))
folder = tmp_path / "backups"
folder.mkdir()
s = pg_settings.model_copy(update={"backup_dir": folder})
sch = Scheduler(s, build="pytest", jobs={"backup-check": backup_check.SPEC})
row = runs.run_job(sch, "backup-check", "schedule")
assert row["status"] == "ok" and row["counts"]["dumps"] == 0 and row["counts"]["problems"]
((kind, etype, kw),) = calls
assert (kind, etype, kw["subject"], kw["condition"]) == ("raise", "backup_stale", "ci", True)
assert kw["title"] == "Backup failed or stale (ci)" and "No backup dump" in kw["body"]
calls.clear()
put(folder, "ps.dump")
put(folder, "last-success", b"")
row = runs.run_job(sch, "backup-check", "schedule")
assert row["counts"]["problems"] == [] and row["counts"]["newest"] == "ps.dump"
assert calls == [("resolve", "backup_stale", {"subject": "ci", "title": "Backup failed or stale (ci)"})]
def test_settings_backup_dir_default(tmp_path: Path) -> None:
assert make_settings(tmp_path).backup_dir == tmp_path / "no-backups"