mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 21:07:14 +01:00
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
105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
"""Tests for ``--backfill-workout-ledger``: idempotent, per source, range-aware."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime
|
|
import json
|
|
from typing import TYPE_CHECKING, Any
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from screen_locker._rest_day import declare
|
|
from screen_locker._workout_ledger import ledger_path
|
|
from screen_locker._workout_ledger_cli import backfill_rows, run_backfill
|
|
from screen_locker.tests._earned_fixtures import signing_key
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
_DAY = date(2026, 10, 4)
|
|
_ISO = _DAY.isoformat()
|
|
|
|
|
|
@pytest.fixture
|
|
def key(tmp_path: Path) -> Iterator[Path]:
|
|
"""Provide a signing key in a temp dir for the duration of the test."""
|
|
with signing_key(tmp_path) as path:
|
|
yield path
|
|
|
|
|
|
def entry(data: dict[str, Any]) -> dict[str, Any]:
|
|
"""Build a log entry around the given workout data."""
|
|
return {"timestamp": "2026-10-04T20:00:00+00:00", "workout_data": data}
|
|
|
|
|
|
def ledger() -> list[dict[str, Any]]:
|
|
"""Read back the entries stored in the signed ledger file."""
|
|
return json.loads(ledger_path().read_text())["entries"]
|
|
|
|
|
|
@pytest.mark.usefixtures("key")
|
|
class TestBackfill:
|
|
"""The backfill CLI rebuilds missing ledger rows from the workout log and rest days."""
|
|
|
|
def _log(self, tmp_path: Path) -> Path:
|
|
log = tmp_path / "log.json"
|
|
log.write_text(
|
|
json.dumps(
|
|
{
|
|
_ISO: [
|
|
entry({"type": "phone_verified"}),
|
|
entry({"type": "relaxed_day_skip"}),
|
|
],
|
|
"2026-10-05": [
|
|
entry({"type": "runnerup_verified", "completed_at": 9.0})
|
|
],
|
|
}
|
|
)
|
|
)
|
|
return log
|
|
|
|
def test_rows_for_a_range_and_its_rest_days(self, tmp_path: Path) -> None:
|
|
"""Backfill yields rows for the log entries in range plus its declared rest days."""
|
|
with patch("screen_locker._rest_day.datetime") as clock:
|
|
clock.now.return_value = datetime(2026, 10, 1).astimezone()
|
|
assert declare(date(2026, 10, 6)).ok
|
|
ledger_path().unlink() # the declaration mirrored it; backfill must too
|
|
rows = backfill_rows(self._log(tmp_path), _DAY, date(2026, 10, 6))
|
|
assert [r["detail"]["source"] for r in rows] == [
|
|
"stronglifts",
|
|
"runnerup_tcx",
|
|
"rest_day",
|
|
]
|
|
|
|
def test_dry_run_prints_and_writes_nothing(
|
|
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""A dry run reports missing rows per source and writes no ledger."""
|
|
argv = ["--dry-run", "--from", _ISO, "--to", "2026-10-05"]
|
|
assert not run_backfill(self._log(tmp_path), argv)
|
|
out = capsys.readouterr().out
|
|
assert "(missing)" in out
|
|
assert "per source: {'runnerup_tcx': 1, 'stronglifts': 1}" in out
|
|
assert not ledger_path().exists()
|
|
|
|
def test_a_real_run_is_idempotent(
|
|
self, tmp_path: Path, capsys: pytest.CaptureFixture[str]
|
|
) -> None:
|
|
"""Running the backfill twice adds rows only the first time."""
|
|
log = self._log(tmp_path)
|
|
assert not run_backfill(log, ["--from", _ISO, "--to", "2026-10-05"])
|
|
assert not run_backfill(log, ["--from", _ISO, "--to", "2026-10-05"])
|
|
out = capsys.readouterr().out.splitlines()
|
|
assert out[0].startswith("2 new row(s)")
|
|
assert out[1].startswith("0 new row(s)")
|
|
assert not run_backfill(log, ["--dry-run"])
|
|
assert "(present)" in capsys.readouterr().out
|
|
|
|
def test_default_is_today(self, tmp_path: Path) -> None:
|
|
"""With no range given, the backfill covers today only."""
|
|
with patch("screen_locker._workout_ledger_cli.today_str", return_value=_ISO):
|
|
assert not run_backfill(self._log(tmp_path), [])
|
|
assert len(ledger()) == 1
|