""" A revocation the hub signed closes what it revokes, not only what comes next. The denylist refuses the next connection. A revoked account's live sessions were left open — streaming, downloading, chatting — until they happened to end; only a group's revocation closed its sessions. """ import asyncio import pytest from meshbay_node.daemon import NodeDaemon from meshbay_node.transport.quic_server import Denylist class _Session: def __init__(self, user_id, group_id): self._user_id = user_id self._group_id = group_id self.closed = False async def close(self): self.closed = True def _daemon(sessions): d = NodeDaemon.__new__(NodeDaemon) d._webrtc = type("T", (), {"_sessions": sessions})() return d @pytest.mark.asyncio async def test_a_revoked_account_is_disconnected(tmp_path): mallory, alice = _Session("mallory", "g1"), _Session("alice", "g1") phone = _Session("mallory", "g2") d = _daemon({"a": mallory, "b": alice, "c": phone}) deny = Denylist(path=tmp_path / "deny.json") d._apply_revocation(deny, "user", "mallory") await asyncio.sleep(0.05) assert mallory.closed and phone.closed, "every session of the account ends" assert not alice.closed assert deny.is_denied("mallory", "") @pytest.mark.asyncio async def test_a_revoked_group_is_still_disconnected(tmp_path): a, b = _Session("alice", "g1"), _Session("alice", "g2") d = _daemon({"a": a, "b": b}) d._apply_revocation(Denylist(path=tmp_path / "deny.json"), "group", "g1") await asyncio.sleep(0.05) assert a.closed and not b.closed