""" Unit Tests — Conversation Models ================================= Tests for the new SQLAlchemy conversation models (mock-based, no DB required): - Conversation.display_name - Conversation.member_count - ConversationMember.is_owner - ConvMessage.__repr__ - MessageReaction unique constraint - Model imports """ import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) import pytest from unittest.mock import MagicMock from sqlalchemy import UniqueConstraint # ============================================================ # Import Tests # ============================================================ class TestModelImports: """Verify all 5 conversation models can be imported from database.""" def test_import_conversation(self): from database import Conversation assert Conversation is not None def test_import_conversation_member(self): from database import ConversationMember assert ConversationMember is not None def test_import_conv_message(self): from database import ConvMessage assert ConvMessage is not None def test_import_message_reaction(self): from database import MessageReaction assert MessageReaction is not None def test_import_message_attachment(self): from database import MessageAttachment assert MessageAttachment is not None # ============================================================ # Helpers # ============================================================ def _make_mock_member(name=None, email='user@example.com'): """Create a MagicMock simulating a ConversationMember with a User.""" member = MagicMock() user = MagicMock() user.name = name user.email = email member.user = user return member def _make_conversation(name=None, members=None): """Create a MagicMock simulating a Conversation instance.""" from database import Conversation conv = MagicMock(spec=Conversation) # Attach the real property logic by calling it on the mock conv.name = name conv.members = members or [] # Bind the real display_name property to this mock conv.display_name = Conversation.display_name.fget(conv) conv.member_count = Conversation.member_count.fget(conv) return conv # ============================================================ # Conversation.display_name Tests # ============================================================ class TestConversationDisplayName: """Test Conversation.display_name property.""" def test_display_name_returns_name_when_set(self): from database import Conversation conv = MagicMock() conv.name = 'Projekt Alpha' conv.members = [] result = Conversation.display_name.fget(conv) assert result == 'Projekt Alpha' def test_display_name_joins_member_names_when_no_name(self): from database import Conversation conv = MagicMock() conv.name = None conv.members = [ _make_mock_member(name='Anna'), _make_mock_member(name='Bob'), ] result = Conversation.display_name.fget(conv) assert 'Anna' in result assert 'Bob' in result def test_display_name_uses_email_prefix_when_user_has_no_name(self): from database import Conversation conv = MagicMock() conv.name = None conv.members = [ _make_mock_member(name=None, email='jan.kowalski@example.com'), ] result = Conversation.display_name.fget(conv) assert 'jan.kowalski' in result def test_display_name_truncates_beyond_four_members(self): from database import Conversation conv = MagicMock() conv.name = None conv.members = [ _make_mock_member(name=f'User{i}', email=f'user{i}@example.com') for i in range(6) ] result = Conversation.display_name.fget(conv) assert '+2' in result def test_display_name_no_suffix_for_four_or_fewer_members(self): from database import Conversation conv = MagicMock() conv.name = None conv.members = [ _make_mock_member(name=f'User{i}', email=f'user{i}@example.com') for i in range(4) ] result = Conversation.display_name.fget(conv) assert '+' not in result # ============================================================ # Conversation.member_count Tests # ============================================================ class TestConversationMemberCount: """Test Conversation.member_count property.""" def test_member_count_returns_length_of_members(self): from database import Conversation conv = MagicMock() conv.members = [MagicMock(), MagicMock(), MagicMock()] assert Conversation.member_count.fget(conv) == 3 def test_member_count_zero_when_no_members(self): from database import Conversation conv = MagicMock() conv.members = [] assert Conversation.member_count.fget(conv) == 0 # ============================================================ # ConversationMember.is_owner Tests # ============================================================ class TestConversationMemberIsOwner: """Test ConversationMember.is_owner property.""" def test_is_owner_true_when_role_is_owner(self): from database import ConversationMember cm = MagicMock() cm.role = 'owner' assert ConversationMember.is_owner.fget(cm) is True def test_is_owner_false_when_role_is_member(self): from database import ConversationMember cm = MagicMock() cm.role = 'member' assert ConversationMember.is_owner.fget(cm) is False def test_is_owner_false_when_role_is_admin(self): from database import ConversationMember cm = MagicMock() cm.role = 'admin' assert ConversationMember.is_owner.fget(cm) is False def test_is_owner_false_when_role_is_empty(self): from database import ConversationMember cm = MagicMock() cm.role = '' assert ConversationMember.is_owner.fget(cm) is False # ============================================================ # ConvMessage.__repr__ Tests # ============================================================ class TestConvMessageRepr: """Test ConvMessage.__repr__ returns expected format.""" def test_repr_format(self): from database import ConvMessage msg = MagicMock() msg.id = 42 msg.conversation_id = 7 msg.sender_id = 13 # Call the actual __repr__ method on the mock instance result = ConvMessage.__repr__(msg) assert '42' in result assert '7' in result assert '13' in result def test_repr_contains_class_indicator(self): from database import ConvMessage msg = MagicMock() msg.id = 1 msg.conversation_id = 2 msg.sender_id = 3 assert 'ConvMessage' in ConvMessage.__repr__(msg) # ============================================================ # MessageReaction UniqueConstraint Tests # ============================================================ class TestMessageReactionUniqueConstraint: """Verify MessageReaction __table_args__ contains UniqueConstraint.""" def test_table_args_contains_unique_constraint(self): from database import MessageReaction table_args = MessageReaction.__table_args__ assert table_args is not None constraint_types = [type(a) for a in table_args] assert UniqueConstraint in constraint_types def test_unique_constraint_covers_message_user_emoji(self): from database import MessageReaction table_args = MessageReaction.__table_args__ unique_constraints = [a for a in table_args if isinstance(a, UniqueConstraint)] assert len(unique_constraints) >= 1 constraint = unique_constraints[0] col_names = [col.key for col in constraint.columns] assert 'message_id' in col_names assert 'user_id' in col_names assert 'emoji' in col_names