776 lines
27 KiB
Python
776 lines
27 KiB
Python
"""Unit tests for ConversationManagerService."""
|
|
|
|
from datetime import UTC, datetime, timedelta
|
|
from typing import Literal
|
|
from unittest.mock import AsyncMock, Mock, patch
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
|
|
from capa_de_integracion.config import Settings
|
|
from capa_de_integracion.models import (
|
|
ConversationEntry,
|
|
ConversationRequest,
|
|
ConversationSession,
|
|
DetectIntentResponse,
|
|
QueryResult,
|
|
User,
|
|
)
|
|
from capa_de_integracion.services.conversation import ConversationManagerService
|
|
from capa_de_integracion.services.dlp import DLPService
|
|
from capa_de_integracion.services.rag import RAGServiceBase
|
|
from capa_de_integracion.services.storage.firestore import FirestoreService
|
|
from capa_de_integracion.services.storage.redis import RedisService
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_settings() -> Settings:
|
|
"""Create mock settings."""
|
|
settings = Mock(spec=Settings)
|
|
settings.dlp_template_complete_flow = "test_template"
|
|
settings.conversation_context_message_limit = 60
|
|
settings.conversation_context_days_limit = 30
|
|
return settings
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_dlp() -> DLPService:
|
|
"""Create mock DLP service."""
|
|
dlp = Mock(spec=DLPService)
|
|
dlp.get_obfuscated_string = AsyncMock(return_value="obfuscated message")
|
|
return dlp
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_rag() -> RAGServiceBase:
|
|
"""Create mock RAG service."""
|
|
rag = Mock(spec=RAGServiceBase)
|
|
rag.query = AsyncMock(return_value="RAG response")
|
|
return rag
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_redis() -> RedisService:
|
|
"""Create mock Redis service."""
|
|
redis = Mock(spec=RedisService)
|
|
redis.get_session = AsyncMock(return_value=None)
|
|
redis.save_session = AsyncMock()
|
|
redis.get_notification_session = AsyncMock(return_value=None)
|
|
redis.delete_notification_session = AsyncMock()
|
|
return redis
|
|
|
|
|
|
@pytest.fixture
|
|
def mock_firestore() -> FirestoreService:
|
|
"""Create mock Firestore service."""
|
|
firestore = Mock(spec=FirestoreService)
|
|
firestore.get_session_by_phone = AsyncMock(return_value=None)
|
|
firestore.create_session = AsyncMock()
|
|
firestore.save_session = AsyncMock()
|
|
firestore.save_entry = AsyncMock()
|
|
firestore.get_entries = AsyncMock(return_value=[])
|
|
firestore.update_notification_status = AsyncMock()
|
|
# Mock db.collection for notifications
|
|
mock_doc = Mock()
|
|
mock_doc.exists = False
|
|
mock_doc_ref = Mock()
|
|
mock_doc_ref.get = AsyncMock(return_value=mock_doc)
|
|
mock_collection = Mock()
|
|
mock_collection.document = Mock(return_value=mock_doc_ref)
|
|
firestore.db = Mock()
|
|
firestore.db.collection = Mock(return_value=mock_collection)
|
|
firestore.notifications_collection = "notifications"
|
|
return firestore
|
|
|
|
|
|
@pytest.fixture
|
|
def conversation_service(
|
|
mock_settings: Settings,
|
|
mock_rag: RAGServiceBase,
|
|
mock_redis: RedisService,
|
|
mock_firestore: FirestoreService,
|
|
mock_dlp: DLPService,
|
|
) -> ConversationManagerService:
|
|
"""Create conversation service with mocked dependencies."""
|
|
with patch(
|
|
"capa_de_integracion.services.conversation.QuickReplyContentService"
|
|
):
|
|
service = ConversationManagerService(
|
|
settings=mock_settings,
|
|
rag_service=mock_rag,
|
|
redis_service=mock_redis,
|
|
firestore_service=mock_firestore,
|
|
dlp_service=mock_dlp,
|
|
)
|
|
return service
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_session() -> ConversationSession:
|
|
"""Create a sample conversation session."""
|
|
return ConversationSession(
|
|
session_id="test_session_123",
|
|
user_id="user_by_phone_1234567890",
|
|
telefono="1234567890",
|
|
last_modified=datetime.now(UTC),
|
|
last_message="Hello",
|
|
pantalla_contexto=None,
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_request() -> ConversationRequest:
|
|
"""Create a sample conversation request."""
|
|
return ConversationRequest(
|
|
mensaje="Hello, I need help",
|
|
usuario=User(
|
|
telefono="1234567890",
|
|
nickname="TestUser",
|
|
),
|
|
canal="whatsapp",
|
|
)
|
|
|
|
|
|
# ============================================================================
|
|
# Test Session Management
|
|
# ============================================================================
|
|
|
|
|
|
class TestSessionManagement:
|
|
"""Tests for session management methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_obtain_session_from_redis(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test obtaining session from Redis."""
|
|
mock_redis.get_session = AsyncMock(return_value=sample_session)
|
|
|
|
result = await conversation_service._obtain_or_create_session("1234567890")
|
|
|
|
assert result == sample_session
|
|
mock_redis.get_session.assert_awaited_once_with("1234567890")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_obtain_session_from_firestore_when_redis_miss(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
mock_firestore: FirestoreService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test obtaining session from Firestore when Redis misses."""
|
|
mock_redis.get_session = AsyncMock(return_value=None)
|
|
mock_firestore.get_session_by_phone = AsyncMock(return_value=sample_session)
|
|
|
|
result = await conversation_service._obtain_or_create_session("1234567890")
|
|
|
|
assert result == sample_session
|
|
mock_redis.get_session.assert_awaited_once()
|
|
mock_firestore.get_session_by_phone.assert_awaited_once_with("1234567890")
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_create_new_session_when_both_miss(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
mock_firestore: FirestoreService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test creating new session when both Redis and Firestore miss."""
|
|
mock_redis.get_session = AsyncMock(return_value=None)
|
|
mock_firestore.get_session_by_phone = AsyncMock(return_value=None)
|
|
mock_firestore.create_session = AsyncMock(return_value=sample_session)
|
|
|
|
result = await conversation_service._obtain_or_create_session("1234567890")
|
|
|
|
assert result == sample_session
|
|
mock_firestore.create_session.assert_awaited_once()
|
|
# Verify the session was auto-cached to Redis
|
|
mock_redis.save_session.assert_awaited_once_with(sample_session)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_session_auto_cached_to_redis(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
mock_firestore: FirestoreService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test that newly created session is auto-cached to Redis."""
|
|
mock_redis.get_session = AsyncMock(return_value=None)
|
|
mock_firestore.get_session_by_phone = AsyncMock(return_value=None)
|
|
mock_firestore.create_session = AsyncMock(return_value=sample_session)
|
|
|
|
await conversation_service._obtain_or_create_session("1234567890")
|
|
|
|
mock_redis.save_session.assert_awaited_once_with(sample_session)
|
|
|
|
|
|
# ============================================================================
|
|
# Test Entry Persistence
|
|
# ============================================================================
|
|
|
|
|
|
class TestEntryPersistence:
|
|
"""Tests for conversation entry persistence methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_conversation_turn_with_conversacion_type(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
) -> None:
|
|
"""Test saving conversation turn with CONVERSACION type."""
|
|
await conversation_service._save_conversation_turn(
|
|
session_id="test_session",
|
|
user_text="Hello",
|
|
assistant_text="Hi there",
|
|
entry_type="CONVERSACION",
|
|
canal="whatsapp",
|
|
)
|
|
|
|
assert mock_firestore.save_entry.await_count == 2
|
|
# Verify user entry
|
|
user_call = mock_firestore.save_entry.await_args_list[0]
|
|
assert user_call[0][0] == "test_session"
|
|
user_entry = user_call[0][1]
|
|
assert user_entry.entity == "user"
|
|
assert user_entry.text == "Hello"
|
|
assert user_entry.type == "CONVERSACION"
|
|
assert user_entry.canal == "whatsapp"
|
|
# Verify assistant entry
|
|
assistant_call = mock_firestore.save_entry.await_args_list[1]
|
|
assistant_entry = assistant_call[0][1]
|
|
assert assistant_entry.entity == "assistant"
|
|
assert assistant_entry.text == "Hi there"
|
|
assert assistant_entry.type == "CONVERSACION"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_conversation_turn_with_llm_type(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
) -> None:
|
|
"""Test saving conversation turn with LLM type."""
|
|
await conversation_service._save_conversation_turn(
|
|
session_id="test_session",
|
|
user_text="What's the weather?",
|
|
assistant_text="It's sunny",
|
|
entry_type="LLM",
|
|
canal="telegram",
|
|
)
|
|
|
|
assert mock_firestore.save_entry.await_count == 2
|
|
assistant_call = mock_firestore.save_entry.await_args_list[1]
|
|
assistant_entry = assistant_call[0][1]
|
|
assert assistant_entry.type == "LLM"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_conversation_turn_with_canal(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
) -> None:
|
|
"""Test saving conversation turn with canal parameter."""
|
|
await conversation_service._save_conversation_turn(
|
|
session_id="test_session",
|
|
user_text="Test",
|
|
assistant_text="Response",
|
|
entry_type="CONVERSACION",
|
|
canal="sms",
|
|
)
|
|
|
|
user_call = mock_firestore.save_entry.await_args_list[0]
|
|
user_entry = user_call[0][1]
|
|
assert user_entry.canal == "sms"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_save_conversation_turn_without_canal(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
) -> None:
|
|
"""Test saving conversation turn without canal parameter."""
|
|
await conversation_service._save_conversation_turn(
|
|
session_id="test_session",
|
|
user_text="Test",
|
|
assistant_text="Response",
|
|
entry_type="CONVERSACION",
|
|
)
|
|
|
|
user_call = mock_firestore.save_entry.await_args_list[0]
|
|
user_entry = user_call[0][1]
|
|
assert user_entry.canal is None
|
|
|
|
|
|
# ============================================================================
|
|
# Test Session Updates
|
|
# ============================================================================
|
|
|
|
|
|
class TestSessionUpdates:
|
|
"""Tests for session update methods."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_session_sets_last_message(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test that update_session sets last_message."""
|
|
await conversation_service._update_session_after_turn(
|
|
session=sample_session,
|
|
last_message="New message",
|
|
)
|
|
|
|
assert sample_session.last_message == "New message"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_session_sets_timestamp(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test that update_session sets timestamp."""
|
|
old_timestamp = sample_session.last_modified
|
|
await conversation_service._update_session_after_turn(
|
|
session=sample_session,
|
|
last_message="New message",
|
|
)
|
|
|
|
assert sample_session.last_modified > old_timestamp
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_session_saves_to_firestore(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test that update_session saves to Firestore."""
|
|
await conversation_service._update_session_after_turn(
|
|
session=sample_session,
|
|
last_message="New message",
|
|
)
|
|
|
|
mock_firestore.save_session.assert_awaited_once_with(sample_session)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_update_session_saves_to_redis(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test that update_session saves to Redis."""
|
|
await conversation_service._update_session_after_turn(
|
|
session=sample_session,
|
|
last_message="New message",
|
|
)
|
|
|
|
mock_redis.save_session.assert_awaited_once_with(sample_session)
|
|
|
|
|
|
# ============================================================================
|
|
# Test Quick Reply Path
|
|
# ============================================================================
|
|
|
|
|
|
class TestQuickReplyPath:
|
|
"""Tests for quick reply path handling."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quick_reply_path_with_valid_context(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test quick reply path with valid pantalla_contexto."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
sample_session.last_modified = datetime.now(UTC)
|
|
|
|
# Mock quick reply service
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Quick reply response"),
|
|
)
|
|
conversation_service._manage_quick_reply_conversation = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
result = await conversation_service._handle_quick_reply_path(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
assert result == mock_response
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quick_reply_path_with_stale_context_returns_none(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test quick reply path with stale pantalla_contexto returns None."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
# Set timestamp to 11 minutes ago (stale)
|
|
sample_session.last_modified = datetime.now(UTC) - timedelta(minutes=11)
|
|
|
|
result = await conversation_service._handle_quick_reply_path(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quick_reply_path_without_context_returns_none(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test quick reply path without pantalla_contexto returns None."""
|
|
sample_session.pantalla_contexto = None
|
|
|
|
result = await conversation_service._handle_quick_reply_path(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
assert result is None
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quick_reply_path_saves_entries(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test quick reply path saves conversation entries."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
sample_session.last_modified = datetime.now(UTC)
|
|
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Quick reply response"),
|
|
)
|
|
conversation_service._manage_quick_reply_conversation = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
await conversation_service._handle_quick_reply_path(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
assert mock_firestore.save_entry.await_count == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_quick_reply_path_updates_session(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_redis: RedisService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test quick reply path updates session."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
sample_session.last_modified = datetime.now(UTC)
|
|
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Quick reply response"),
|
|
)
|
|
conversation_service._manage_quick_reply_conversation = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
await conversation_service._handle_quick_reply_path(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
mock_firestore.save_session.assert_awaited_once()
|
|
mock_redis.save_session.assert_awaited_once()
|
|
|
|
|
|
# ============================================================================
|
|
# Test Standard Conversation Path
|
|
# ============================================================================
|
|
|
|
|
|
class TestStandardConversation:
|
|
"""Tests for standard conversation flow."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_loads_history(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation loads history."""
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
mock_firestore.get_entries.assert_awaited_once_with(
|
|
sample_session.session_id,
|
|
limit=60,
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_queries_rag(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_rag: RAGServiceBase,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation queries RAG service."""
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
mock_rag.query.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_saves_entries(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation saves entries."""
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
assert mock_firestore.save_entry.await_count == 2
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_updates_session(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
mock_redis: RedisService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation updates session."""
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
# save_session is called in _update_session_after_turn
|
|
assert mock_firestore.save_session.await_count >= 1
|
|
assert mock_redis.save_session.await_count >= 1
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_marks_notifications_processed(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation marks notifications as processed."""
|
|
# Mock that there are active notifications
|
|
conversation_service._get_active_notifications = AsyncMock(
|
|
return_value=[Mock(texto="Test notification")]
|
|
)
|
|
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
mock_firestore.update_notification_status.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_standard_conversation_without_notifications(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_firestore: FirestoreService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test standard conversation without notifications."""
|
|
conversation_service._get_active_notifications = AsyncMock(return_value=[])
|
|
|
|
await conversation_service._handle_standard_conversation(
|
|
request=sample_request,
|
|
session=sample_session,
|
|
)
|
|
|
|
mock_firestore.update_notification_status.assert_not_awaited()
|
|
|
|
|
|
# ============================================================================
|
|
# Test Orchestration
|
|
# ============================================================================
|
|
|
|
|
|
class TestOrchestration:
|
|
"""Tests for main orchestration logic."""
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_applies_dlp(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
mock_dlp: DLPService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test manage_conversation applies DLP obfuscation."""
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
return_value=sample_session
|
|
)
|
|
conversation_service._handle_standard_conversation = AsyncMock(
|
|
return_value=DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Response"),
|
|
)
|
|
)
|
|
|
|
await conversation_service.manage_conversation(sample_request)
|
|
|
|
mock_dlp.get_obfuscated_string.assert_awaited_once()
|
|
assert sample_request.mensaje == "obfuscated message"
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_obtains_session(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test manage_conversation obtains session."""
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
return_value=sample_session
|
|
)
|
|
conversation_service._handle_standard_conversation = AsyncMock(
|
|
return_value=DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Response"),
|
|
)
|
|
)
|
|
|
|
await conversation_service.manage_conversation(sample_request)
|
|
|
|
conversation_service._obtain_or_create_session.assert_awaited_once_with(
|
|
"1234567890"
|
|
)
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_uses_quick_reply_path_when_valid(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test manage_conversation uses quick reply path when valid."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
sample_session.last_modified = datetime.now(UTC)
|
|
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
return_value=sample_session
|
|
)
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Quick reply"),
|
|
)
|
|
conversation_service._handle_quick_reply_path = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
conversation_service._handle_standard_conversation = AsyncMock()
|
|
|
|
result = await conversation_service.manage_conversation(sample_request)
|
|
|
|
assert result == mock_response
|
|
conversation_service._handle_quick_reply_path.assert_awaited_once()
|
|
conversation_service._handle_standard_conversation.assert_not_awaited()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_uses_standard_path_when_no_context(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test manage_conversation uses standard path when no context."""
|
|
sample_session.pantalla_contexto = None
|
|
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
return_value=sample_session
|
|
)
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Standard response"),
|
|
)
|
|
conversation_service._handle_standard_conversation = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
result = await conversation_service.manage_conversation(sample_request)
|
|
|
|
assert result == mock_response
|
|
conversation_service._handle_standard_conversation.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_uses_standard_path_when_stale_context(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
sample_session: ConversationSession,
|
|
) -> None:
|
|
"""Test manage_conversation uses standard path when context is stale."""
|
|
sample_session.pantalla_contexto = "screen_123"
|
|
sample_session.last_modified = datetime.now(UTC) - timedelta(minutes=11)
|
|
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
return_value=sample_session
|
|
)
|
|
mock_response = DetectIntentResponse(
|
|
responseId=str(uuid4()),
|
|
queryResult=QueryResult(responseText="Standard response"),
|
|
)
|
|
conversation_service._handle_quick_reply_path = AsyncMock(return_value=None)
|
|
conversation_service._handle_standard_conversation = AsyncMock(
|
|
return_value=mock_response
|
|
)
|
|
|
|
result = await conversation_service.manage_conversation(sample_request)
|
|
|
|
assert result == mock_response
|
|
conversation_service._handle_standard_conversation.assert_awaited_once()
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_manage_conversation_handles_exceptions(
|
|
self,
|
|
conversation_service: ConversationManagerService,
|
|
sample_request: ConversationRequest,
|
|
) -> None:
|
|
"""Test manage_conversation handles exceptions properly."""
|
|
conversation_service._obtain_or_create_session = AsyncMock(
|
|
side_effect=Exception("Test error")
|
|
)
|
|
|
|
with pytest.raises(Exception, match="Test error"):
|
|
await conversation_service.manage_conversation(sample_request)
|