feat(bridge): BLE 桥接能力——bridge_server.py(Windows原生) + BridgeTransport + --via/PPCLOCK_VIA,149 测试全绿
This commit is contained in:
1 parent
17e2512f6a
commit
49b2010088
7 files changed
+658
-2
No files matched your search
@@ -0,0 +1,151 @@
|
||||
"""BridgeTransport 测试 — 用内存假 bridge 服务端验证 JSONL 协议与同构接口。"""
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from ppclock import bridge
|
||||
|
||||
|
||||
class FakeBridgeServer:
|
||||
"""模拟 tools/bridge_server.py 的 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"
|
||||
assert w[1]["char"] == "rxtx" 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
|
||||
Reference in new issue
Block a user