Files
qianbian/tests/test_bridge.py
T

153 lines
5.3 KiB
Python

"""BridgeTransport 测试 — 用内存假 bridge 服务端验证 JSONL 协议与同构接口。"""
import asyncio
import json
import pytest
import pytest_asyncio
from ppclock.transports import bridge
class FakeBridgeServer:
"""模拟 ppclock.bridge_server 的 JSONL 协议。"""
def __init__(self):
self.received = []
self.device_id = "89794900980101"
async def start(self):
self.server = await asyncio.start_server(self._handle, "127.0.0.1", 0)
return self.server.sockets[0].getsockname()[1]
async def _handle(self, reader, writer):
async for line in reader:
req = json.loads(line)
self.received.append(req)
op, rid = req["op"], req["id"]
if op == "ping":
data = {"pong": True, "version": 1, "platform": "FakeOS"}
elif op == "scan":
data = {"devices": [{"name": "NRF-FAKE", "address": "AA:BB:CC:DD:EE:FF",
"rssi": -50}]}
elif op == "connect":
data = {"address": req["address"]}
elif op == "write":
data = {"sent": len(req["data"]) // 2}
elif op == "read":
data = {"data": "beef"}
elif op == "request_device_id":
data = {"device_id": self.device_id}
elif op == "ota":
for i in (1, 2):
writer.write((json.dumps({"event": "ota_progress", "block": i,
"total": 2}) + "\n").encode())
await writer.drain()
data = {"done": True}
elif op == "close":
data = {}
elif op == "fail_me":
writer.write((json.dumps({"id": rid, "ok": False,
"error": "RuntimeError: boom"}) + "\n").encode())
await writer.drain()
continue
else:
continue
writer.write((json.dumps({"id": rid, "ok": True, "data": data}) + "\n").encode())
await writer.drain()
@pytest_asyncio.fixture
async def server():
s = FakeBridgeServer()
port = await s.start()
yield s, port
s.server.close()
@pytest.mark.asyncio
async def test_scan_via_rpc(server):
s, port = server
devices = await bridge.scan("127.0.0.1", port)
assert devices[0]["name"] == "NRF-FAKE"
@pytest.mark.asyncio
async def test_connect_autoscan(server):
"""无 address 时自动扫描选首台 NRF-(与 BLETransport 语义一致)"""
s, port = server
async with bridge.BridgeTransport(host="127.0.0.1", port=port) as t:
assert t.address == "AA:BB:CC:DD:EE:FF"
ops = [r["op"] for r in s.received]
assert ops[:2] == ["scan", "connect"]
@pytest.mark.asyncio
async def test_connect_explicit_address(server):
s, port = server
async with bridge.BridgeTransport(address="18:BC:5A:5D:BF:28",
host="127.0.0.1", port=port) as t:
assert t.address == "18:BC:5A:5D:BF:28"
conn = next(r for r in s.received if r["op"] == "connect")
assert conn["address"] == "18:BC:5A:5D:BF:28"
@pytest.mark.asyncio
async def test_write_channels_and_hex(server):
s, port = server
async with bridge.BridgeTransport(address="X", host="127.0.0.1", port=port) as t:
await t.write_epd(bytes([0x03, 0xFF]))
await t.write_rxtx(bytes([0xE2]), response=False)
w = [r for r in s.received if r["op"] == "write"]
assert w[0]["char"] == "epd" and w[0]["data"] == "03ff"
# RXTX 写走 alt(331f) 活通道(实机证据)
assert w[1]["char"] == "alt" and w[1]["data"] == "e2" and w[1]["response"] is False
@pytest.mark.asyncio
async def test_read_rxtx(server):
s, port = server
async with bridge.BridgeTransport(address="X", host="127.0.0.1", port=port) as t:
assert await t.read_rxtx() == bytes([0xBE, 0xEF])
@pytest.mark.asyncio
async def test_request_device_id(server):
s, port = server
async with bridge.BridgeTransport(address="X", host="127.0.0.1", port=port) as t:
assert await t.request_device_id() == "89794900980101"
@pytest.mark.asyncio
async def test_run_ota_streams_progress(server):
s, port = server
async with bridge.BridgeTransport(address="X", host="127.0.0.1", port=port) as t:
events = []
result = await t.run_ota(b"\x70\x51" + b"\x00" * 100, on_progress=events.append)
assert result == {"done": True}
assert [e["block"] for e in events] == [1, 2]
@pytest.mark.asyncio
async def test_error_raises_bridge_error(server):
s, port = server
async with bridge.BridgeTransport(address="X", host="127.0.0.1", port=port) as t:
with pytest.raises(bridge.BridgeError, match="boom"):
await t._rpc("fail_me")
class TestParseVia:
def test_bridge_uri(self):
host, port = bridge.parse_via("bridge://192.168.61.35:8971")
assert (host, port) == ("192.168.61.35", 8971)
def test_default_port(self):
host, port = bridge.parse_via("bridge://192.168.61.35")
assert (host, port) == ("192.168.61.35", 8971)
def test_bad_scheme(self):
with pytest.raises(ValueError):
bridge.parse_via("tcp://x")
def test_local_is_none(self):
assert bridge.parse_via("local") is None