screen-locker/screen_locker/tests/test_sync_tombstones.py
Krzysztof kuhy Rudnicki aa3e290391 Document every test, split files at the cap
Real one-line docstrings stating what each test pins down. Files that
would pass 250 lines moved whole classes into _part2 siblings
(sick_panel, earned, poke_server, shutdown_reading,
manual_workout_dialog).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
2026-11-01 21:11:34 +01:00

161 lines
6.5 KiB
Python

"""Tests for the durable sync-tombstone ledger and its place in every push.
Guards the 2026-09-13 finding: a tombstone written once to the PC's device log
was gone by the next tick, because ``sync_log`` never reads this device's own
remote log and ``_manual_push`` re-derives it from ``log.json`` every time.
"""
from __future__ import annotations
import json
from typing import TYPE_CHECKING
from crdt_sync import Hlc, Record
from screen_locker import _sync_tombstones
from screen_locker._manual_push import records_from_workout_log
from screen_locker._sync_tombstones import add_tombstone, tombstone_records
if TYPE_CHECKING:
from pathlib import Path
_DEVICE = "57582b3b-01c6-45cc-9674-ad2b95bebf29"
_RID = "manual:2026-09-13T14:30"
_MANUAL = {"type": "manual_workout", "source": "football 11v11", "start_time": "14:30"}
class TestAddTombstone:
"""The ledger is written once per id, with a clock minted at that moment."""
def test_creates_the_ledger_with_hlc_reason_and_time(self, tmp_path: Path) -> None:
"""The first tombstone creates the ledger with a device-stamped HLC, reason and time."""
path = tmp_path / "sync_tombstones.json"
assert add_tombstone(_RID, _DEVICE, "duplicate", path) is True
entry = json.loads(path.read_text())[_RID]
assert Hlc.from_str(entry["deleted_hlc"]).node_id == _DEVICE
assert entry["reason"] == "duplicate"
assert entry["tombstoned_at"].startswith("20")
def test_second_add_is_a_no_op_and_keeps_the_original_clock(
self, tmp_path: Path
) -> None:
"""Tombstoning an id twice returns False and leaves the file, and its clock, untouched."""
path = tmp_path / "sync_tombstones.json"
add_tombstone(_RID, _DEVICE, "first", path)
before = path.read_text()
assert add_tombstone(_RID, _DEVICE, "second", path) is False
assert path.read_text() == before
def test_adds_alongside_existing_ids(self, tmp_path: Path) -> None:
"""A new tombstone is added next to the ids already in the ledger."""
path = tmp_path / "sync_tombstones.json"
add_tombstone(_RID, _DEVICE, "a", path)
add_tombstone("manual:2026-01-01T08:00", _DEVICE, "b", path)
assert set(json.loads(path.read_text())) == {_RID, "manual:2026-01-01T08:00"}
class TestTombstoneRecords:
"""Ledger → crdt_sync Records, loud about anything it cannot push."""
def test_missing_ledger_is_empty(self, tmp_path: Path) -> None:
"""A ledger file that does not exist yields no records."""
assert not tombstone_records(tmp_path / "absent.json")
def test_ledgered_id_becomes_a_deleted_record(self, tmp_path: Path) -> None:
"""A ledgered id becomes a deleted Record carrying its stored HLC."""
path = tmp_path / "sync_tombstones.json"
add_tombstone(_RID, _DEVICE, "duplicate", path)
record = tombstone_records(path)[_RID]
assert record == Record(
id=_RID,
fields={},
deleted=True,
deleted_hlc=Hlc.from_str(json.loads(path.read_text())[_RID]["deleted_hlc"]),
)
def test_same_clock_on_every_read(self, tmp_path: Path) -> None:
"""Re-pushing must not churn the store: the HLC is stored, not re-minted."""
path = tmp_path / "sync_tombstones.json"
add_tombstone(_RID, _DEVICE, "duplicate", path)
assert tombstone_records(path) == tombstone_records(path)
def test_corrupt_ledger_pushes_nothing_and_warns(
self, tmp_path: Path, caplog
) -> None:
"""A corrupt ledger file pushes nothing and logs that it is unreadable."""
path = tmp_path / "sync_tombstones.json"
path.write_text("{not json")
assert not tombstone_records(path)
assert "unreadable" in caplog.text
def test_non_object_ledger_is_ignored_and_warns(
self, tmp_path: Path, caplog
) -> None:
"""A ledger that is not a JSON object is ignored with a warning."""
path = tmp_path / "sync_tombstones.json"
path.write_text("[]")
assert not tombstone_records(path)
assert "not a JSON object" in caplog.text
def test_non_dict_entries_are_dropped(self, tmp_path: Path) -> None:
"""Ledger entries that are not dicts are dropped."""
path = tmp_path / "sync_tombstones.json"
path.write_text(json.dumps({_RID: "garbage"}))
assert not tombstone_records(path)
def test_entry_without_usable_hlc_is_skipped_and_warns(
self, tmp_path: Path, caplog
) -> None:
"""An entry with no usable deleted_hlc is skipped with a warning."""
path = tmp_path / "sync_tombstones.json"
path.write_text(json.dumps({_RID: {"reason": "x"}}))
assert not tombstone_records(path)
assert "no usable deleted_hlc" in caplog.text
class TestPushCarriesTombstones:
"""records_from_workout_log replaces the live copy with the tombstone."""
def _log(self, tmp_path: Path) -> Path:
log_file = tmp_path / "log.json"
log_file.write_text(
json.dumps(
{
"2026-09-13": [
{
"timestamp": "2026-09-13T07:40:06+00:00",
"workout_data": _MANUAL,
"workout_id": _RID,
}
]
}
)
)
return log_file
def test_live_entry_is_pushed_when_not_ledgered(self, tmp_path: Path) -> None:
"""An entry absent from the tombstone ledger is pushed as a live record."""
log = records_from_workout_log(self._log(tmp_path))
assert log[_RID].deleted is False
def test_ledgered_entry_is_pushed_as_a_tombstone(self, tmp_path: Path) -> None:
"""The isolated SYNC_TOMBSTONES_FILE (conftest) is what the push reads."""
add_tombstone(_RID, _DEVICE, "duplicate", _sync_tombstones.SYNC_TOMBSTONES_FILE)
log = records_from_workout_log(self._log(tmp_path))
assert log[_RID].deleted is True
assert log[_RID].fields == {}
def test_tombstone_for_an_id_not_in_the_log_is_still_pushed(
self, tmp_path: Path
) -> None:
"""A tombstone is pushed even for an id the workout log no longer holds."""
add_tombstone(
"manual:2026-01-01T08:00",
_DEVICE,
"x",
_sync_tombstones.SYNC_TOMBSTONES_FILE,
)
log = records_from_workout_log(self._log(tmp_path))
assert log["manual:2026-01-01T08:00"].deleted is True
assert log[_RID].deleted is False