mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-10 20:27:13 +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
176 lines
6.7 KiB
Python
176 lines
6.7 KiB
Python
"""Tests for manual-workout integration in _ui_flows.py and screen_lock.py."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date, datetime, time
|
|
import pathlib
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from screen_locker._day import today_str
|
|
from screen_locker._log_mixin import RecordResult
|
|
from screen_locker.tests.conftest import create_locker
|
|
|
|
# The Dart mirror of the budget caps; read at test time so drift on
|
|
# EITHER side fails, not just a local edit to the Python constants.
|
|
DART_MANUAL_WORKOUT = (
|
|
pathlib.Path(__file__).resolve().parents[2]
|
|
/ "stronglift_replacement"
|
|
/ "workout_app"
|
|
/ "lib"
|
|
/ "models"
|
|
/ "manual_workout.dart"
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
def _today_at(hhmm: str) -> float:
|
|
"""Unix time of ``hhmm`` local on the locker's today."""
|
|
hours, minutes = (int(p) for p in hhmm.split(":"))
|
|
day = date.fromisoformat(today_str())
|
|
return datetime.combine(day, time(hours, minutes)).astimezone().timestamp()
|
|
|
|
|
|
@pytest.mark.usefixtures("pre_ladder")
|
|
class TestApplyWorkoutCredit:
|
|
"""Tests for ScreenLocker._apply_workout_credit under the multi-per-day rule.
|
|
|
|
Credit is now driven by the :class:`RecordResult` the write chokepoint
|
|
returns (``appended`` + ``prior_entries``), not a separate
|
|
``_was_already_counted_today`` probe (removed).
|
|
"""
|
|
|
|
def test_skips_credit_when_duplicate_already_logged_today(
|
|
self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""save_workout_log reports a duplicate (appended=False) → no new credit."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
locker.workout_data = {"type": "manual_workout"}
|
|
object.__setattr__(
|
|
locker,
|
|
"save_workout_log",
|
|
MagicMock(return_value=RecordResult(appended=False, prior_entries=[])),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_try_adjust_shutdown_for_workout", MagicMock(return_value=True)
|
|
)
|
|
|
|
result = locker._apply_workout_credit()
|
|
|
|
assert result.already_counted_today is True
|
|
assert result.shutdown_adjusted is False
|
|
assert not result.extra_bonus_delta
|
|
locker._try_adjust_shutdown_for_workout.assert_not_called()
|
|
locker.save_workout_log.assert_called_once() # log entry write attempted
|
|
|
|
def test_first_workout_of_day_gets_base_shutdown_push(
|
|
self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""First counted workout of the day (empty prior_entries) → base +2h push."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
locker.workout_data = {"type": "manual_workout"}
|
|
object.__setattr__(
|
|
locker,
|
|
"save_workout_log",
|
|
MagicMock(return_value=RecordResult(appended=True, prior_entries=[])),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_try_adjust_shutdown_for_workout", MagicMock(return_value=True)
|
|
)
|
|
|
|
result = locker._apply_workout_credit()
|
|
|
|
assert result.already_counted_today is False
|
|
assert result.shutdown_adjusted is True
|
|
assert not result.extra_bonus_delta
|
|
locker._try_adjust_shutdown_for_workout.assert_called_once()
|
|
|
|
def test_additional_manual_workout_earns_plus_one_hour(
|
|
self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""A second manual workout on a day that already counted → +1h extra.
|
|
|
|
Manual workouts now stack intra-day shutdown credit exactly like
|
|
verified ones — the rate budget (not this stacking rule) is the sole
|
|
limiter on manual workouts. Mirrors
|
|
``test_additional_verified_workout_earns_plus_one_hour``.
|
|
"""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
locker.workout_data = {"type": "manual_workout", "end_time": "17:00"}
|
|
prior = [{"workout_data": {"type": "manual_workout"}}]
|
|
object.__setattr__(
|
|
locker,
|
|
"save_workout_log",
|
|
MagicMock(return_value=RecordResult(appended=True, prior_entries=prior)),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_try_adjust_shutdown_for_workout", MagicMock(return_value=True)
|
|
)
|
|
object.__setattr__(
|
|
locker,
|
|
"_read_shutdown_config",
|
|
MagicMock(
|
|
side_effect=[(1260, 1260, 300), (1320, 1320, 300), (1320, 1320, 300)]
|
|
),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_adjust_shutdown_time_by", MagicMock(return_value=True)
|
|
)
|
|
with patch(
|
|
"screen_locker._workout_credit.count_weekly_workouts", return_value=5
|
|
):
|
|
result = locker._apply_workout_credit()
|
|
|
|
assert result.weekly_count == 5
|
|
assert result.shutdown_adjusted is False
|
|
assert result.extra_bonus_delta == 60
|
|
locker._try_adjust_shutdown_for_workout.assert_not_called()
|
|
locker._adjust_shutdown_time_by.assert_called_once_with(
|
|
60, at=_today_at("17:00")
|
|
)
|
|
|
|
def test_additional_verified_workout_earns_plus_one_hour(
|
|
self, mock_tk: MagicMock, mock_sys_exit: MagicMock, tmp_path: Path
|
|
) -> None:
|
|
"""An additional same-day VERIFIED workout earns the +1h extra bonus.
|
|
|
|
prior_entries already holds a StrongLifts session; a RunnerUp run is a
|
|
distinct credit slot, so it takes the +1h branch, and
|
|
extra_bonus_delta reflects the shutdown-config minute delta.
|
|
"""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
locker.workout_data = {"type": "runnerup_verified"}
|
|
prior = [{"workout_data": {"type": "phone_verified"}}]
|
|
object.__setattr__(
|
|
locker,
|
|
"save_workout_log",
|
|
MagicMock(return_value=RecordResult(appended=True, prior_entries=prior)),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_try_adjust_shutdown_for_workout", MagicMock(return_value=True)
|
|
)
|
|
object.__setattr__(
|
|
locker,
|
|
"_read_shutdown_config",
|
|
MagicMock(
|
|
side_effect=[(1260, 1260, 300), (1320, 1320, 300), (1320, 1320, 300)]
|
|
),
|
|
)
|
|
object.__setattr__(
|
|
locker, "_adjust_shutdown_time_by", MagicMock(return_value=True)
|
|
)
|
|
with patch(
|
|
"screen_locker._workout_credit.count_weekly_workouts", return_value=5
|
|
):
|
|
result = locker._apply_workout_credit()
|
|
|
|
assert result.weekly_count == 5
|
|
assert result.shutdown_adjusted is False
|
|
assert result.extra_bonus_delta == 60
|
|
locker._try_adjust_shutdown_for_workout.assert_not_called()
|
|
locker._adjust_shutdown_time_by.assert_called_once_with(60, at=None)
|