Add MX-T40 BLE desktop controller and protocol documentation
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
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
This commit is contained in:
38 files changed
+2344
No files matched your search
@@ -0,0 +1,88 @@
|
||||
"""Verify direct connections, write completion, and disconnect handling."""
|
||||
|
||||
import socket
|
||||
import types
|
||||
import unittest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from controller.transport import RfcommStream, open_stream
|
||||
|
||||
|
||||
class TransportTests(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.mock_socket = MagicMock()
|
||||
# Replace this adapter's socket module reference rather than globally
|
||||
# mocking socket.socket, which also breaks asyncio's internal sockets
|
||||
# (particularly Windows' Proactor event loop).
|
||||
isolated_socket = types.SimpleNamespace(
|
||||
socket=self.mock_socket,
|
||||
AF_BLUETOOTH=32,
|
||||
BTPROTO_RFCOMM=3,
|
||||
SOCK_STREAM=socket.SOCK_STREAM,
|
||||
timeout=TimeoutError,
|
||||
)
|
||||
replacement = patch("controller.transport.socket", isolated_socket)
|
||||
replacement.start()
|
||||
self.addCleanup(replacement.stop)
|
||||
self.peer = self.mock_socket.return_value
|
||||
|
||||
def test_direct_address_bypasses_serial_and_closes(self):
|
||||
with patch.dict("sys.modules", {"serial": None}):
|
||||
with open_stream("12:34:56:78:9A:BC", 1) as stream:
|
||||
self.assertEqual(stream.write(b"packet"), 6)
|
||||
self.peer.connect.assert_called_once_with(("12:34:56:78:9A:BC", 1))
|
||||
self.peer.sendall.assert_called_once_with(b"packet")
|
||||
self.peer.close.assert_called_once()
|
||||
|
||||
def test_idle_is_not_disconnect(self):
|
||||
with RfcommStream("12:34:56:78:9A:BC") as stream:
|
||||
self.peer.recv.side_effect = TimeoutError()
|
||||
self.assertEqual(stream.read(1024), b"")
|
||||
self.peer.recv.side_effect = None
|
||||
self.peer.recv.return_value = b""
|
||||
with self.assertRaises(ConnectionError):
|
||||
stream.read(1024)
|
||||
|
||||
def test_connect_failure_closes_socket(self):
|
||||
self.peer.connect.side_effect = OSError("connection refused")
|
||||
with self.assertRaises(OSError):
|
||||
with RfcommStream("12:34:56:78:9A:BC"):
|
||||
self.fail("Failed connection entered context")
|
||||
self.peer.close.assert_called_once()
|
||||
|
||||
def test_connect_timeout_identifies_phase_and_closes(self):
|
||||
self.peer.connect.side_effect = TimeoutError("timed out")
|
||||
with self.assertRaisesRegex(TimeoutError, "before the Samsung handshake"):
|
||||
with RfcommStream("12:34:56:78:9A:BC"):
|
||||
self.fail("Timed out connection entered context")
|
||||
self.peer.close.assert_called_once()
|
||||
|
||||
def test_write_timeout_identifies_phase(self):
|
||||
with RfcommStream("12:34:56:78:9A:BC") as stream:
|
||||
self.peer.sendall.side_effect = TimeoutError("timed out")
|
||||
with self.assertRaisesRegex(TimeoutError, "sending a Samsung packet"):
|
||||
stream.write(b"packet")
|
||||
|
||||
def test_prepared_connection_retains_windows_handle(self):
|
||||
device = MagicMock()
|
||||
prepare = AsyncMock(return_value=device)
|
||||
report = MagicMock()
|
||||
with patch("controller.windows_bluetooth.prepare_device", prepare):
|
||||
with open_stream("12:34:56:78:9A:BC", 1, prepare=True, report=report):
|
||||
device.close.assert_not_called()
|
||||
prepare.assert_awaited_once_with("12:34:56:78:9A:BC", report)
|
||||
self.peer.connect.assert_called_once_with(("12:34:56:78:9A:BC", 1))
|
||||
device.close.assert_called_once()
|
||||
|
||||
def test_prepared_connect_failure_releases_both_handles(self):
|
||||
device = MagicMock()
|
||||
# The base adapter's socket cleanup is covered separately. Mock its
|
||||
# entry here so asyncio's own internal sockets do not affect counts.
|
||||
with (
|
||||
patch("controller.windows_bluetooth.prepare_device", AsyncMock(return_value=device)),
|
||||
patch.object(RfcommStream, "__enter__", side_effect=OSError("host down")),
|
||||
):
|
||||
with self.assertRaises(OSError):
|
||||
with open_stream("12:34:56:78:9A:BC", 1, prepare=True):
|
||||
self.fail("Failed connection entered context")
|
||||
device.close.assert_called_once()
|
||||
Reference in new issue
Block a user