feat(k1): stabilize LAB bridge and isolate onboard device integration
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
import asyncio
|
||||
import queue
|
||||
|
||||
import pytest
|
||||
from aiortc import RTCConfiguration, RTCPeerConnection, RTCSessionDescription
|
||||
|
||||
from k1link.viewer.node_media import NodeMediaPeers, admit_sdp
|
||||
|
||||
SDP_HEADER = "v=0\r\nm=application 9 UDP/DTLS/SCTP webrtc-datachannel\r\n"
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"address,kind", [("8.8.8.8", "host"), ("192.168.1.2", "relay"), ("::1", "host")]
|
||||
)
|
||||
def test_media_rejects_non_private_or_relay_candidates(address, kind):
|
||||
with pytest.raises(ValueError):
|
||||
admit_sdp(SDP_HEADER + f"a=candidate:1 1 udp 1 {address} 12345 typ {kind}\r\n")
|
||||
|
||||
|
||||
def test_media_accepts_paired_lan_and_tailnet_candidates():
|
||||
for address in ("192.168.1.2", "100.80.6.113", "peer.local"):
|
||||
admit_sdp(SDP_HEADER + f"a=candidate:1 1 udp 1 {address} 12345 typ host\r\n")
|
||||
|
||||
|
||||
def test_failed_media_offer_retires_peer_without_camera_channel(monkeypatch):
|
||||
async def reject(*_):
|
||||
raise ValueError("Invalid remote description")
|
||||
|
||||
monkeypatch.setattr(RTCPeerConnection, "setRemoteDescription", reject)
|
||||
|
||||
async def run():
|
||||
class Camera:
|
||||
def snapshot(self):
|
||||
return {}
|
||||
|
||||
peers = NodeMediaPeers(None, Camera())
|
||||
try:
|
||||
with pytest.raises(ValueError):
|
||||
await peers.offer({"sdp": SDP_HEADER})
|
||||
assert peers.items == {}
|
||||
finally:
|
||||
await peers.close_all()
|
||||
|
||||
asyncio.run(run())
|
||||
|
||||
|
||||
def test_native_webrtc_roundtrip_and_missing_camera_preserve_rrd(monkeypatch):
|
||||
"""One bounded loopback peer, no STUN/TURN, device or external network."""
|
||||
import aioice.ice
|
||||
|
||||
class Subscription:
|
||||
def __init__(self):
|
||||
self.output = queue.Queue()
|
||||
self.output.put(b"RRF2-transport-fixture")
|
||||
|
||||
def read(self):
|
||||
try:
|
||||
return self.output.get(timeout=0.1)
|
||||
except queue.Empty:
|
||||
return b""
|
||||
|
||||
def close(self):
|
||||
pass
|
||||
|
||||
class Hub:
|
||||
def subscribe(self):
|
||||
return Subscription()
|
||||
|
||||
class Camera:
|
||||
def snapshot(self):
|
||||
return {"generation": None}
|
||||
|
||||
async def run():
|
||||
peers = NodeMediaPeers(Hub(), Camera())
|
||||
monkeypatch.setattr(aioice.ice, "get_host_addresses", lambda **_: ["127.0.0.1"])
|
||||
client = RTCPeerConnection(RTCConfiguration(iceServers=[]))
|
||||
channel = client.createDataChannel("rrd", ordered=True)
|
||||
client.createDataChannel("camera", ordered=True)
|
||||
received = asyncio.Event()
|
||||
payloads = []
|
||||
|
||||
@channel.on("message")
|
||||
def message(data):
|
||||
payloads.append(data)
|
||||
received.set()
|
||||
|
||||
try:
|
||||
await client.setLocalDescription(await client.createOffer())
|
||||
answer = await peers.offer({"sdp": client.localDescription.sdp})
|
||||
await client.setRemoteDescription(
|
||||
RTCSessionDescription(sdp=answer["sdp"], type="answer")
|
||||
)
|
||||
await asyncio.wait_for(received.wait(), timeout=8)
|
||||
assert payloads == [b"RRF2-transport-fixture"]
|
||||
assert answer["peer_id"] in peers.items
|
||||
assert channel.readyState == "open"
|
||||
finally:
|
||||
await client.close()
|
||||
await peers.close_all()
|
||||
assert not peers.items
|
||||
|
||||
asyncio.run(run())
|
||||
Reference in New Issue
Block a user