201 lines
5.6 KiB
Python
201 lines
5.6 KiB
Python
"""
|
|
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
|