todoappapi / tests /integration /test_conversation_persistence.py
GrowWithTalha's picture
feat: sync backend changes from main repo
dc3879e
"""Integration tests for conversation persistence.
[Task]: T028
[From]: specs/004-ai-chatbot/tasks.md
Tests for User Story 6: Persistent Conversations
Validates that:
1. Conversations persist across page refreshes
2. Conversation IDs are properly returned and stored
3. Message history is loaded correctly
4. Conversations are updated on new messages
"""
import uuid
import pytest
from datetime import datetime
from sqlmodel import Session, select
from models.conversation import Conversation
from models.message import Message, MessageRole
from services.conversation import (
get_or_create_conversation,
load_conversation_history,
update_conversation_timestamp
)
class TestConversationPersistence:
"""Test conversation persistence across sessions."""
def test_create_new_conversation(self, test_session: Session):
"""Test creating a new conversation generates valid ID."""
# Arrange
user_id = uuid.uuid4()
# Act
conversation = get_or_create_conversation(
db=test_session,
user_id=user_id
)
# Assert
assert conversation is not None
assert conversation.id is not None
assert conversation.user_id == user_id
assert isinstance(conversation.created_at, datetime)
assert isinstance(conversation.updated_at, datetime)
def test_load_existing_conversation(self, test_session: Session):
"""Test loading an existing conversation by ID."""
# Arrange
user_id = uuid.uuid4()
original = get_or_create_conversation(
db=test_session,
user_id=user_id
)
original_id = original.id
# Act - Load the same conversation
loaded = get_or_create_conversation(
db=test_session,
user_id=user_id,
conversation_id=original_id
)
# Assert
assert loaded.id == original_id
assert loaded.user_id == user_id
def test_conversation_not_found_for_different_user(self, test_session: Session):
"""Test that a user cannot access another user's conversation."""
# Arrange
user1_id = uuid.uuid4()
user2_id = uuid.uuid4()
conversation = get_or_create_conversation(
db=test_session,
user_id=user1_id
)
# Act & Assert - Trying to access with different user should fail
with pytest.raises(ValueError, match="does not belong to this user"):
get_or_create_conversation(
db=test_session,
user_id=user2_id,
conversation_id=conversation.id
)
def test_conversation_not_found_invalid_id(self, test_session: Session):
"""Test that an invalid conversation ID raises an error."""
# Arrange
user_id = uuid.uuid4()
fake_id = uuid.uuid4()
# Act & Assert
with pytest.raises(ValueError, match="not found"):
get_or_create_conversation(
db=test_session,
user_id=user_id,
conversation_id=fake_id
)
def test_save_and_load_message_history(self, test_session: Session):
"""Test that messages are persisted and can be loaded."""
# Arrange
user_id = uuid.uuid4()
conversation = get_or_create_conversation(
db=test_session,
user_id=user_id
)
# Create user message
user_message = Message(
id=uuid.uuid4(),
conversation_id=conversation.id,
user_id=user_id,
role=MessageRole.USER,
content="Hello, AI!",
created_at=datetime.utcnow()
)
test_session.add(user_message)
test_session.commit()
# Create assistant message
assistant_message = Message(
id=uuid.uuid4(),
conversation_id=conversation.id,
user_id=user_id,
role=MessageRole.ASSISTANT,
content="Hi! How can I help?",
created_at=datetime.utcnow()
)
test_session.add(assistant_message)
test_session.commit()
# Act - Load conversation history
history = load_conversation_history(
db=test_session,
conversation_id=conversation.id
)
# Assert
assert len(history) == 2
assert history[0]["role"] == "user"
assert history[0]["content"] == "Hello, AI!"
assert history[1]["role"] == "assistant"
assert history[1]["content"] == "Hi! How can I help?"
def test_conversation_timestamp_update(self, test_session: Session):
"""Test that conversation updated_at is refreshed on new activity."""
# Arrange
user_id = uuid.uuid4()
conversation = get_or_create_conversation(
db=test_session,
user_id=user_id
)
original_updated_at = conversation.updated_at
# Small delay to ensure timestamp difference
import time
time.sleep(0.01)
# Act - Update timestamp
update_conversation_timestamp(
db=test_session,
conversation_id=conversation.id
)
# Assert
# Refresh from database
test_session.refresh(conversation)
assert conversation.updated_at > original_updated_at
def test_empty_conversation_history(self, test_session: Session):
"""Test loading history from a conversation with no messages."""
# Arrange
user_id = uuid.uuid4()
conversation = get_or_create_conversation(
db=test_session,
user_id=user_id
)
# Act - Load empty history
history = load_conversation_history(
db=test_session,
conversation_id=conversation.id
)
# Assert
assert history == []
def test_multiple_conversations_per_user(self, test_session: Session):
"""Test that a user can have multiple conversations."""
# Arrange
user_id = uuid.uuid4()
# Act - Create multiple conversations
conv1 = get_or_create_conversation(
db=test_session,
user_id=user_id
)
conv2 = get_or_create_conversation(
db=test_session,
user_id=user_id,
conversation_id=conv1.id # Same ID, returns same conversation
)
# Create a new conversation explicitly
# (In real usage, this would be triggered by user starting a new chat)
conv3 = get_or_create_conversation(
db=test_session,
user_id=uuid.uuid4() # Different user
)
# Assert
assert conv1.id == conv2.id # Same conversation
assert conv1.id != conv3.id # Different conversation
def test_messages_ordered_by_creation_time(self, test_session: Session):
"""Test that conversation history returns messages in chronological order."""
# Arrange
user_id = uuid.uuid4()
conversation = get_or_create_conversation(
db=test_session,
user_id=user_id
)
# Create messages with slight delays
messages = []
for i in range(3):
msg = Message(
id=uuid.uuid4(),
conversation_id=conversation.id,
user_id=user_id,
role=MessageRole.USER,
content=f"Message {i}",
created_at=datetime.utcnow()
)
test_session.add(msg)
test_session.commit()
messages.append(msg)
# Act - Load history
history = load_conversation_history(
db=test_session,
conversation_id=conversation.id
)
# Assert - Messages should be in chronological order
assert len(history) == 3
assert history[0]["content"] == "Message 0"
assert history[1]["content"] == "Message 1"
assert history[2]["content"] == "Message 2"