From 871322bf68f6242f8cf30b171e9c6a7a93c7826d Mon Sep 17 00:00:00 2001 From: "Peter F. Patel-Schneider" Date: Sat, 16 Mar 2024 09:00:15 -0400 Subject: [PATCH] tests: improve coverage of receiver.py --- tests/logitech_receiver/test_receiver.py | 175 +++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/tests/logitech_receiver/test_receiver.py b/tests/logitech_receiver/test_receiver.py index 513ea2fa..193c4842 100644 --- a/tests/logitech_receiver/test_receiver.py +++ b/tests/logitech_receiver/test_receiver.py @@ -1,7 +1,12 @@ +from dataclasses import dataclass +from functools import partial +from struct import pack +from typing import Optional from unittest import mock import pytest +from logitech_receiver import common from logitech_receiver import exceptions from logitech_receiver import receiver @@ -25,3 +30,173 @@ def test_get_kind_from_index(index, expected_kind): else: with pytest.raises(exceptions.NoSuchDevice): receiver._get_kind_from_index(mock_receiver, index) + + +@dataclass +class DeviceInfo: + path: str + vendor_id: int = 1133 + product_id: int = 0xC52B + + +@dataclass +class Response: + response: Optional[str] + handle: int + devnumber: int + id: int + params: str = "" + no_reply: bool = False + + +def request(responses, handle, devnumber, id, *params, no_reply=False, return_error=False, long_message=False, protocol=1.0): + params = b"".join(pack("B", p) if isinstance(p, int) else p for p in params) + print("REQUEST ", hex(handle), hex(devnumber), hex(id), params.hex()) + for r in responses: + if handle == r.handle and devnumber == r.devnumber and r.id == id and bytes.fromhex(r.params) == params: + print("RESPONSE", hex(r.handle), hex(r.devnumber), hex(r.id), r.params, r.response) + return bytes.fromhex(r.response) if r.response is not None else None + + +def open_path(path: Optional[str]) -> Optional[int]: + return int(path, 16) if path is not None else None + + +@pytest.fixture +def mock_request(): + with mock.patch("logitech_receiver.base.request", return_value=None) as mock_request: + yield mock_request + + +@pytest.fixture +def mock_base(): + with mock.patch("logitech_receiver.base.open_path", return_value=None) as mock_open_path: + with mock.patch("logitech_receiver.base.request", return_value=None) as mock_request: + yield mock_open_path, mock_request + + +responses_unifying = [ + Response("000000", 0x11, 0xFF, 0x8003, "FF"), + Response("000300", 0x11, 0xFF, 0x8102), + Response("0316CC9CB40506220000000000000000", 0x11, 0xFF, 0x83B5, "03"), + Response("20200840820402020700000000000000", 0x11, 0xFF, 0x83B5, "20"), + Response("21211420110400010D1A000000000000", 0x11, 0xFF, 0x83B5, "21"), + Response("22220840660402010700000000020000", 0x11, 0xFF, 0x83B5, "22"), + Response("30198E3EB80600000001000000000000", 0x11, 0xFF, 0x83B5, "30"), + Response("31811119511A40000002000000000000", 0x11, 0xFF, 0x83B5, "31"), + Response("32112C46EA1E40000003000000000000", 0x11, 0xFF, 0x83B5, "32"), + Response("400B4D58204D61737465722033000000", 0x11, 0xFF, 0x83B5, "40"), + Response("41044B35323020202020202020202020", 0x11, 0xFF, 0x83B5, "41"), + Response("42054372616674000000000000000000", 0x11, 0xFF, 0x83B5, "42"), + Response("012411", 0x11, 0xFF, 0x81F1, "01"), + Response("020036", 0x11, 0xFF, 0x81F1, "02"), + Response("03AAAC", 0x11, 0xFF, 0x81F1, "03"), + Response("040209", 0x11, 0xFF, 0x81F1, "04"), +] +responses_c534 = [ + Response("000000", 0x12, 0xFF, 0x8003, "FF"), + Response("000209", 0x12, 0xFF, 0x8102), + Response("0316CC9CB40502220000000000000000", 0x12, 0xFF, 0x83B5, "03"), + Response("00000445AB", 0x12, 0xFF, 0x83B5, "04"), +] +responses_unusual = [ + Response("000000", 0x13, 0xFF, 0x8003, "FF"), + Response("000300", 0x13, 0xFF, 0x8102), + Response("00000445AB", 0x13, 0xFF, 0x83B5, "04"), + Response("0326CC9CB40508220000000000000000", 0x13, 0xFF, 0x83B5, "03"), +] +responses_lacking = [ + Response("000000", 0x14, 0xFF, 0x8003, "FF"), + Response("000300", 0x14, 0xFF, 0x8102), +] + +mouse_info = { + "kind": common.NamedInt(2, "mouse"), + "polling": "8ms", + "power_switch": common.NamedInt(1, "base"), + "serial": "198E3EB8", + "wpid": "4082", +} +c534_info = {"kind": common.NamedInt(0, "unknown"), "polling": "", "power_switch": "(unknown)", "serial": None, "wpid": "45AB"} + + +@pytest.mark.parametrize( + "device_info, responses, handle, serial, max_devices, ", + [ + (DeviceInfo(None), [], None, None, None), + (DeviceInfo(None), [], None, None, None), + (DeviceInfo("11"), responses_unifying, 0x11, "16CC9CB4", 6), + (DeviceInfo("12", product_id=0xC534), responses_c534, 0x12, "16CC9CB4", 2), + (DeviceInfo("12", product_id=0xC539), responses_c534, 0x12, "16CC9CB4", 2), + (DeviceInfo("13"), responses_unusual, 0x13, "26CC9CB4", 1), + (DeviceInfo("14"), responses_lacking, 0x14, None, 1), + ], +) +def test_ReceiverFactory_create_receiver(device_info, responses, handle, serial, max_devices, mock_base): + mock_base[0].side_effect = open_path + mock_base[1].side_effect = partial(request, responses) + + r = receiver.ReceiverFactory.create_receiver(device_info, lambda x: x) + + if handle is None: + assert r is None + else: + assert r.handle == handle + assert r.serial == serial + assert r.max_devices == max_devices + + +@pytest.mark.parametrize( + "device_info, responses, firmware, codename, remaining_pairings, pairing_info, count", + [ + (DeviceInfo("11"), responses_unifying, 3, "K520", -1, mouse_info, 3), + (DeviceInfo("12", product_id=0xC534), responses_c534, None, None, 4, c534_info, 2), + (DeviceInfo("13", product_id=0xCCCC), responses_unusual, None, None, -1, c534_info, 3), + ], +) +def test_ReceiverFactory_props(device_info, responses, firmware, codename, remaining_pairings, pairing_info, count, mock_base): + mock_base[0].side_effect = open_path + mock_base[1].side_effect = partial(request, responses) + + r = receiver.ReceiverFactory.create_receiver(device_info, lambda x: x) + + assert len(r.firmware) == firmware if firmware is not None else firmware is None + assert r.device_codename(2) == codename + assert r.remaining_pairings() == remaining_pairings + assert r.device_pairing_information(1) == pairing_info + assert r.count() == count + + +@pytest.mark.parametrize( + "device_info, responses, status_str, strng", + [ + (DeviceInfo("11"), responses_unifying, "No paired devices.", ""), + (DeviceInfo("12", product_id=0xC534), responses_c534, "No paired devices.", ""), + (DeviceInfo("13", product_id=0xCCCC), responses_unusual, "No paired devices.", ""), + ], +) +def test_ReceiverFactory_string(device_info, responses, status_str, strng, mock_base): + mock_base[0].side_effect = open_path + mock_base[1].side_effect = partial(request, responses) + + r = receiver.ReceiverFactory.create_receiver(device_info, lambda x: x) + + assert r.status_string() == status_str + assert str(r) == strng + + +@pytest.mark.parametrize( + "device_info, responses", + [ + (DeviceInfo("14"), responses_lacking), + (DeviceInfo("14", product_id="C534"), responses_lacking), + ], +) +def test_ReceiverFactory_nodevice(device_info, responses, mock_base): + mock_base[0].side_effect = open_path + mock_base[1].side_effect = partial(request, responses) + + r = receiver.ReceiverFactory.create_receiver(device_info, lambda x: x) + + with pytest.raises(exceptions.NoSuchDevice): + r.device_pairing_information(1)