# ABOUTME: Tests for fprintd D-Bus integration. # ABOUTME: Verifies fingerprint listener lifecycle and signal handling with mocked D-Bus. from unittest.mock import patch, MagicMock, call from moonlock.fingerprint import FingerprintListener class TestFingerprintListenerAvailability: """Tests for checking fprintd availability.""" @patch("moonlock.fingerprint.Gio.DBusProxy.new_for_bus_sync") def test_is_available_when_fprintd_running_and_enrolled(self, mock_proxy_cls): manager = MagicMock() mock_proxy_cls.return_value = manager manager.GetDefaultDevice.return_value = ("(o)", "/dev/0") device = MagicMock() mock_proxy_cls.return_value = device device.ListEnrolledFingers.return_value = ("(as)", ["right-index-finger"]) listener = FingerprintListener.__new__(FingerprintListener) listener._manager_proxy = manager listener._device_proxy = device listener._device_path = "/dev/0" assert listener.is_available("testuser") is True def test_is_available_returns_false_when_no_device(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = None listener._device_path = None assert listener.is_available("testuser") is False class TestFingerprintListenerLifecycle: """Tests for start/stop lifecycle.""" def test_start_calls_verify_start(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._device_path = "/dev/0" listener._signal_id = None listener._running = False on_success = MagicMock() on_failure = MagicMock() listener.start("testuser", on_success=on_success, on_failure=on_failure) listener._device_proxy.Claim.assert_called_once_with("(s)", "testuser") listener._device_proxy.VerifyStart.assert_called_once_with("(s)", "any") def test_stop_calls_verify_stop_and_release(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._running = True listener._signal_id = 42 listener.stop() listener._device_proxy.VerifyStop.assert_called_once() listener._device_proxy.Release.assert_called_once() assert listener._running is False def test_stop_is_noop_when_not_running(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._running = False listener.stop() listener._device_proxy.VerifyStop.assert_not_called() class TestFingerprintSignalHandling: """Tests for VerifyStatus signal processing.""" def test_verify_match_calls_on_success(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._running = True on_success = MagicMock() on_failure = MagicMock() listener._on_success = on_success listener._on_failure = on_failure listener._on_verify_status("verify-match", False) on_success.assert_called_once() def test_verify_no_match_calls_on_failure_and_retries(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._running = True on_success = MagicMock() on_failure = MagicMock() listener._on_success = on_success listener._on_failure = on_failure listener._on_verify_status("verify-no-match", False) on_failure.assert_called_once() # Should restart verification for retry listener._device_proxy.VerifyStart.assert_called_once_with("(s)", "any") def test_verify_swipe_too_short_retries_without_failure(self): listener = FingerprintListener.__new__(FingerprintListener) listener._device_proxy = MagicMock() listener._running = True on_success = MagicMock() on_failure = MagicMock() listener._on_success = on_success listener._on_failure = on_failure listener._on_verify_status("verify-swipe-too-short", True) on_success.assert_not_called() on_failure.assert_not_called() listener._device_proxy.VerifyStart.assert_called_once_with("(s)", "any")