Files
chat/tests/test_backup.py
T
2026-04-26 14:18:57 -04:00

93 lines
3.2 KiB
Python

"""Tests for nightly DB backups (T32).
The backup service is intentionally simple: a flat ``data/backups/`` dir
containing timestamped copies of ``chat.db``, with retention of the most
recent 14. The scheduling decision (``should_take_backup``) is a pure
function of clock + filesystem state so it can be unit-tested without
spinning up the BackgroundWorker tick loop.
"""
from __future__ import annotations
from datetime import datetime
from unittest.mock import patch
from chat.services.backup import (
prune_backups,
should_take_backup,
take_backup,
)
def test_take_backup_creates_timestamped_copy(tmp_path):
db = tmp_path / "chat.db"
db.write_text("fake db contents")
backup_path = take_backup(db_path=db, data_dir=tmp_path / "data")
assert backup_path.exists()
assert backup_path.name.startswith("chat-")
assert backup_path.name.endswith(".db")
# Contents copied
assert backup_path.read_text() == "fake db contents"
# Located in data/backups/
assert backup_path.parent == tmp_path / "data" / "backups"
def test_prune_keeps_last_14(tmp_path):
backup_dir = tmp_path / "data" / "backups"
backup_dir.mkdir(parents=True)
# Create 17 dummy backup files spanning days 1..17 of Jan 2026.
# Filenames sort lexicographically by the embedded timestamp, so
# prune_backups should drop the three oldest.
for i in range(1, 18):
(backup_dir / f"chat-202601{i:02d}T000000Z.db").write_text(
f"backup {i}"
)
removed = prune_backups(tmp_path / "data", keep=14)
assert removed == 3
remaining = sorted(backup_dir.glob("chat-*.db"))
assert len(remaining) == 14
# Days 1, 2, 3 removed; day 4 is now the oldest retained backup.
assert remaining[0].name == "chat-20260104T000000Z.db"
def test_should_take_backup_when_no_prior_and_target_hour_matches(tmp_path):
from chat.services import backup as backup_mod
class FakeDateTime(datetime):
@classmethod
def now(cls, tz=None):
return datetime(2026, 4, 26, 3, 0, 0)
with patch.object(backup_mod, "datetime", FakeDateTime):
assert should_take_backup(tmp_path / "data") is True
def test_should_not_take_backup_outside_target_hour(tmp_path):
from chat.services import backup as backup_mod
class FakeDateTime(datetime):
@classmethod
def now(cls, tz=None):
return datetime(2026, 4, 26, 14, 0, 0)
with patch.object(backup_mod, "datetime", FakeDateTime):
assert should_take_backup(tmp_path / "data") is False
def test_should_not_take_backup_when_recent_backup_exists(tmp_path):
backup_dir = tmp_path / "data" / "backups"
backup_dir.mkdir(parents=True)
recent = backup_dir / "chat-recent.db"
recent.write_text("x")
# mtime defaults to "now" — within the 23h freshness window so
# should_take_backup must return False even at the target hour.
from chat.services import backup as backup_mod
class FakeDateTime(datetime):
@classmethod
def now(cls, tz=None):
return datetime(2026, 4, 26, 3, 0, 0)
with patch.object(backup_mod, "datetime", FakeDateTime):
assert should_take_backup(tmp_path / "data") is False