"""Unit tests for channels.py — register, deliver, pending_replies queue.""" import asyncio import pytest from unittest.mock import AsyncMock, patch import channels @pytest.fixture(autouse=True) def reset_channels_state(): """Clear module-level state before and after every test.""" channels._callbacks.clear() channels.pending_replies.clear() yield channels._callbacks.clear() channels.pending_replies.clear() # ── register ─────────────────────────────────────────────────────────────────── class TestRegister: def test_register_stores_callback(self): cb = AsyncMock() channels.register("test_channel", cb) assert channels._callbacks["test_channel"] is cb def test_register_overwrites_existing(self): cb1 = AsyncMock() cb2 = AsyncMock() channels.register("ch", cb1) channels.register("ch", cb2) assert channels._callbacks["ch"] is cb2 def test_register_multiple_channels(self): cb_a = AsyncMock() cb_b = AsyncMock() channels.register("a", cb_a) channels.register("b", cb_b) assert channels._callbacks["a"] is cb_a assert channels._callbacks["b"] is cb_b # ── deliver ──────────────────────────────────────────────────────────────────── class TestDeliver: async def test_deliver_enqueues_reply(self): channels.register("cli", AsyncMock()) await channels.deliver("cli-alvis", "cli", "hello world") q = channels.pending_replies["cli-alvis"] assert not q.empty() assert await q.get() == "hello world" async def test_deliver_calls_channel_callback(self): cb = AsyncMock() channels.register("telegram", cb) await channels.deliver("tg-123", "telegram", "reply text") cb.assert_awaited_once_with("tg-123", "reply text") async def test_deliver_unknown_channel_still_enqueues(self): """No registered callback for channel → reply still goes to the queue.""" await channels.deliver("cli-bob", "nonexistent", "fallback reply") q = channels.pending_replies["cli-bob"] assert await q.get() == "fallback reply" async def test_deliver_unknown_channel_does_not_raise(self): """Missing callback must not raise an exception.""" await channels.deliver("cli-x", "ghost_channel", "msg") async def test_deliver_creates_queue_if_absent(self): channels.register("cli", AsyncMock()) assert "cli-new" not in channels.pending_replies await channels.deliver("cli-new", "cli", "hi") assert "cli-new" in channels.pending_replies async def test_deliver_reuses_existing_queue(self): """Second deliver to the same session appends to the same queue.""" channels.register("cli", AsyncMock()) await channels.deliver("cli-alvis", "cli", "first") await channels.deliver("cli-alvis", "cli", "second") q = channels.pending_replies["cli-alvis"] assert await q.get() == "first" assert await q.get() == "second" async def test_deliver_telegram_sends_to_callback(self): sent = [] async def fake_tg(session_id, text): sent.append((session_id, text)) channels.register("telegram", fake_tg) await channels.deliver("tg-999", "telegram", "test message") assert sent == [("tg-999", "test message")] # ── register_defaults ────────────────────────────────────────────────────────── class TestRegisterDefaults: def test_registers_telegram_and_cli(self): channels.register_defaults() assert "telegram" in channels._callbacks assert "cli" in channels._callbacks async def test_cli_callback_is_noop(self): """CLI send callback does nothing (replies are handled via SSE queue).""" channels.register_defaults() cb = channels._callbacks["cli"] # Should not raise and should return None result = await cb("cli-alvis", "some reply") assert result is None async def test_telegram_callback_chunks_long_messages(self): """Telegram callback splits messages > 4000 chars into chunks.""" channels.register_defaults() cb = channels._callbacks["telegram"] long_text = "x" * 9000 # > 4000 chars → should produce 3 chunks with patch("channels.httpx.AsyncClient") as mock_client_cls: mock_client = AsyncMock() mock_client.__aenter__ = AsyncMock(return_value=mock_client) mock_client.__aexit__ = AsyncMock(return_value=False) mock_client.post = AsyncMock() mock_client_cls.return_value = mock_client await cb("tg-123", long_text) # 9000 chars / 4000 per chunk = 3 POST calls assert mock_client.post.await_count == 3