mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 20:13:56 +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
120 lines
4.6 KiB
Python
120 lines
4.6 KiB
Python
"""Live pass for the reading hour: applied once, never stamped on failure.
|
|
|
|
Split from test_shutdown_reading.py, which covers the base cut and the reset.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock
|
|
|
|
import pytest
|
|
|
|
from screen_locker._day import today_str
|
|
from screen_locker._shutdown_base import base_minutes
|
|
from screen_locker.tests._earned_fixtures import answering
|
|
from screen_locker.tests.test_shutdown_reading import apply_reading_bonus_if_new
|
|
|
|
if TYPE_CHECKING:
|
|
from collections.abc import Iterator
|
|
from pathlib import Path
|
|
|
|
|
|
def _mixin(*, adjust_ok: bool = True) -> MagicMock:
|
|
mixin = MagicMock()
|
|
mixin._read_shutdown_config.return_value = (base_minutes(), base_minutes(), 300)
|
|
mixin._write_shutdown_config.return_value = True
|
|
mixin._adjust_shutdown_time_by.return_value = adjust_ok
|
|
return mixin
|
|
|
|
|
|
@pytest.fixture(name="answers")
|
|
def answers_fixture() -> Iterator[tuple[dict[str, bool | None], MagicMock]]:
|
|
"""(answers, earned_today mock): every flat earner says no until set."""
|
|
given: dict[str, bool | None] = {"leetcode": False, "reading": False}
|
|
with answering(given) as mock:
|
|
yield given, mock
|
|
|
|
|
|
class TestReadingLivePass:
|
|
"""The live pass adds the reading hour once, after the reset."""
|
|
|
|
@pytest.mark.usefixtures("pre_ladder")
|
|
def test_applies_once_and_stamps(
|
|
self, tmp_path: Path, answers: tuple[dict[str, bool | None], MagicMock]
|
|
) -> None:
|
|
"""A reading credit is applied once and stamped, so the second pass does nothing."""
|
|
answers[0]["reading"] = True
|
|
state = tmp_path / "state.json"
|
|
state.write_text(json.dumps({"last_reset_date": today_str()}))
|
|
mixin = _mixin()
|
|
assert apply_reading_bonus_if_new(state, mixin) is True
|
|
assert apply_reading_bonus_if_new(state, mixin) is False
|
|
mixin._adjust_shutdown_time_by.assert_called_once_with(60, at=None)
|
|
assert json.loads(state.read_text()) == {
|
|
"last_reset_date": today_str(),
|
|
"reading_bonus_date": today_str(),
|
|
}
|
|
|
|
def test_stamped_today_never_reads_the_ledger(
|
|
self, tmp_path: Path, answers: tuple[dict[str, bool | None], MagicMock]
|
|
) -> None:
|
|
"""A day already stamped never queries the ledger again."""
|
|
state = tmp_path / "state.json"
|
|
state.write_text(json.dumps({"reading_bonus_date": today_str()}))
|
|
assert apply_reading_bonus_if_new(state, _mixin()) is False
|
|
answers[1].assert_not_called()
|
|
|
|
def test_no_reading_no_write(
|
|
self, tmp_path: Path, answers: tuple[dict[str, bool | None], MagicMock]
|
|
) -> None:
|
|
"""With no reading credit the live pass writes nothing."""
|
|
mixin = _mixin()
|
|
assert apply_reading_bonus_if_new(tmp_path / "state.json", mixin) is False
|
|
mixin._adjust_shutdown_time_by.assert_not_called()
|
|
|
|
def test_failed_write_leaves_no_stamp_and_warns(
|
|
self,
|
|
tmp_path: Path,
|
|
answers: tuple[dict[str, bool | None], MagicMock],
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""A stamp without a write would silently forfeit the hour for the day."""
|
|
answers[0]["reading"] = True
|
|
state = tmp_path / "state.json"
|
|
mixin = _mixin(adjust_ok=False)
|
|
with caplog.at_level("WARNING"):
|
|
assert apply_reading_bonus_if_new(state, mixin) is False
|
|
assert "reading bonus: failed to write" in caplog.text
|
|
assert not state.exists()
|
|
|
|
def test_cannot_check_warns_and_leaves_no_stamp(
|
|
self,
|
|
tmp_path: Path,
|
|
answers: tuple[dict[str, bool | None], MagicMock],
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Unknown is not "no": logged, no hour, and re-asked on the next tick."""
|
|
answers[0]["reading"] = None
|
|
state = tmp_path / "state.json"
|
|
mixin = _mixin()
|
|
with caplog.at_level("WARNING"):
|
|
assert apply_reading_bonus_if_new(state, mixin) is False
|
|
assert "reading state could not be checked" in caplog.text
|
|
mixin._adjust_shutdown_time_by.assert_not_called()
|
|
assert not state.exists()
|
|
|
|
@pytest.mark.usefixtures("pre_ladder")
|
|
def test_applied_hour_is_logged(
|
|
self,
|
|
tmp_path: Path,
|
|
answers: tuple[dict[str, bool | None], MagicMock],
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Applying the reading hour logs it."""
|
|
answers[0]["reading"] = True
|
|
with caplog.at_level("INFO"):
|
|
assert apply_reading_bonus_if_new(tmp_path / "state.json", _mixin())
|
|
assert "reading bonus: +1h shutdown time today." in caplog.text
|