Checks / tests (ubuntu-latest, 3.11) (push) Canceled after 0s
Checks / tests (ubuntu-latest, 3.14) (push) Canceled after 0s
Checks / tests (windows-latest, 3.11) (push) Canceled after 0s
Checks / tests (windows-latest, 3.14) (push) Canceled after 0s
Checks / package (push) Canceled after 0s
96 lines
3.7 KiB
Python
96 lines
3.7 KiB
Python
"""Replay the captured reply through asynchronous FFA1/FFA2 operations."""
|
|
|
|
import queue
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from unittest.mock import AsyncMock, patch
|
|
|
|
from controller.ble_link import NOTIFY, SERVICE, WRITE, BLELink
|
|
|
|
|
|
class BLELinkTests(unittest.TestCase):
|
|
def run_peer(self, write_error=False):
|
|
operations = []
|
|
closed = []
|
|
write = types.SimpleNamespace(uuid=WRITE, properties=["write"])
|
|
notify = types.SimpleNamespace(uuid=NOTIFY, properties=["notify"])
|
|
service = types.SimpleNamespace(
|
|
get_characteristic=lambda uuid: {WRITE: write, NOTIFY: notify}.get(uuid)
|
|
)
|
|
|
|
class Events(queue.Queue):
|
|
def put(self, event, *args, **kwargs):
|
|
super().put(event, *args, **kwargs)
|
|
if event[0] == "ready":
|
|
link.stop()
|
|
|
|
events = Events()
|
|
link = BLELink("12:34:56:78:9A:BC", events)
|
|
|
|
class Peer:
|
|
services = types.SimpleNamespace(
|
|
get_service=lambda uuid: service if uuid == SERVICE else None
|
|
)
|
|
is_connected = True
|
|
|
|
async def __aenter__(self):
|
|
return self
|
|
|
|
async def __aexit__(self, *args):
|
|
closed.append(True)
|
|
|
|
async def start_notify(self, characteristic, callback):
|
|
operations.append(("subscribe", characteristic.uuid))
|
|
self.callback = callback
|
|
|
|
async def write_gatt_char(self, characteristic, data, response):
|
|
operations.append(("write", characteristic.uuid, data, response))
|
|
if write_error:
|
|
raise OSError("GATT write failed")
|
|
if data[6:-1] == b"\x01":
|
|
self.callback(notify, bytes.fromhex("00 bb 00 01 00 00 01"))
|
|
reply = bytes.fromhex("00 bb 01 1d 00 0b 02 10 01 34 30 04 01 02 04 05 01 b0")
|
|
self.callback(notify, reply[:8])
|
|
self.callback(notify, reply[8:])
|
|
elif data[6:-1] == b"\x04":
|
|
self.callback(notify, bytes.fromhex("00 bb 00 02 00 00 02"))
|
|
|
|
scanner = types.SimpleNamespace(find_device_by_address=AsyncMock(return_value=object()))
|
|
with patch.dict(
|
|
sys.modules,
|
|
{
|
|
"bleak": types.SimpleNamespace(
|
|
BleakScanner=scanner, BleakClient=lambda *a, **k: Peer()
|
|
)
|
|
},
|
|
):
|
|
link.run()
|
|
return operations, list(events.queue), closed
|
|
|
|
def test_subscribe_before_handshake_and_reassemble_notifications(self):
|
|
operations, events, closed = self.run_peer()
|
|
self.assertEqual(operations[0], ("subscribe", NOTIFY))
|
|
writes = operations[1:]
|
|
self.assertEqual([op[1] for op in writes], [WRITE, WRITE])
|
|
self.assertEqual(
|
|
[op[2] for op in writes],
|
|
[
|
|
bytes.fromhex("00 bb 01 01 00 01 01 04"),
|
|
bytes.fromhex("00 bb 01 02 00 01 04 08"),
|
|
],
|
|
)
|
|
self.assertTrue(all(op[3] is True for op in writes))
|
|
self.assertEqual(sum(kind == "ready" for kind, _ in events), 1)
|
|
self.assertFalse(any(kind == "error" for kind, _ in events))
|
|
self.assertEqual(closed, [True])
|
|
|
|
def test_write_error_disconnects_without_enabling_controls(self):
|
|
_, events, closed = self.run_peer(write_error=True)
|
|
self.assertFalse(any(kind == "ready" for kind, _ in events))
|
|
self.assertTrue(
|
|
any(kind == "error" and "GATT write failed" in value for kind, value in events)
|
|
)
|
|
self.assertEqual(events[-1], ("closed", None))
|
|
self.assertEqual(closed, [True])
|