mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 12:27:45 +01:00
Booleaness asserts, hoisted imports, fixture(name=...) instead of shadowing arguments, explicit encodings, snake_case names, comparison of bound methods, dead protected-access disables. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WzxdWYdwRAtMg7ki6TKxuU
150 lines
5.6 KiB
Python
150 lines
5.6 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:
|
|
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:
|
|
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:
|
|
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:
|
|
assert not tombstone_records(tmp_path / "absent.json")
|
|
|
|
def test_ledgered_id_becomes_a_deleted_record(self, tmp_path: Path) -> None:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|