Introduce a styles/ plugin package: - styles/base.py: AlarmStyle ABC (injected set_led, update()->bool contract) - styles/blink.py: BlinkStyle, the existing count-the-blinks puzzle moved out of wecker.py's AlarmClock - styles/simple.py: SimpleStyle, press-once-to-stop (the new default style) - styles/__init__.py: STYLES registry + get_style() validator + LEGACY_STYLE wecker.py now resolves the style via the registry and owns only the shared music/button/cleanup loop; the --style CLI arg defaults to blink so existing cron entries keep their behaviour. Unknown styles raise before hardware init.
74 lines
2.3 KiB
Python
74 lines
2.3 KiB
Python
import pytest
|
|
import sys
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# Mock RPi.GPIO and pygame before importing wecker
|
|
sys.modules["RPi"] = MagicMock()
|
|
sys.modules["RPi.GPIO"] = MagicMock()
|
|
sys.modules["pygame"] = MagicMock()
|
|
|
|
import wecker # noqa: E402
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_gpio():
|
|
with patch.object(wecker, "GPIO") as mock:
|
|
mock.LOW = 0
|
|
mock.HIGH = 1
|
|
yield mock
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_pygame():
|
|
with patch.object(wecker, "pygame") as mock:
|
|
yield mock
|
|
|
|
|
|
def test_set_led(mock_gpio):
|
|
wecker.set_led(True)
|
|
mock_gpio.output.assert_called_with(wecker.LED_PIN, mock_gpio.LOW)
|
|
|
|
wecker.set_led(False)
|
|
mock_gpio.output.assert_called_with(wecker.LED_PIN, mock_gpio.HIGH)
|
|
|
|
|
|
@patch("wecker.time.time")
|
|
def test_run_alarm_plays_music(mock_time, mock_gpio, mock_pygame):
|
|
mock_pygame.mixer.get_init.return_value = True
|
|
wecker.run_alarm(test_mode=True)
|
|
assert mock_pygame.mixer.music.play.called
|
|
|
|
|
|
def test_run_alarm_unknown_style_raises(mock_gpio, mock_pygame):
|
|
"""An unknown style is rejected before any hardware is touched."""
|
|
with pytest.raises(ValueError, match="Unknown alarm style"):
|
|
wecker.run_alarm(style_name="nope", test_mode=True)
|
|
mock_pygame.mixer.init.assert_not_called()
|
|
|
|
|
|
def test_run_alarm_uses_selected_style(mock_gpio, mock_pygame):
|
|
"""run_alarm looks up the style by name and drives the returned style."""
|
|
mock_pygame.mixer.get_init.return_value = True
|
|
fake = MagicMock()
|
|
fake.update.return_value = True # keep ringing
|
|
fake_style_cls = MagicMock(return_value=fake)
|
|
|
|
with patch("wecker.get_style", return_value=fake_style_cls) as get_style:
|
|
wecker.run_alarm(style_name="whatever", test_mode=True)
|
|
|
|
get_style.assert_called_once_with("whatever")
|
|
fake_style_cls.assert_called_once_with(wecker.set_led)
|
|
fake.update.assert_called()
|
|
|
|
|
|
def test_setup_uses_env_music_file(mock_gpio, mock_pygame, monkeypatch):
|
|
monkeypatch.setenv("MUSIC_FILE", "/custom/track.mp3")
|
|
wecker.setup()
|
|
mock_pygame.mixer.music.load.assert_called_with("/custom/track.mp3")
|
|
|
|
|
|
def test_setup_music_file_argument_overrides_env(mock_gpio, mock_pygame, monkeypatch):
|
|
monkeypatch.setenv("MUSIC_FILE", "/env/track.mp3")
|
|
wecker.setup(music_file="/arg/track.mp3")
|
|
mock_pygame.mixer.music.load.assert_called_with("/arg/track.mp3")
|