mirror of
https://github.com/kuhyx/screen-locker.git
synced 2026-11-11 17:13:59 +01:00
Six new modules, all composed or re-exported so no caller changes:
_shutdown.py (384) -> _shutdown_sick_state.py SickDayStateMixin
_wake_alarm.py WakeAlarmMixin
_phone_verification.py (380)-> _adb_transport.py AdbTransportMixin
_http_workout_fetch.py HttpWorkoutFetchMixin
_status_data.py (370) -> _status_types.py the 7 dataclasses
_status_projection.py shutdown projection
+ format_summary_line
Moving a name means moving where it is *read*, so 30-odd mock patch targets
move with it. Most importantly conftest.py's autouse _block_real_network
fixture: ruff correctly dropped the now-unused `socket` import from
_phone_verification, which silently removed the guard's target and would have
let subnet probes reach the real network. It now blocks socket in both modules
that actually own the probes, and both targets are asserted to exist.
976 tests pass at 100% branch coverage; pylint 8.70 -> 8.71; `screen_lock
--status` renders the real week unchanged. File-length gate: 41 -> 35.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EveB813BQ8abAP68VSqZ5Y
189 lines
6.0 KiB
Python
189 lines
6.0 KiB
Python
"""Tests for rtcwake integration in ShutdownMixin."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from screen_locker.tests.conftest import create_locker
|
|
|
|
if TYPE_CHECKING:
|
|
from pathlib import Path
|
|
|
|
|
|
class TestIsTomorrowAlarmDay:
|
|
"""Tests for _is_tomorrow_alarm_day."""
|
|
|
|
def test_sunday_evening_means_monday_alarm(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Sunday evening → Monday is alarm day (weekday=0)."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
from datetime import datetime, timezone
|
|
|
|
# Sunday 2026-04-12 → tomorrow Monday
|
|
with patch(
|
|
"screen_locker._wake_alarm.datetime",
|
|
) as mock_dt:
|
|
mock_dt.now.return_value = datetime(2026, 4, 12, 23, 0, tzinfo=timezone.utc)
|
|
mock_dt.side_effect = datetime
|
|
from datetime import timedelta
|
|
|
|
# Ensure timedelta works
|
|
with patch(
|
|
"screen_locker._wake_alarm.timedelta",
|
|
timedelta,
|
|
):
|
|
assert locker._is_tomorrow_alarm_day() is True
|
|
|
|
def test_monday_evening_is_not_alarm_next(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Monday evening → Tuesday is NOT an alarm day."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
# Monday 2026-04-13 → tomorrow Tuesday (weekday=1)
|
|
with (
|
|
patch(
|
|
"screen_locker._wake_alarm.datetime",
|
|
) as mock_dt,
|
|
patch(
|
|
"screen_locker._wake_alarm.timedelta",
|
|
timedelta,
|
|
),
|
|
):
|
|
mock_dt.now.return_value = datetime(2026, 4, 13, 23, 0, tzinfo=timezone.utc)
|
|
mock_dt.side_effect = datetime
|
|
assert locker._is_tomorrow_alarm_day() is False
|
|
|
|
def test_thursday_evening_friday_is_alarm(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Thursday evening → Friday is alarm day (weekday=4)."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
from datetime import datetime, timedelta, timezone
|
|
|
|
# Thursday 2026-04-16 → tomorrow Friday (weekday=4)
|
|
with (
|
|
patch(
|
|
"screen_locker._wake_alarm.datetime",
|
|
) as mock_dt,
|
|
patch(
|
|
"screen_locker._wake_alarm.timedelta",
|
|
timedelta,
|
|
),
|
|
):
|
|
mock_dt.now.return_value = datetime(2026, 4, 16, 23, 0, tzinfo=timezone.utc)
|
|
mock_dt.side_effect = datetime
|
|
assert locker._is_tomorrow_alarm_day() is True
|
|
|
|
|
|
class TestScheduleRtcwake:
|
|
"""Tests for _schedule_rtcwake."""
|
|
|
|
def test_success(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Successful rtcwake call returns True."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
with patch(
|
|
"screen_locker._wake_alarm.subprocess.run",
|
|
) as mock_run:
|
|
mock_run.return_value = MagicMock(returncode=0)
|
|
assert locker._schedule_rtcwake() is True
|
|
mock_run.assert_called_once()
|
|
cmd = mock_run.call_args[0][0]
|
|
assert "rtcwake" in cmd[1]
|
|
|
|
def test_failure_returns_false(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Failed rtcwake call returns False."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
import subprocess
|
|
|
|
with patch(
|
|
"screen_locker._wake_alarm.subprocess.run",
|
|
side_effect=subprocess.SubprocessError("rtcwake failed"),
|
|
):
|
|
assert locker._schedule_rtcwake() is False
|
|
|
|
|
|
class TestScheduleWakeIfNeeded:
|
|
"""Tests for schedule_wake_if_needed."""
|
|
|
|
def test_skips_when_not_alarm_day(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Returns False when tomorrow is not an alarm day."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
with patch.object(locker, "_is_tomorrow_alarm_day", return_value=False):
|
|
assert locker.schedule_wake_if_needed() is False
|
|
|
|
def test_schedules_when_alarm_day(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Returns True when tomorrow is an alarm day and rtcwake succeeds."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
with (
|
|
patch.object(locker, "_is_tomorrow_alarm_day", return_value=True),
|
|
patch.object(locker, "_schedule_rtcwake", return_value=True),
|
|
):
|
|
assert locker.schedule_wake_if_needed() is True
|
|
|
|
def test_returns_false_when_rtcwake_fails(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Returns False when rtcwake call fails."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
with (
|
|
patch.object(locker, "_is_tomorrow_alarm_day", return_value=True),
|
|
patch.object(locker, "_schedule_rtcwake", return_value=False),
|
|
):
|
|
assert locker.schedule_wake_if_needed() is False
|
|
|
|
|
|
class TestComputeWakeTimestamp:
|
|
"""Tests for _compute_wake_timestamp."""
|
|
|
|
def test_returns_future_epoch(
|
|
self,
|
|
mock_tk: MagicMock,
|
|
mock_sys_exit: MagicMock,
|
|
tmp_path: Path,
|
|
) -> None:
|
|
"""Wake timestamp is roughly 8 hours from now."""
|
|
locker = create_locker(mock_tk, tmp_path)
|
|
import time
|
|
|
|
now = int(time.time())
|
|
wake = locker._compute_wake_timestamp()
|
|
# Should be ~8 hours ahead (within 60 second tolerance)
|
|
expected = now + 8 * 3600
|
|
assert abs(wake - expected) < 60
|