""" Tests for the Base Adapter and IncomingMessage. """ import pytest from datetime import datetime, timezone from adapters.base import BaseAdapter, IncomingMessage # --------------------------------------------------------------------------- # Concrete adapter for testing (implements all abstract methods) # --------------------------------------------------------------------------- class MockAdapter(BaseAdapter): """A minimal concrete adapter for testing BaseAdapter.""" def __init__(self): super().__init__() self.sent_messages: list[dict] = [] self._started = False @property def source_name(self) -> str: return "mock" def start(self) -> None: self._started = True def start_async(self) -> None: self._started = True def stop(self) -> None: self._started = False def send_message( self, channel_id: str, text: str, thread_id: str | None = None, ) -> None: self.sent_messages.append({ "channel_id": channel_id, "text": text, "thread_id": thread_id, }) # --------------------------------------------------------------------------- # Fixtures # --------------------------------------------------------------------------- @pytest.fixture def adapter(): return MockAdapter() @pytest.fixture def sample_message(): return IncomingMessage( text="Hello world", user_id="U123", user_name="testuser", channel_id="C456", channel_name="general", conversation_id="conv-789", source="mock", is_dm=False, raw_event={"thread_id": "T100"}, ) # --------------------------------------------------------------------------- # Tests: IncomingMessage # --------------------------------------------------------------------------- class TestIncomingMessage: def test_create_message(self, sample_message): assert sample_message.text == "Hello world" assert sample_message.user_id == "U123" assert sample_message.user_name == "testuser" assert sample_message.channel_id == "C456" assert sample_message.source == "mock" assert sample_message.is_dm is False def test_timestamp_default(self): msg = IncomingMessage( text="test", user_id="U1", user_name="user", channel_id="C1", channel_name="ch", conversation_id="conv", source="test", is_dm=True, ) assert msg.timestamp is not None assert msg.timestamp.tzinfo is not None # has timezone def test_raw_event_default(self): msg = IncomingMessage( text="test", user_id="U1", user_name="user", channel_id="C1", channel_name="ch", conversation_id="conv", source="test", is_dm=False, ) assert msg.raw_event == {} # --------------------------------------------------------------------------- # Tests: BaseAdapter # --------------------------------------------------------------------------- class TestBaseAdapter: def test_register_handler(self, adapter): handler = lambda msg: "response" adapter.on_message(handler) assert len(adapter._message_handlers) == 1 def test_on_message_as_decorator(self, adapter): @adapter.on_message def my_handler(msg): return "decorated response" assert len(adapter._message_handlers) == 1 assert my_handler("test") == "decorated response" def test_dispatch_calls_handler(self, adapter, sample_message): responses = [] @adapter.on_message def handler(msg): responses.append(msg.text) return f"reply to: {msg.text}" adapter._dispatch(sample_message) assert responses == ["Hello world"] def test_dispatch_sends_response(self, adapter, sample_message): @adapter.on_message def handler(msg): return "Auto reply" adapter._dispatch(sample_message) assert len(adapter.sent_messages) == 1 assert adapter.sent_messages[0]["text"] == "Auto reply" assert adapter.sent_messages[0]["channel_id"] == "C456" def test_dispatch_no_response(self, adapter, sample_message): @adapter.on_message def handler(msg): return None # explicit no response adapter._dispatch(sample_message) assert len(adapter.sent_messages) == 0 def test_dispatch_handler_error(self, adapter, sample_message): @adapter.on_message def bad_handler(msg): raise ValueError("Something broke") # Should not raise — dispatch catches errors adapter._dispatch(sample_message) # Should send error message assert len(adapter.sent_messages) == 1 assert "Something went wrong" in adapter.sent_messages[0]["text"] def test_multiple_handlers(self, adapter, sample_message): calls = [] @adapter.on_message def handler1(msg): calls.append("h1") return None @adapter.on_message def handler2(msg): calls.append("h2") return "from h2" adapter._dispatch(sample_message) assert calls == ["h1", "h2"] assert len(adapter.sent_messages) == 1 # only h2 returned a response def test_source_name(self, adapter): assert adapter.source_name == "mock" def test_start_stop(self, adapter): adapter.start() assert adapter._started is True adapter.stop() assert adapter._started is False