mirror of
https://github.com/langgenius/dify.git
synced 2026-03-04 15:26:21 +08:00
274 lines
9.3 KiB
Python
274 lines
9.3 KiB
Python
"""
|
|
Comprehensive unit tests for ConversationService.
|
|
|
|
This file keeps non-SQL guard/unit tests.
|
|
SQL-related tests were migrated to testcontainers integration tests.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
from unittest.mock import MagicMock, Mock, create_autospec, patch
|
|
|
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
|
from models import Account
|
|
from models.model import App, Conversation, EndUser
|
|
from services.conversation_service import ConversationService
|
|
from services.message_service import MessageService
|
|
|
|
|
|
class ConversationServiceTestDataFactory:
|
|
"""
|
|
Factory for creating test data and mock objects.
|
|
|
|
Provides reusable methods to create consistent mock objects for testing
|
|
conversation-related operations.
|
|
"""
|
|
|
|
@staticmethod
|
|
def create_account_mock(account_id: str = "account-123", **kwargs) -> Mock:
|
|
"""
|
|
Create a mock Account object.
|
|
|
|
Args:
|
|
account_id: Unique identifier for the account
|
|
**kwargs: Additional attributes to set on the mock
|
|
|
|
Returns:
|
|
Mock Account object with specified attributes
|
|
"""
|
|
account = create_autospec(Account, instance=True)
|
|
account.id = account_id
|
|
for key, value in kwargs.items():
|
|
setattr(account, key, value)
|
|
return account
|
|
|
|
@staticmethod
|
|
def create_end_user_mock(user_id: str = "user-123", **kwargs) -> Mock:
|
|
"""
|
|
Create a mock EndUser object.
|
|
|
|
Args:
|
|
user_id: Unique identifier for the end user
|
|
**kwargs: Additional attributes to set on the mock
|
|
|
|
Returns:
|
|
Mock EndUser object with specified attributes
|
|
"""
|
|
user = create_autospec(EndUser, instance=True)
|
|
user.id = user_id
|
|
for key, value in kwargs.items():
|
|
setattr(user, key, value)
|
|
return user
|
|
|
|
@staticmethod
|
|
def create_app_mock(app_id: str = "app-123", tenant_id: str = "tenant-123", **kwargs) -> Mock:
|
|
"""
|
|
Create a mock App object.
|
|
|
|
Args:
|
|
app_id: Unique identifier for the app
|
|
tenant_id: Tenant/workspace identifier
|
|
**kwargs: Additional attributes to set on the mock
|
|
|
|
Returns:
|
|
Mock App object with specified attributes
|
|
"""
|
|
app = create_autospec(App, instance=True)
|
|
app.id = app_id
|
|
app.tenant_id = tenant_id
|
|
app.name = kwargs.get("name", "Test App")
|
|
app.mode = kwargs.get("mode", "chat")
|
|
app.status = kwargs.get("status", "normal")
|
|
for key, value in kwargs.items():
|
|
setattr(app, key, value)
|
|
return app
|
|
|
|
@staticmethod
|
|
def create_conversation_mock(
|
|
conversation_id: str = "conv-123",
|
|
app_id: str = "app-123",
|
|
from_source: str = "console",
|
|
**kwargs,
|
|
) -> Mock:
|
|
"""
|
|
Create a mock Conversation object.
|
|
|
|
Args:
|
|
conversation_id: Unique identifier for the conversation
|
|
app_id: Associated app identifier
|
|
from_source: Source of conversation ('console' or 'api')
|
|
**kwargs: Additional attributes to set on the mock
|
|
|
|
Returns:
|
|
Mock Conversation object with specified attributes
|
|
"""
|
|
conversation = create_autospec(Conversation, instance=True)
|
|
conversation.id = conversation_id
|
|
conversation.app_id = app_id
|
|
conversation.from_source = from_source
|
|
conversation.from_end_user_id = kwargs.get("from_end_user_id")
|
|
conversation.from_account_id = kwargs.get("from_account_id")
|
|
conversation.is_deleted = kwargs.get("is_deleted", False)
|
|
conversation.name = kwargs.get("name", "Test Conversation")
|
|
conversation.status = kwargs.get("status", "normal")
|
|
conversation.created_at = kwargs.get("created_at", datetime.utcnow())
|
|
conversation.updated_at = kwargs.get("updated_at", datetime.utcnow())
|
|
for key, value in kwargs.items():
|
|
setattr(conversation, key, value)
|
|
return conversation
|
|
|
|
|
|
class TestConversationServicePagination:
|
|
"""Test conversation pagination operations."""
|
|
|
|
def test_pagination_with_empty_include_ids(self):
|
|
"""
|
|
Test that empty include_ids returns empty result.
|
|
|
|
When include_ids is an empty list, the service should short-circuit
|
|
and return empty results without querying the database.
|
|
"""
|
|
# Arrange - Set up test data
|
|
mock_session = MagicMock() # Mock database session
|
|
mock_app_model = ConversationServiceTestDataFactory.create_app_mock()
|
|
mock_user = ConversationServiceTestDataFactory.create_account_mock()
|
|
|
|
# Act - Call the service method with empty include_ids
|
|
result = ConversationService.pagination_by_last_id(
|
|
session=mock_session,
|
|
app_model=mock_app_model,
|
|
user=mock_user,
|
|
last_id=None,
|
|
limit=20,
|
|
invoke_from=InvokeFrom.WEB_APP,
|
|
include_ids=[], # Empty list should trigger early return
|
|
exclude_ids=None,
|
|
)
|
|
|
|
# Assert - Verify empty result without database query
|
|
assert result.data == [] # No conversations returned
|
|
assert result.has_more is False # No more pages available
|
|
assert result.limit == 20 # Limit preserved in response
|
|
|
|
def test_pagination_returns_empty_when_user_is_none(self):
|
|
"""
|
|
Test that pagination returns empty result when user is None.
|
|
|
|
This ensures proper handling of unauthenticated requests.
|
|
"""
|
|
# Arrange
|
|
mock_session = MagicMock()
|
|
mock_app_model = ConversationServiceTestDataFactory.create_app_mock()
|
|
|
|
# Act
|
|
result = ConversationService.pagination_by_last_id(
|
|
session=mock_session,
|
|
app_model=mock_app_model,
|
|
user=None, # No user provided
|
|
last_id=None,
|
|
limit=20,
|
|
invoke_from=InvokeFrom.WEB_APP,
|
|
)
|
|
|
|
# Assert - should return empty result without querying database
|
|
assert result.data == []
|
|
assert result.has_more is False
|
|
assert result.limit == 20
|
|
|
|
|
|
class TestConversationServiceMessageCreation:
|
|
"""
|
|
Test message creation and pagination.
|
|
|
|
Tests MessageService operations for creating and retrieving messages
|
|
within conversations.
|
|
"""
|
|
|
|
def test_pagination_returns_empty_when_no_user(self):
|
|
"""
|
|
Test that pagination returns empty result when user is None.
|
|
|
|
This ensures proper handling of unauthenticated requests.
|
|
"""
|
|
# Arrange
|
|
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
|
|
|
# Act
|
|
result = MessageService.pagination_by_first_id(
|
|
app_model=app_model,
|
|
user=None,
|
|
conversation_id="conv-123",
|
|
first_id=None,
|
|
limit=10,
|
|
)
|
|
|
|
# Assert
|
|
assert result.data == []
|
|
assert result.has_more is False
|
|
|
|
def test_pagination_returns_empty_when_no_conversation_id(self):
|
|
"""
|
|
Test that pagination returns empty result when conversation_id is None.
|
|
|
|
This ensures proper handling of invalid requests.
|
|
"""
|
|
# Arrange
|
|
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
|
user = ConversationServiceTestDataFactory.create_account_mock()
|
|
|
|
# Act
|
|
result = MessageService.pagination_by_first_id(
|
|
app_model=app_model,
|
|
user=user,
|
|
conversation_id="",
|
|
first_id=None,
|
|
limit=10,
|
|
)
|
|
|
|
# Assert
|
|
assert result.data == []
|
|
assert result.has_more is False
|
|
|
|
|
|
class TestConversationServiceSummarization:
|
|
"""
|
|
Test conversation summarization (auto-generated names).
|
|
|
|
Tests the auto_generate_name functionality that creates conversation
|
|
titles based on the first message.
|
|
"""
|
|
|
|
@patch("services.conversation_service.db.session", autospec=True)
|
|
@patch("services.conversation_service.ConversationService.get_conversation", autospec=True)
|
|
@patch("services.conversation_service.ConversationService.auto_generate_name", autospec=True)
|
|
def test_rename_with_auto_generate(self, mock_auto_generate, mock_get_conversation, mock_db_session):
|
|
"""
|
|
Test renaming conversation with auto-generation enabled.
|
|
|
|
When auto_generate is True, the service should call the auto_generate_name
|
|
method to generate a new name for the conversation.
|
|
"""
|
|
# Arrange
|
|
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
|
user = ConversationServiceTestDataFactory.create_account_mock()
|
|
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
|
conversation.name = "Auto-generated Name"
|
|
|
|
# Mock the conversation lookup to return our test conversation
|
|
mock_get_conversation.return_value = conversation
|
|
|
|
# Mock the auto_generate_name method to return the conversation
|
|
mock_auto_generate.return_value = conversation
|
|
|
|
# Act
|
|
result = ConversationService.rename(
|
|
app_model=app_model,
|
|
conversation_id=conversation.id,
|
|
user=user,
|
|
name="",
|
|
auto_generate=True,
|
|
)
|
|
|
|
# Assert
|
|
mock_auto_generate.assert_called_once_with(app_model, conversation)
|
|
assert result == conversation
|