from __future__ import annotations from datetime import datetime import pytest from pydantic import ValidationError from yuxi.channels.models import ( AgentRequest, AgentResult, Attachment, ChannelAccountSnapshot, ChannelIdentity, ChannelMessage, ChannelResponse, ChannelStatus, ChannelType, ChatType, DeliveryResult, EventType, HealthStatus, MentionsInfo, MessageType, build_snapshot_from_adapter, ) class TestMessageType: def test_text_value(self): assert MessageType.TEXT == "text" def test_all_values_are_strings(self): for member in MessageType: assert isinstance(member.value, str) class TestChannelType: def test_webchat(self): assert ChannelType.WEBCHAT == "webchat" def test_has_wechat(self): assert ChannelType.WECHAT == "wechat" class TestChatType: def test_direct(self): assert ChatType.DIRECT == "direct" def test_group(self): assert ChatType.GROUP == "group" class TestEventType: def test_message_received(self): assert EventType.MESSAGE_RECEIVED == "message.received" class TestChannelIdentity: def test_minimal_fields(self): identity = ChannelIdentity( channel_id="test_chan", channel_type=ChannelType.WEBCHAT, channel_user_id="user_123", channel_chat_id="chat_456", ) assert identity.channel_id == "test_chan" assert identity.channel_type == ChannelType.WEBCHAT assert identity.channel_message_id is None def test_missing_required_field_raises(self): with pytest.raises(ValidationError): ChannelIdentity(channel_id="test_chan", channel_type=ChannelType.WEBCHAT, channel_user_id="u1") def test_all_fields(self): identity = ChannelIdentity( channel_id="telegram", channel_type=ChannelType.TELEGRAM, channel_user_id="@user", channel_chat_id="12345", channel_message_id="msg_001", ) assert identity.channel_message_id == "msg_001" class TestChannelMessage: def _make_identity(self) -> ChannelIdentity: return ChannelIdentity( channel_id="test", channel_type=ChannelType.WEBCHAT, channel_user_id="u1", channel_chat_id="c1", ) def test_minimal_fields(self): msg = ChannelMessage(identity=self._make_identity(), content="hello") assert msg.content == "hello" assert msg.message_type == MessageType.TEXT assert msg.event_type == EventType.MESSAGE_RECEIVED assert msg.chat_type == ChatType.DIRECT assert msg.attachments == [] assert msg.mentions is None assert msg.extracted_urls == [] assert msg.metadata == {} assert isinstance(msg.timestamp, datetime) def test_with_mentions(self): mentions = MentionsInfo(mentioned_user_ids=["u2"], is_bot_mentioned=True) msg = ChannelMessage(identity=self._make_identity(), content="@bot hi", mentions=mentions) assert msg.mentions.is_bot_mentioned is True def test_with_attachments(self): attachment = Attachment(type="image", url="https://example.com/img.png", filename="img.png") msg = ChannelMessage(identity=self._make_identity(), content="image", attachments=[attachment]) assert len(msg.attachments) == 1 assert msg.attachments[0].type == "image" def test_content_required(self): with pytest.raises(ValidationError): ChannelMessage(identity=self._make_identity()) def test_identity_required(self): with pytest.raises(ValidationError): ChannelMessage(content="hello") def test_metadata_default(self): msg = ChannelMessage(identity=self._make_identity(), content="test") assert msg.metadata == {} def test_metadata_custom(self): msg = ChannelMessage(identity=self._make_identity(), content="test", metadata={"key": "val"}) assert msg.metadata["key"] == "val" def test_timestamp_auto_generated(self): msg = ChannelMessage(identity=self._make_identity(), content="test") assert isinstance(msg.timestamp, datetime) def test_default_message_type(self): msg = ChannelMessage(identity=self._make_identity(), content="test") assert msg.message_type == MessageType.TEXT def test_custom_message_type(self): msg = ChannelMessage(identity=self._make_identity(), content="test", message_type=MessageType.IMAGE) assert msg.message_type == MessageType.IMAGE def test_reply_to_message_id(self): msg = ChannelMessage(identity=self._make_identity(), content="test", reply_to_message_id="reply_123") assert msg.reply_to_message_id == "reply_123" class TestChannelResponse: def _make_identity(self) -> ChannelIdentity: return ChannelIdentity( channel_id="test", channel_type=ChannelType.WEBCHAT, channel_user_id="u1", channel_chat_id="c1", ) def test_minimal_fields(self): resp = ChannelResponse(identity=self._make_identity(), content="reply") assert resp.content == "reply" assert resp.message_type == MessageType.TEXT assert isinstance(resp.timestamp, datetime) def test_with_attachments(self): attachment = Attachment(type="file", url="https://example.com/doc.pdf", filename="doc.pdf") resp = ChannelResponse(identity=self._make_identity(), content="here", attachments=[attachment]) assert len(resp.attachments) == 1 assert resp.attachments[0].filename == "doc.pdf" class TestDeliveryResult: def test_success(self): result = DeliveryResult(success=True, message_id="msg_123") assert result.success is True assert result.message_id == "msg_123" assert result.error is None def test_failure(self): result = DeliveryResult(success=False, error="timeout") assert result.success is False assert result.error == "timeout" def test_defaults(self): result = DeliveryResult(success=True) assert result.message_id is None assert result.error is None assert result.metadata == {} class TestAgentRequest: def test_minimal_fields(self): identity = ChannelIdentity( channel_id="test", channel_type=ChannelType.WEBCHAT, channel_user_id="u1", channel_chat_id="c1" ) message = ChannelMessage(identity=identity, content="hello") req = AgentRequest(user_id="user_1", thread_id="thread_1", agent_config_id=1, message=message) assert req.user_id == "user_1" assert req.thread_id == "thread_1" assert req.agent_config_id == 1 assert req.agent_id is None assert req.context == {} class TestAgentResult: def test_text_response(self): result = AgentResult(response_text="hello world") assert result.response_text == "hello world" assert result.response_type == "text" assert result.attachments == [] assert result.agent_state is None def test_error_response(self): result = AgentResult(response_text="error occurred", response_type="error") assert result.response_type == "error" def test_with_attachments(self): attachment = Attachment(type="file", url="https://example.com/f.pdf") result = AgentResult(response_text="here is file", attachments=[attachment]) assert len(result.attachments) == 1 class TestHealthStatus: def test_healthy(self): hs = HealthStatus(status="healthy", latency_ms=50.0) assert hs.status == "healthy" assert hs.latency_ms == 50.0 def test_degraded(self): hs = HealthStatus(status="degraded", last_error="slow response") assert hs.status == "degraded" assert hs.last_error == "slow response" def test_unhealthy(self): hs = HealthStatus(status="unhealthy") assert hs.status == "unhealthy" def test_invalid_status_raises(self): with pytest.raises(ValidationError): HealthStatus(status="unknown") def test_default_metadata(self): hs = HealthStatus(status="healthy") assert hs.metadata == {} class TestChannelStatus: def test_disconnected(self): assert ChannelStatus.DISCONNECTED == "disconnected" def test_connected(self): assert ChannelStatus.CONNECTED == "connected" class TestMentionsInfo: def test_defaults(self): mentions = MentionsInfo() assert mentions.mentioned_user_ids == [] assert mentions.is_bot_mentioned is False assert mentions.raw_text is None def test_bot_mentioned(self): mentions = MentionsInfo(is_bot_mentioned=True) assert mentions.is_bot_mentioned is True class TestAttachment: def test_defaults(self): attachment = Attachment() assert attachment.type == "file" assert attachment.url is None assert attachment.filename is None def test_full_fields(self): attachment = Attachment( type="image", url="https://example.com/photo.jpg", filename="photo.jpg", size_bytes=1024, mime_type="image/jpeg", file_id="file_001", ) assert attachment.size_bytes == 1024 assert attachment.mime_type == "image/jpeg" class TestChannelAccountSnapshot: def test_default_values(self): snap = ChannelAccountSnapshot() assert snap.account_id == "" assert snap.configured is False assert snap.enabled is True assert snap.linked is False assert snap.running is False assert snap.connected is False assert snap.status_state == "not-configured" assert snap.health_state == "stopped" assert snap.dm_policy == "pairing" assert snap.group_policy == "allowlist" assert snap.reconnect_attempts == 0 def test_custom_values(self): snap = ChannelAccountSnapshot(account_id="acc_1", name="test", configured=True, linked=True) assert snap.account_id == "acc_1" assert snap.name == "test" assert snap.configured is True assert snap.linked is True def test_extra_fields_allowed(self): snap = ChannelAccountSnapshot(account_id="acc_1", custom_field="extra_value") assert snap.custom_field == "extra_value" class TestBuildSnapshotFromAdapter: def test_build_from_minimal_adapter(self): class MinimalAdapter: pass adapter = MinimalAdapter() snap = build_snapshot_from_adapter(adapter) assert isinstance(snap, ChannelAccountSnapshot) assert snap.status_state == "not-configured" def test_build_from_full_adapter(self): class FullAdapter: account_id = "acc_full" account_name = "Full Adapter" _token_mgr = object() _enabled = True _linked = True _running = True _connected = True _status_state = "active" _status = None adapter = FullAdapter() snap = build_snapshot_from_adapter(adapter) assert snap.account_id == "acc_full" assert snap.name == "Full Adapter" assert snap.configured is True assert snap.linked is True assert snap.running is True assert snap.connected is True