mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-07-04 17:03:04 +02:00
Extracted from testsAndMisc monorepo. Changes: - Rewrote imports from python_pkg.screen_locker.* → screen_locker.* - Vendored python_pkg.shared.log_integrity → screen_locker._log_integrity - Vendored wake_alarm constants (ALARM_DAYS, WAKE_AFTER_HOURS, RTCWAKE_BIN) into _constants.py - Extracted has_workout_skip_today into new screen_locker._wake_state module - Added tests for _wake_state.py (392 tests, 100% branch coverage) - Moved scripts/service files to repo root - Added standalone pyproject.toml, requirements.txt, .pre-commit-config.yaml, .gitignore - Added GitHub Actions CI workflows Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
244 lines
8.6 KiB
Python
244 lines
8.6 KiB
Python
"""Tests for _weekly_check: is_relaxed_day, count_weekly_workouts,
|
|
has_weekly_minimum."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
import json
|
|
from typing import TYPE_CHECKING, Any
|
|
from unittest.mock import patch
|
|
|
|
from screen_locker._weekly_check import (
|
|
_RELAXED_WEEKDAYS,
|
|
WEEKLY_WORKOUT_MINIMUM,
|
|
count_weekly_workouts,
|
|
has_weekly_minimum,
|
|
is_relaxed_day,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _dt(weekday: int, hour: int = 10) -> datetime:
|
|
"""Return a UTC-aware datetime for the given ISO weekday (0=Mon, 6=Sun)."""
|
|
# 2025-05-19 is a Monday (weekday 0)
|
|
base = datetime(2025, 5, 19, hour, 0, 0, tzinfo=timezone.utc)
|
|
from datetime import timedelta
|
|
|
|
return base + timedelta(days=weekday)
|
|
|
|
|
|
def _make_log(entries: dict[str, str], log_file: Path) -> Path:
|
|
"""Write a workout_log.json with given date→workout_type mapping."""
|
|
data: dict[str, Any] = {
|
|
date: {
|
|
"timestamp": f"{date}T10:00:00+00:00",
|
|
"workout_data": {"type": wtype},
|
|
}
|
|
for date, wtype in entries.items()
|
|
}
|
|
log_file.write_text(json.dumps(data))
|
|
return log_file
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# is_relaxed_day
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestIsRelaxedDay:
|
|
def test_monday_is_enforced(self) -> None:
|
|
assert is_relaxed_day(today=_dt(0)) is False
|
|
|
|
def test_tuesday_is_relaxed(self) -> None:
|
|
assert is_relaxed_day(today=_dt(1)) is True
|
|
|
|
def test_wednesday_is_relaxed(self) -> None:
|
|
assert is_relaxed_day(today=_dt(2)) is True
|
|
|
|
def test_thursday_is_relaxed(self) -> None:
|
|
assert is_relaxed_day(today=_dt(3)) is True
|
|
|
|
def test_friday_is_enforced(self) -> None:
|
|
assert is_relaxed_day(today=_dt(4)) is False
|
|
|
|
def test_saturday_is_enforced(self) -> None:
|
|
assert is_relaxed_day(today=_dt(5)) is False
|
|
|
|
def test_sunday_is_enforced(self) -> None:
|
|
assert is_relaxed_day(today=_dt(6)) is False
|
|
|
|
def test_relaxed_weekdays_constant_correct(self) -> None:
|
|
assert frozenset({1, 2, 3}) == _RELAXED_WEEKDAYS
|
|
|
|
def test_uses_local_time_by_default(self) -> None:
|
|
result = is_relaxed_day()
|
|
assert isinstance(result, bool)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# count_weekly_workouts
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestCountWeeklyWorkouts:
|
|
def test_no_log_file_returns_zero(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_corrupt_json_returns_zero(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
log.write_text("{not valid json}")
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_oserror_returns_zero(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
log.write_text("{}")
|
|
with patch("builtins.open", side_effect=OSError("no permission")):
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_counts_phone_verified_in_current_week(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
# Mon=2025-05-19, Tue=2025-05-20 both in same week; check on Fri=2025-05-23
|
|
_make_log({"2025-05-19": "phone_verified", "2025-05-20": "phone_verified"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 2
|
|
|
|
def test_sick_day_not_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log({"2025-05-19": "sick_day"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_early_bird_not_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log({"2025-05-19": "early_bird"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_previous_week_not_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
# 2025-05-12 is the Monday of the previous week
|
|
_make_log({"2025-05-12": "phone_verified"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_future_date_not_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
# 2025-05-24 is Saturday, checking on Friday 2025-05-23
|
|
_make_log({"2025-05-24": "phone_verified"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_invalid_date_key_skipped(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
data: dict[str, Any] = {
|
|
"not-a-date": {
|
|
"timestamp": "x",
|
|
"workout_data": {"type": "phone_verified"},
|
|
},
|
|
"2025-05-19": {
|
|
"timestamp": "x",
|
|
"workout_data": {"type": "phone_verified"},
|
|
},
|
|
}
|
|
log.write_text(json.dumps(data))
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 1
|
|
|
|
def test_non_dict_entry_skipped(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
data: dict[str, Any] = {"2025-05-19": "not-a-dict"}
|
|
log.write_text(json.dumps(data))
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 0
|
|
|
|
def test_counts_up_to_four(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log(
|
|
{
|
|
"2025-05-19": "phone_verified",
|
|
"2025-05-20": "phone_verified",
|
|
"2025-05-21": "phone_verified",
|
|
"2025-05-22": "phone_verified",
|
|
},
|
|
log,
|
|
)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 4
|
|
|
|
def test_today_counts_if_this_week(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
# today is Friday 2025-05-23
|
|
_make_log({"2025-05-23": "phone_verified"}, log)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 1
|
|
|
|
def test_monday_start_of_week_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log({"2025-05-19": "phone_verified"}, log)
|
|
# Checking on Monday itself (today=Mon)
|
|
assert count_weekly_workouts(log, today=_dt(0)) == 1
|
|
|
|
def test_mixed_types_only_verified_counted(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log(
|
|
{
|
|
"2025-05-19": "phone_verified",
|
|
"2025-05-20": "sick_day",
|
|
"2025-05-21": "early_bird",
|
|
"2025-05-22": "phone_verified",
|
|
},
|
|
log,
|
|
)
|
|
assert count_weekly_workouts(log, today=_dt(4)) == 2
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# has_weekly_minimum
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
class TestHasWeeklyMinimum:
|
|
def test_zero_workouts_is_false(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
assert has_weekly_minimum(log, today=_dt(4)) is False
|
|
|
|
def test_three_workouts_is_false(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log(
|
|
{
|
|
"2025-05-19": "phone_verified",
|
|
"2025-05-20": "phone_verified",
|
|
"2025-05-21": "phone_verified",
|
|
},
|
|
log,
|
|
)
|
|
assert has_weekly_minimum(log, today=_dt(4)) is False
|
|
|
|
def test_four_workouts_is_true(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log(
|
|
{
|
|
"2025-05-19": "phone_verified",
|
|
"2025-05-20": "phone_verified",
|
|
"2025-05-21": "phone_verified",
|
|
"2025-05-22": "phone_verified",
|
|
},
|
|
log,
|
|
)
|
|
assert has_weekly_minimum(log, today=_dt(4)) is True
|
|
|
|
def test_five_workouts_is_true(self, tmp_path: Path) -> None:
|
|
log = tmp_path / "workout_log.json"
|
|
_make_log(
|
|
{
|
|
"2025-05-19": "phone_verified",
|
|
"2025-05-20": "phone_verified",
|
|
"2025-05-21": "phone_verified",
|
|
"2025-05-22": "phone_verified",
|
|
"2025-05-23": "phone_verified",
|
|
},
|
|
log,
|
|
)
|
|
assert has_weekly_minimum(log, today=_dt(4)) is True
|
|
|
|
def test_weekly_workout_minimum_constant(self) -> None:
|
|
assert WEEKLY_WORKOUT_MINIMUM == 4
|