Files
loyal_companion/tests/conftest.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

310 lines
8.1 KiB
Python

"""Pytest configuration and fixtures for the test suite."""
import asyncio
from datetime import datetime, timezone
from typing import AsyncGenerator, Generator
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
import pytest_asyncio
from sqlalchemy import event
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
from sqlalchemy.pool import StaticPool
from loyal_companion.config import Settings
from loyal_companion.models.base import Base
# --- Event Loop Fixture ---
@pytest.fixture(scope="session")
def event_loop() -> Generator[asyncio.AbstractEventLoop, None, None]:
"""Create an event loop for the test session."""
loop = asyncio.new_event_loop()
yield loop
loop.close()
# --- Database Fixtures ---
@pytest_asyncio.fixture
async def async_engine():
"""Create an async SQLite engine for testing."""
engine = create_async_engine(
"sqlite+aiosqlite:///:memory:",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
echo=False,
)
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
yield engine
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.drop_all)
await engine.dispose()
@pytest_asyncio.fixture
async def db_session(async_engine) -> AsyncGenerator[AsyncSession, None]:
"""Create a database session for testing."""
async_session_maker = async_sessionmaker(
async_engine,
class_=AsyncSession,
expire_on_commit=False,
)
async with async_session_maker() as session:
yield session
await session.rollback()
# --- Mock Settings Fixture ---
@pytest.fixture
def mock_settings() -> Settings:
"""Create mock settings for testing."""
with patch.dict(
"os.environ",
{
"DISCORD_TOKEN": "test_token",
"AI_PROVIDER": "openai",
"AI_MODEL": "gpt-4o-mini",
"OPENAI_API_KEY": "test_openai_key",
"ANTHROPIC_API_KEY": "test_anthropic_key",
"GEMINI_API_KEY": "test_gemini_key",
"OPENROUTER_API_KEY": "test_openrouter_key",
"BOT_NAME": "TestBot",
"BOT_PERSONALITY": "helpful and friendly",
"DATABASE_URL": "",
"LIVING_AI_ENABLED": "true",
"MOOD_ENABLED": "true",
"RELATIONSHIP_ENABLED": "true",
},
):
return Settings()
# --- Mock Discord Fixtures ---
@pytest.fixture
def mock_discord_user() -> MagicMock:
"""Create a mock Discord user."""
user = MagicMock()
user.id = 123456789
user.name = "TestUser"
user.display_name = "Test User"
user.mention = "<@123456789>"
user.bot = False
return user
@pytest.fixture
def mock_discord_message(mock_discord_user) -> MagicMock:
"""Create a mock Discord message."""
message = MagicMock()
message.author = mock_discord_user
message.content = "Hello, bot!"
message.channel = MagicMock()
message.channel.id = 987654321
message.channel.send = AsyncMock()
message.channel.typing = MagicMock(return_value=AsyncMock())
message.guild = MagicMock()
message.guild.id = 111222333
message.guild.name = "Test Guild"
message.id = 555666777
message.mentions = []
return message
@pytest.fixture
def mock_discord_bot() -> MagicMock:
"""Create a mock Discord bot."""
bot = MagicMock()
bot.user = MagicMock()
bot.user.id = 999888777
bot.user.name = "TestBot"
bot.user.mentioned_in = MagicMock(return_value=True)
return bot
# --- Mock AI Provider Fixtures ---
@pytest.fixture
def mock_ai_response() -> MagicMock:
"""Create a mock AI response."""
from loyal_companion.services.providers.base import AIResponse
return AIResponse(
content="This is a test response from the AI.",
model="test-model",
usage={"prompt_tokens": 10, "completion_tokens": 20, "total_tokens": 30},
)
@pytest.fixture
def mock_openai_client() -> MagicMock:
"""Create a mock OpenAI client."""
client = MagicMock()
response = MagicMock()
response.choices = [MagicMock()]
response.choices[0].message.content = "Test OpenAI response"
response.model = "gpt-4o-mini"
response.usage = MagicMock()
response.usage.prompt_tokens = 10
response.usage.completion_tokens = 20
response.usage.total_tokens = 30
client.chat.completions.create = AsyncMock(return_value=response)
return client
@pytest.fixture
def mock_anthropic_client() -> MagicMock:
"""Create a mock Anthropic client."""
client = MagicMock()
response = MagicMock()
response.content = [MagicMock()]
response.content[0].type = "text"
response.content[0].text = "Test Anthropic response"
response.model = "claude-sonnet-4-20250514"
response.usage = MagicMock()
response.usage.input_tokens = 10
response.usage.output_tokens = 20
client.messages.create = AsyncMock(return_value=response)
return client
@pytest.fixture
def mock_gemini_client() -> MagicMock:
"""Create a mock Gemini client."""
client = MagicMock()
response = MagicMock()
response.text = "Test Gemini response"
response.usage_metadata = MagicMock()
response.usage_metadata.prompt_token_count = 10
response.usage_metadata.candidates_token_count = 20
response.usage_metadata.total_token_count = 30
client.aio.models.generate_content = AsyncMock(return_value=response)
return client
# --- Model Fixtures ---
@pytest_asyncio.fixture
async def sample_user(db_session: AsyncSession):
"""Create a sample user in the database."""
from loyal_companion.models import User
user = User(
discord_id=123456789,
discord_username="testuser",
discord_display_name="Test User",
)
db_session.add(user)
await db_session.commit()
await db_session.refresh(user)
return user
@pytest_asyncio.fixture
async def sample_user_with_facts(db_session: AsyncSession, sample_user):
"""Create a sample user with facts."""
from loyal_companion.models import UserFact
facts = [
UserFact(
user_id=sample_user.id,
fact_type="hobby",
fact_content="likes programming",
confidence=1.0,
source="explicit",
),
UserFact(
user_id=sample_user.id,
fact_type="preference",
fact_content="prefers dark mode",
confidence=0.8,
source="conversation",
),
]
for fact in facts:
db_session.add(fact)
await db_session.commit()
return sample_user
@pytest_asyncio.fixture
async def sample_conversation(db_session: AsyncSession, sample_user):
"""Create a sample conversation."""
from loyal_companion.models import Conversation
conversation = Conversation(
user_id=sample_user.id,
guild_id=111222333,
channel_id=987654321,
)
db_session.add(conversation)
await db_session.commit()
await db_session.refresh(conversation)
return conversation
@pytest_asyncio.fixture
async def sample_bot_state(db_session: AsyncSession):
"""Create a sample bot state."""
from loyal_companion.models import BotState
bot_state = BotState(
guild_id=111222333,
mood_valence=0.5,
mood_arousal=0.3,
)
db_session.add(bot_state)
await db_session.commit()
await db_session.refresh(bot_state)
return bot_state
@pytest_asyncio.fixture
async def sample_user_relationship(db_session: AsyncSession, sample_user):
"""Create a sample user relationship."""
from loyal_companion.models import UserRelationship
relationship = UserRelationship(
user_id=sample_user.id,
guild_id=111222333,
relationship_score=50.0,
total_interactions=10,
positive_interactions=8,
negative_interactions=1,
)
db_session.add(relationship)
await db_session.commit()
await db_session.refresh(relationship)
return relationship
# --- Utility Fixtures ---
@pytest.fixture
def utc_now() -> datetime:
"""Get current UTC time."""
return datetime.now(timezone.utc)