"""Tests for database models.""" from datetime import datetime, timedelta, timezone import pytest from daemon_boyfriend.models import ( BotOpinion, BotState, Conversation, FactAssociation, Guild, GuildMember, Message, MoodHistory, ScheduledEvent, User, UserCommunicationStyle, UserFact, UserPreference, UserRelationship, ) from daemon_boyfriend.models.base import utc_now class TestUtcNow: """Tests for the utc_now helper function.""" def test_returns_timezone_aware(self): """Test that utc_now returns timezone-aware datetime.""" now = utc_now() assert now.tzinfo is not None assert now.tzinfo == timezone.utc def test_returns_current_time(self): """Test that utc_now returns approximately current time.""" before = datetime.now(timezone.utc) now = utc_now() after = datetime.now(timezone.utc) assert before <= now <= after class TestUserModel: """Tests for the User model.""" @pytest.mark.asyncio async def test_create_user(self, db_session): """Test creating a user.""" user = User( discord_id=123456789, discord_username="testuser", discord_display_name="Test User", ) db_session.add(user) await db_session.commit() assert user.id is not None assert user.discord_id == 123456789 assert user.is_active is True assert user.created_at is not None @pytest.mark.asyncio async def test_user_display_name_property(self, db_session): """Test the display_name property.""" user = User( discord_id=123456789, discord_username="testuser", discord_display_name="Test User", ) db_session.add(user) await db_session.commit() # Uses discord_display_name when no custom_name assert user.display_name == "Test User" # Uses custom_name when set user.custom_name = "Custom Name" assert user.display_name == "Custom Name" @pytest.mark.asyncio async def test_user_display_name_fallback(self, db_session): """Test display_name falls back to username.""" user = User( discord_id=123456789, discord_username="testuser", ) db_session.add(user) await db_session.commit() assert user.display_name == "testuser" @pytest.mark.asyncio async def test_user_timestamps(self, db_session): """Test user timestamp fields.""" user = User( discord_id=123456789, discord_username="testuser", ) db_session.add(user) await db_session.commit() assert user.first_seen_at is not None assert user.last_seen_at is not None assert user.created_at is not None assert user.updated_at is not None class TestUserFactModel: """Tests for the UserFact model.""" @pytest.mark.asyncio async def test_create_fact(self, db_session, sample_user): """Test creating a user fact.""" fact = UserFact( user_id=sample_user.id, fact_type="hobby", fact_content="likes gaming", confidence=0.9, source="conversation", ) db_session.add(fact) await db_session.commit() assert fact.id is not None assert fact.is_active is True assert fact.learned_at is not None @pytest.mark.asyncio async def test_fact_default_values(self, db_session, sample_user): """Test fact default values.""" fact = UserFact( user_id=sample_user.id, fact_type="general", fact_content="test fact", ) db_session.add(fact) await db_session.commit() assert fact.confidence == 1.0 assert fact.source == "conversation" assert fact.is_active is True class TestConversationModel: """Tests for the Conversation model.""" @pytest.mark.asyncio async def test_create_conversation(self, db_session, sample_user): """Test creating a conversation.""" conv = Conversation( user_id=sample_user.id, guild_id=111222333, channel_id=444555666, ) db_session.add(conv) await db_session.commit() assert conv.id is not None assert conv.message_count == 0 assert conv.is_active is True assert conv.started_at is not None @pytest.mark.asyncio async def test_conversation_with_messages(self, db_session, sample_user): """Test conversation with messages.""" conv = Conversation( user_id=sample_user.id, channel_id=444555666, ) db_session.add(conv) await db_session.commit() msg = Message( conversation_id=conv.id, user_id=sample_user.id, role="user", content="Hello!", ) db_session.add(msg) await db_session.commit() assert msg.id is not None assert msg.role == "user" assert msg.has_images is False class TestMessageModel: """Tests for the Message model.""" @pytest.mark.asyncio async def test_create_message(self, db_session, sample_conversation, sample_user): """Test creating a message.""" msg = Message( conversation_id=sample_conversation.id, user_id=sample_user.id, role="user", content="Test message", ) db_session.add(msg) await db_session.commit() assert msg.id is not None assert msg.has_images is False assert msg.image_urls is None @pytest.mark.asyncio async def test_message_with_images(self, db_session, sample_conversation, sample_user): """Test message with images.""" msg = Message( conversation_id=sample_conversation.id, user_id=sample_user.id, role="user", content="Look at this", has_images=True, image_urls=["https://example.com/image.png"], ) db_session.add(msg) await db_session.commit() assert msg.has_images is True assert len(msg.image_urls) == 1 class TestGuildModel: """Tests for the Guild model.""" @pytest.mark.asyncio async def test_create_guild(self, db_session): """Test creating a guild.""" guild = Guild( discord_id=111222333, name="Test Guild", ) db_session.add(guild) await db_session.commit() assert guild.id is not None assert guild.is_active is True assert guild.settings == {} @pytest.mark.asyncio async def test_guild_with_settings(self, db_session): """Test guild with custom settings.""" guild = Guild( discord_id=111222333, name="Test Guild", settings={"prefix": "!", "language": "en"}, ) db_session.add(guild) await db_session.commit() assert guild.settings["prefix"] == "!" assert guild.settings["language"] == "en" class TestGuildMemberModel: """Tests for the GuildMember model.""" @pytest.mark.asyncio async def test_create_guild_member(self, db_session, sample_user): """Test creating a guild member.""" guild = Guild(discord_id=111222333, name="Test Guild") db_session.add(guild) await db_session.commit() member = GuildMember( guild_id=guild.id, user_id=sample_user.id, guild_nickname="TestNick", ) db_session.add(member) await db_session.commit() assert member.id is not None assert member.guild_nickname == "TestNick" class TestBotStateModel: """Tests for the BotState model.""" @pytest.mark.asyncio async def test_create_bot_state(self, db_session): """Test creating a bot state.""" state = BotState(guild_id=111222333) db_session.add(state) await db_session.commit() assert state.id is not None assert state.mood_valence == 0.0 assert state.mood_arousal == 0.0 assert state.total_messages_sent == 0 @pytest.mark.asyncio async def test_bot_state_defaults(self, db_session): """Test bot state default values.""" state = BotState() db_session.add(state) await db_session.commit() assert state.guild_id is None assert state.preferences == {} assert state.total_facts_learned == 0 assert state.total_users_known == 0 class TestBotOpinionModel: """Tests for the BotOpinion model.""" @pytest.mark.asyncio async def test_create_opinion(self, db_session): """Test creating a bot opinion.""" opinion = BotOpinion( topic="programming", sentiment=0.8, interest_level=0.9, ) db_session.add(opinion) await db_session.commit() assert opinion.id is not None assert opinion.discussion_count == 0 assert opinion.formed_at is not None class TestUserRelationshipModel: """Tests for the UserRelationship model.""" @pytest.mark.asyncio async def test_create_relationship(self, db_session, sample_user): """Test creating a user relationship.""" rel = UserRelationship( user_id=sample_user.id, guild_id=111222333, ) db_session.add(rel) await db_session.commit() assert rel.id is not None assert rel.relationship_score == 10.0 assert rel.total_interactions == 0 @pytest.mark.asyncio async def test_relationship_defaults(self, db_session, sample_user): """Test relationship default values.""" rel = UserRelationship(user_id=sample_user.id) db_session.add(rel) await db_session.commit() assert rel.shared_references == {} assert rel.positive_interactions == 0 assert rel.negative_interactions == 0 assert rel.avg_message_length == 0.0 class TestUserCommunicationStyleModel: """Tests for the UserCommunicationStyle model.""" @pytest.mark.asyncio async def test_create_style(self, db_session, sample_user): """Test creating a communication style.""" style = UserCommunicationStyle(user_id=sample_user.id) db_session.add(style) await db_session.commit() assert style.id is not None assert style.preferred_length == "medium" assert style.preferred_formality == 0.5 @pytest.mark.asyncio async def test_style_defaults(self, db_session, sample_user): """Test communication style defaults.""" style = UserCommunicationStyle(user_id=sample_user.id) db_session.add(style) await db_session.commit() assert style.emoji_affinity == 0.5 assert style.humor_affinity == 0.5 assert style.detail_preference == 0.5 assert style.samples_collected == 0 assert style.confidence == 0.0 class TestScheduledEventModel: """Tests for the ScheduledEvent model.""" @pytest.mark.asyncio async def test_create_event(self, db_session, sample_user): """Test creating a scheduled event.""" trigger_time = datetime.now(timezone.utc) + timedelta(days=1) event = ScheduledEvent( user_id=sample_user.id, event_type="birthday", trigger_at=trigger_time, title="Birthday reminder", ) db_session.add(event) await db_session.commit() assert event.id is not None assert event.status == "pending" assert event.is_recurring is False @pytest.mark.asyncio async def test_recurring_event(self, db_session, sample_user): """Test creating a recurring event.""" trigger_time = datetime.now(timezone.utc) + timedelta(days=1) event = ScheduledEvent( user_id=sample_user.id, event_type="birthday", trigger_at=trigger_time, title="Birthday", is_recurring=True, recurrence_rule="yearly", ) db_session.add(event) await db_session.commit() assert event.is_recurring is True assert event.recurrence_rule == "yearly" class TestFactAssociationModel: """Tests for the FactAssociation model.""" @pytest.mark.asyncio async def test_create_association(self, db_session, sample_user): """Test creating a fact association.""" fact1 = UserFact( user_id=sample_user.id, fact_type="hobby", fact_content="likes programming", ) fact2 = UserFact( user_id=sample_user.id, fact_type="hobby", fact_content="likes Python", ) db_session.add(fact1) db_session.add(fact2) await db_session.commit() assoc = FactAssociation( fact_id_1=fact1.id, fact_id_2=fact2.id, association_type="shared_interest", strength=0.8, ) db_session.add(assoc) await db_session.commit() assert assoc.id is not None assert assoc.discovered_at is not None class TestMoodHistoryModel: """Tests for the MoodHistory model.""" @pytest.mark.asyncio async def test_create_mood_history(self, db_session, sample_user): """Test creating a mood history entry.""" history = MoodHistory( guild_id=111222333, valence=0.5, arousal=0.3, trigger_type="conversation", trigger_user_id=sample_user.id, trigger_description="Had a nice chat", ) db_session.add(history) await db_session.commit() assert history.id is not None assert history.recorded_at is not None @pytest.mark.asyncio async def test_mood_history_without_user(self, db_session): """Test mood history without trigger user.""" history = MoodHistory( valence=-0.2, arousal=-0.1, trigger_type="time_decay", ) db_session.add(history) await db_session.commit() assert history.trigger_user_id is None assert history.trigger_description is None