import asyncio import json from uuid import uuid4 import pytest from k1link.viewer.node_local_media import acknowledge, admit_local, read_local, start_local from k1link.viewer.node_media import NodeMediaPeers def test_local_stream_fragments_ack_resume_and_releases_before_reconnect(): payload = b"RRF2" + bytes(range(256)) * 256 released = [] subscriptions = [] class Subscription: pending = None def __init__(self, after): self.after = after self.sent = False def next_batch(self, **_): if self.sent: return b"" self.sent = True self.pending = (self.after + 1, payload, None) return self.pending def snapshot(self, _): return dict(sequence=self.after + 1, age_ms=0, points=5000) def acknowledge(self, seq): if seq == self.after + 1: self.pending = None def release(self): released.append(self.after) class Hub: def subscribe(self, view_id, after): sub = Subscription(after) subscriptions.append((view_id, sub)) return sub class Camera: def snapshot(self): return {"recording": {"source_end_expected": True}} async def run(): peers = NodeMediaPeers(Hub(), Camera()) view = str(uuid4()) for after in (0, 1): peer = await start_local(peers, dict(view_id=view, after=after)) if after: assert released == [0] # Lost response/reconnect retires the old lease. collected = [] while sum(map(len, collected)) < len(payload): response = await asyncio.wait_for(read_local(peers, peer, after), 1.2) assert len(response) <= 196608 offset = 0 while offset < len(response): kind = response[offset] size = int.from_bytes(response[offset + 1 : offset + 5], "big") data = response[offset + 5 : offset + 5 + size] if kind == 0: assert json.loads(data)["peer_id"] == peer if kind == 2: if data.startswith(b"MCF1"): assert int.from_bytes(data[4:], "big") == len(payload) else: collected.append(data) offset += size + 5 assert b"".join(collected) == payload acknowledge(peers, peer, after + 1) assert subscriptions[-1][1].pending is None await peers.close_all() assert not peers.items and released == [0, 1] assert subscriptions[0][0] == subscriptions[1][0] == view asyncio.run(run()) @pytest.mark.parametrize("after", [True, -1, 2**53, "1"]) def test_local_cursor_rejects_noncanonical_values(after): with pytest.raises(ValueError): admit_local(dict(view_id=str(uuid4()), after=after)) def test_local_carrier_does_not_exceed_peer_capacity(): async def run(): class Peers: items = {"a": {}, "b": {}} with pytest.raises(ValueError): await start_local(Peers(), dict(view_id=str(uuid4()), after=0)) asyncio.run(run())