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.sleep") def test_blink_led(mock_sleep, mock_gpio): wecker.blink_led(2) # 2 blinks = 4 sleep calls, 2 set_led(True), 2 set_led(False) assert mock_sleep.call_count == 4 # GPIO output called 4 times total (on, off, on, off) assert mock_gpio.output.call_count == 4 @patch("wecker.time.time") def test_run_alarm_start_to_wait(mock_time, mock_gpio, mock_pygame): # Mock pygame.mixer.get_init() to return True so music plays mock_pygame.mixer.get_init.return_value = True # Just run it in test mode, it should execute the loop once and exit wecker.run_alarm(test_mode=True) assert mock_pygame.mixer.music.play.called def test_state_machine_evaluation(mock_gpio, mock_pygame): clock = wecker.AlarmClock() # Transition to ringing -> wait before blink clock.update(100.0, True) # button press assert clock.state == wecker.STATE_WAIT_BEFORE_BLINK # Wait 3 seconds -> blinking with patch("wecker.blink_led"): clock.update(103.1, False) assert clock.state == wecker.STATE_BLINKING with patch("wecker.blink_led"): clock.update(103.2, False) assert clock.state == wecker.STATE_WAIT_FOR_INPUT assert clock.target_blinks >= 1 # Set the user presses to be correct clock.target_blinks = 3 # Press 1 clock.update(104.0, True) clock.update(104.1, False) # Press 2 clock.update(104.5, True) clock.update(104.6, False) # Press 3 clock.update(105.0, True) clock.update(105.1, False) assert clock.user_presses == 3 # Wait 3 seconds to evaluate clock.update(108.2, False) # Triggers state change keep_running = clock.update(108.3, False) # Triggers evaluation # It should evaluate, see it's correct, and return False (stop running) assert clock.state == wecker.STATE_EVALUATING assert not keep_running def test_state_machine_incorrect(mock_gpio, mock_pygame): clock = wecker.AlarmClock() clock.state = wecker.STATE_WAIT_FOR_INPUT clock.target_blinks = 3 # Only press once clock.update(100.0, True) clock.update(100.1, False) # Wait to evaluate clock.last_interaction_time = 100.1 clock.update(103.2, False) # triggers eval state keep_running = clock.update(103.3, False) # evals to incorrect # Evaluated incorrectly, should wait before retry assert keep_running assert clock.state == wecker.STATE_WAIT_BEFORE_RETRY @patch("wecker.time.time") def test_blinking_updates_time_correctly(mock_time, mock_gpio, mock_pygame): clock = wecker.AlarmClock() clock.state = wecker.STATE_BLINKING clock.target_blinks = 4 mock_time.return_value = 200.0 with patch("wecker.blink_led"): clock.update(100.0, False) assert clock.state == wecker.STATE_WAIT_FOR_INPUT assert clock.last_interaction_time == 200.0