switch-pico/tests/test_pairing_manager.py

156 lines
4.5 KiB
Python

from __future__ import annotations
import struct
import pytest
import switch_pico_bridge.pairing_manager as pairing_manager
def make_payload(
generation: int,
records: list[tuple[int, int, bytes]],
*,
status: int = pairing_manager.STATUS_READY,
overflow: bool = False,
) -> bytes:
payload = bytearray(b"SPPM")
payload.extend(
[
pairing_manager.PROTOCOL_VERSION,
status,
len(records),
int(overflow),
]
)
payload.extend(struct.pack("<I", generation))
for transport, address_type, address in records:
payload.extend([transport, address_type])
payload.extend(address)
return bytes(payload)
class FakeDevice:
bus = 1
address = 7
def __init__(self) -> None:
self.generation = 3
self.records = [
(
pairing_manager.TRANSPORT_CLASSIC,
0xFE,
bytes.fromhex("010203040506"),
),
(
pairing_manager.TRANSPORT_BLE,
2,
bytes.fromhex("A1A2A3A4A5A6"),
),
]
self.requests: list[int] = []
def ctrl_transfer(
self,
bm_request_type: int,
request: int,
value: int,
index: int,
data_or_w_length: object,
timeout: int,
) -> bytes | int:
assert value == pairing_manager.REQUEST_VALUE
assert index == pairing_manager.REQUEST_INDEX
assert timeout == pairing_manager.USB_TIMEOUT_MS
self.requests.append(request)
if bm_request_type == 0xC0:
assert request == pairing_manager.REQUEST_GET
return make_payload(self.generation, self.records)
assert bm_request_type == 0x40
if request == pairing_manager.REQUEST_REFRESH:
self.generation += 1
elif request == pairing_manager.REQUEST_CLEAR:
self.records = []
self.generation += 1
else:
raise AssertionError(f"unexpected request {request}")
return 0
def test_parse_snapshot() -> None:
snapshot = pairing_manager.parse_snapshot(
make_payload(
0x78563412,
[
(
pairing_manager.TRANSPORT_CLASSIC,
0xFE,
bytes.fromhex("010203040506"),
),
(
pairing_manager.TRANSPORT_BLE,
3,
bytes.fromhex("A1A2A3A4A5A6"),
),
],
overflow=True,
)
)
assert snapshot.generation == 0x78563412
assert snapshot.overflow
assert snapshot.records[0].transport_text == "Classic"
assert snapshot.records[0].address_text == "01:02:03:04:05:06"
assert snapshot.records[1].transport_text == "BLE (random identity)"
@pytest.mark.parametrize(
"payload",
[
b"",
b"NOPE" + bytes(8),
b"SPPM\x02" + bytes(7),
b"SPPM\x01\x00\x11\x00" + bytes(4),
],
)
def test_parse_rejects_invalid_payload(payload: bytes) -> None:
with pytest.raises(pairing_manager.PairingManagerError):
pairing_manager.parse_snapshot(payload)
def test_list_and_clear_commands(
monkeypatch: pytest.MonkeyPatch,
capsys: pytest.CaptureFixture[str],
) -> None:
device = FakeDevice()
monkeypatch.setattr(pairing_manager, "_candidate_devices", lambda: [device])
assert pairing_manager.main(["list"]) == 0
output = capsys.readouterr().out
assert "Classic 01:02:03:04:05:06" in output
assert "BLE (public identity) A1:A2:A3:A4:A5:A6" in output
assert pairing_manager.main(["clear"]) == 2
assert "requires --yes" in capsys.readouterr().err
assert pairing_manager.main(["clear", "--yes"]) == 0
assert capsys.readouterr().out == "Cleared 2 stored pairing(s).\n"
assert device.records == []
assert pairing_manager.REQUEST_REFRESH in device.requests
assert pairing_manager.REQUEST_CLEAR in device.requests
def test_find_requires_selector_for_multiple_picos(
monkeypatch: pytest.MonkeyPatch,
) -> None:
first = FakeDevice()
second = FakeDevice()
second.address = 8
monkeypatch.setattr(
pairing_manager, "_candidate_devices", lambda: [first, second]
)
with pytest.raises(
pairing_manager.PairingManagerError,
match="multiple switch-pico devices",
):
pairing_manager.find_pico(None, None)
assert pairing_manager.find_pico(1, 8) is second