488 lines
14 KiB
Python
488 lines
14 KiB
Python
"""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
|