VerifyStart was called unconditionally on retry/no-match statuses, ignoring the done parameter from fprintd's VerifyStatus signal. When done=False the verify session is still active, causing AlreadyInUse errors. Now only restarts verification when done=True.
153 lines
5.5 KiB
Python
153 lines
5.5 KiB
Python
# 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_when_done(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", True)
|
|
|
|
on_failure.assert_called_once()
|
|
# Should restart verification when done=True
|
|
listener._device_proxy.VerifyStart.assert_called_once_with("(s)", "any")
|
|
|
|
def test_verify_no_match_calls_on_failure_without_restart_when_not_done(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 NOT restart verification when done=False (still in progress)
|
|
listener._device_proxy.VerifyStart.assert_not_called()
|
|
|
|
def test_verify_swipe_too_short_retries_when_done(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")
|
|
|
|
def test_retry_status_does_not_restart_when_not_done(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", False)
|
|
|
|
on_success.assert_not_called()
|
|
on_failure.assert_not_called()
|
|
# Should NOT restart — verification still in progress
|
|
listener._device_proxy.VerifyStart.assert_not_called()
|