Files
loyal_companion/tests/test_models.py
latte dbd534d860 refactor: Transform daemon_boyfriend into Loyal Companion
Rebrand and personalize the bot as 'Bartender' - a companion for those
who love deeply and feel intensely.

Major changes:
- Rename package: daemon_boyfriend -> loyal_companion
- New default personality: Bartender - wise, steady, non-judgmental
- Grief-aware system prompt (no toxic positivity, attachment-informed)
- New relationship levels: New Face -> Close Friend progression
- Bartender-style mood modifiers (steady presence)
- New fact types: attachment_pattern, grief_context, coping_mechanism
- Lower mood decay (0.05) for emotional stability
- Higher fact extraction rate (0.4) - Bartender pays attention

Updated all imports, configs, Docker files, and documentation.
2026-01-14 18:08:35 +01:00

488 lines
14 KiB
Python

"""Tests for database models."""
from datetime import datetime, timedelta, timezone
import pytest
from loyal_companion.models import (
BotOpinion,
BotState,
Conversation,
FactAssociation,
Guild,
GuildMember,
Message,
MoodHistory,
ScheduledEvent,
User,
UserCommunicationStyle,
UserFact,
UserPreference,
UserRelationship,
)
from loyal_companion.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