mirror of
https://github.com/langgenius/dify.git
synced 2026-04-17 03:16:33 +08:00
test: migrate conversation service mock tests to testcontainers (#35198)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
7f4fe4d064
commit
e8af6a6b3b
@ -0,0 +1,524 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from graphon.variables import FloatVariable, IntegerVariable, StringVariable
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from extensions.ext_database import db
|
||||
from models.account import Account, Tenant, TenantAccountJoin
|
||||
from models.enums import ConversationFromSource
|
||||
from models.model import App, Conversation, EndUser
|
||||
from models.workflow import ConversationVariable
|
||||
from services.conversation_service import ConversationService
|
||||
from services.errors.conversation import (
|
||||
ConversationVariableNotExistsError,
|
||||
ConversationVariableTypeMismatchError,
|
||||
LastConversationNotExistsError,
|
||||
)
|
||||
|
||||
|
||||
class ConversationServiceVariableIntegrationFactory:
|
||||
@staticmethod
|
||||
def create_app_and_account(db_session_with_containers):
|
||||
tenant = Tenant(name=f"Tenant {uuid4()}")
|
||||
db_session_with_containers.add(tenant)
|
||||
db_session_with_containers.flush()
|
||||
|
||||
account = Account(
|
||||
name=f"Account {uuid4()}",
|
||||
email=f"conversation-variable-{uuid4()}@example.com",
|
||||
password="hashed-password",
|
||||
password_salt="salt",
|
||||
interface_language="en-US",
|
||||
timezone="UTC",
|
||||
)
|
||||
db_session_with_containers.add(account)
|
||||
db_session_with_containers.flush()
|
||||
|
||||
tenant_join = TenantAccountJoin(
|
||||
tenant_id=tenant.id,
|
||||
account_id=account.id,
|
||||
role="owner",
|
||||
current=True,
|
||||
)
|
||||
db_session_with_containers.add(tenant_join)
|
||||
db_session_with_containers.flush()
|
||||
|
||||
app = App(
|
||||
tenant_id=tenant.id,
|
||||
name=f"App {uuid4()}",
|
||||
description="",
|
||||
mode="chat",
|
||||
icon_type="emoji",
|
||||
icon="bot",
|
||||
icon_background="#FFFFFF",
|
||||
enable_site=False,
|
||||
enable_api=True,
|
||||
api_rpm=100,
|
||||
api_rph=100,
|
||||
is_demo=False,
|
||||
is_public=False,
|
||||
is_universal=False,
|
||||
created_by=account.id,
|
||||
updated_by=account.id,
|
||||
)
|
||||
db_session_with_containers.add(app)
|
||||
db_session_with_containers.commit()
|
||||
|
||||
return app, account
|
||||
|
||||
@staticmethod
|
||||
def create_end_user(db_session_with_containers, app: App):
|
||||
end_user = EndUser(
|
||||
tenant_id=app.tenant_id,
|
||||
app_id=app.id,
|
||||
type=InvokeFrom.SERVICE_API.value,
|
||||
external_user_id=f"external-{uuid4()}",
|
||||
name=f"End User {uuid4()}",
|
||||
is_anonymous=False,
|
||||
session_id=f"session-{uuid4()}",
|
||||
)
|
||||
db_session_with_containers.add(end_user)
|
||||
db_session_with_containers.commit()
|
||||
return end_user
|
||||
|
||||
@staticmethod
|
||||
def create_conversation(
|
||||
db_session_with_containers,
|
||||
app: App,
|
||||
user: Account | EndUser,
|
||||
*,
|
||||
name: str | None = None,
|
||||
invoke_from: InvokeFrom = InvokeFrom.WEB_APP,
|
||||
created_at: datetime | None = None,
|
||||
updated_at: datetime | None = None,
|
||||
) -> Conversation:
|
||||
conversation = Conversation(
|
||||
app_id=app.id,
|
||||
app_model_config_id=None,
|
||||
model_provider=None,
|
||||
model_id="",
|
||||
override_model_configs=None,
|
||||
mode=app.mode,
|
||||
name=name or f"Conversation {uuid4()}",
|
||||
summary="",
|
||||
inputs={},
|
||||
introduction="",
|
||||
system_instruction="",
|
||||
system_instruction_tokens=0,
|
||||
status="normal",
|
||||
invoke_from=invoke_from.value,
|
||||
from_source=ConversationFromSource.API if isinstance(user, EndUser) else ConversationFromSource.CONSOLE,
|
||||
from_end_user_id=user.id if isinstance(user, EndUser) else None,
|
||||
from_account_id=user.id if isinstance(user, Account) else None,
|
||||
dialogue_count=0,
|
||||
is_deleted=False,
|
||||
)
|
||||
conversation.inputs = {}
|
||||
if created_at is not None:
|
||||
conversation.created_at = created_at
|
||||
if updated_at is not None:
|
||||
conversation.updated_at = updated_at
|
||||
|
||||
db_session_with_containers.add(conversation)
|
||||
db_session_with_containers.commit()
|
||||
return conversation
|
||||
|
||||
@staticmethod
|
||||
def create_variable(
|
||||
db_session_with_containers,
|
||||
*,
|
||||
app: App,
|
||||
conversation: Conversation,
|
||||
variable: StringVariable | FloatVariable | IntegerVariable,
|
||||
created_at: datetime | None = None,
|
||||
) -> ConversationVariable:
|
||||
row = ConversationVariable.from_variable(app_id=app.id, conversation_id=conversation.id, variable=variable)
|
||||
if created_at is not None:
|
||||
row.created_at = created_at
|
||||
row.updated_at = created_at
|
||||
|
||||
db_session_with_containers.add(row)
|
||||
db_session_with_containers.commit()
|
||||
return row
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def real_conversation_service_session_factory(flask_app_with_containers):
|
||||
del flask_app_with_containers
|
||||
real_session_maker = sessionmaker(bind=db.engine, expire_on_commit=False)
|
||||
|
||||
with (
|
||||
patch("services.conversation_service.session_factory.create_session", side_effect=lambda: real_session_maker()),
|
||||
patch("services.conversation_service.session_factory.get_session_maker", return_value=real_session_maker),
|
||||
):
|
||||
yield
|
||||
|
||||
|
||||
class TestConversationServiceVariables:
|
||||
def test_get_conversational_variable_success(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
older_time = datetime(2024, 1, 1, 12, 0, 0)
|
||||
newer_time = older_time + timedelta(minutes=5)
|
||||
|
||||
first_variable = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="topic", value="billing"),
|
||||
created_at=older_time,
|
||||
)
|
||||
second_variable = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="priority", value="high"),
|
||||
created_at=newer_time,
|
||||
)
|
||||
|
||||
result = ConversationService.get_conversational_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
user=account,
|
||||
limit=10,
|
||||
last_id=None,
|
||||
)
|
||||
|
||||
assert [item["id"] for item in result.data] == [first_variable.id, second_variable.id]
|
||||
assert [item["name"] for item in result.data] == ["topic", "priority"]
|
||||
assert result.limit == 10
|
||||
assert result.has_more is False
|
||||
|
||||
def test_get_conversational_variable_with_last_id(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
base_time = datetime(2024, 1, 1, 9, 0, 0)
|
||||
|
||||
first_variable = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="topic", value="billing"),
|
||||
created_at=base_time,
|
||||
)
|
||||
second_variable = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="priority", value="high"),
|
||||
created_at=base_time + timedelta(minutes=1),
|
||||
)
|
||||
third_variable = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="owner", value="alice"),
|
||||
created_at=base_time + timedelta(minutes=2),
|
||||
)
|
||||
|
||||
result = ConversationService.get_conversational_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
user=account,
|
||||
limit=10,
|
||||
last_id=first_variable.id,
|
||||
)
|
||||
|
||||
assert [item["id"] for item in result.data] == [second_variable.id, third_variable.id]
|
||||
assert result.has_more is False
|
||||
|
||||
def test_get_conversational_variable_last_id_not_found_raises_error(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
|
||||
with pytest.raises(ConversationVariableNotExistsError):
|
||||
ConversationService.get_conversational_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
user=account,
|
||||
limit=10,
|
||||
last_id=str(uuid4()),
|
||||
)
|
||||
|
||||
def test_get_conversational_variable_sets_has_more(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
|
||||
for index in range(3):
|
||||
factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name=f"var_{index}", value=f"value_{index}"),
|
||||
created_at=datetime(2024, 1, 1, 10, 0, index),
|
||||
)
|
||||
|
||||
result = ConversationService.get_conversational_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
user=account,
|
||||
limit=2,
|
||||
last_id=None,
|
||||
)
|
||||
|
||||
assert len(result.data) == 2
|
||||
assert result.has_more is True
|
||||
|
||||
def test_update_conversation_variable_success(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
existing = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=StringVariable(id=str(uuid4()), name="topic", value="billing"),
|
||||
)
|
||||
updated_at = datetime(2024, 1, 1, 15, 0, 0)
|
||||
|
||||
with patch("services.conversation_service.naive_utc_now", return_value=updated_at):
|
||||
result = ConversationService.update_conversation_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
variable_id=existing.id,
|
||||
user=account,
|
||||
new_value="support",
|
||||
)
|
||||
|
||||
db_session_with_containers.expire_all()
|
||||
persisted = db_session_with_containers.get(ConversationVariable, (existing.id, conversation.id))
|
||||
|
||||
assert persisted is not None
|
||||
assert persisted.to_variable().value == "support"
|
||||
assert result["id"] == existing.id
|
||||
assert result["value"] == "support"
|
||||
assert result["updated_at"] == updated_at
|
||||
|
||||
def test_update_conversation_variable_not_found_raises_error(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
|
||||
with pytest.raises(ConversationVariableNotExistsError):
|
||||
ConversationService.update_conversation_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
variable_id=str(uuid4()),
|
||||
user=account,
|
||||
new_value="support",
|
||||
)
|
||||
|
||||
def test_update_conversation_variable_type_mismatch_raises_error(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
existing = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=FloatVariable(id=str(uuid4()), name="score", value=1.5),
|
||||
)
|
||||
|
||||
with pytest.raises(ConversationVariableTypeMismatchError, match="expects float"):
|
||||
ConversationService.update_conversation_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
variable_id=existing.id,
|
||||
user=account,
|
||||
new_value="wrong-type",
|
||||
)
|
||||
|
||||
def test_update_conversation_variable_integer_number_compatibility(
|
||||
self, db_session_with_containers, real_conversation_service_session_factory
|
||||
):
|
||||
del real_conversation_service_session_factory
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
conversation = factory.create_conversation(db_session_with_containers, app, account)
|
||||
existing = factory.create_variable(
|
||||
db_session_with_containers,
|
||||
app=app,
|
||||
conversation=conversation,
|
||||
variable=IntegerVariable(id=str(uuid4()), name="attempts", value=1),
|
||||
)
|
||||
|
||||
result = ConversationService.update_conversation_variable(
|
||||
app_model=app,
|
||||
conversation_id=conversation.id,
|
||||
variable_id=existing.id,
|
||||
user=account,
|
||||
new_value=42,
|
||||
)
|
||||
|
||||
db_session_with_containers.expire_all()
|
||||
persisted = db_session_with_containers.get(ConversationVariable, (existing.id, conversation.id))
|
||||
|
||||
assert persisted is not None
|
||||
assert persisted.to_variable().value == 42
|
||||
assert result["value"] == 42
|
||||
|
||||
|
||||
class TestConversationServicePaginationWithContainers:
|
||||
def test_pagination_by_last_id_raises_error_when_last_id_missing(self, db_session_with_containers):
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
|
||||
with pytest.raises(LastConversationNotExistsError):
|
||||
ConversationService.pagination_by_last_id(
|
||||
session=db_session_with_containers,
|
||||
app_model=app,
|
||||
user=account,
|
||||
last_id=str(uuid4()),
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
def test_pagination_by_last_id_with_default_desc_updated_at(self, db_session_with_containers):
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
base_time = datetime(2024, 1, 1, 8, 0, 0)
|
||||
newest = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
account,
|
||||
name="Newest",
|
||||
updated_at=base_time + timedelta(minutes=2),
|
||||
)
|
||||
middle = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
account,
|
||||
name="Middle",
|
||||
updated_at=base_time + timedelta(minutes=1),
|
||||
)
|
||||
oldest = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
account,
|
||||
name="Oldest",
|
||||
updated_at=base_time,
|
||||
)
|
||||
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=db_session_with_containers,
|
||||
app_model=app,
|
||||
user=account,
|
||||
last_id=middle.id,
|
||||
limit=10,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
assert newest.id != middle.id
|
||||
assert [conversation.id for conversation in result.data] == [oldest.id]
|
||||
|
||||
def test_pagination_by_last_id_with_name_sort(self, db_session_with_containers):
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
alpha = factory.create_conversation(db_session_with_containers, app, account, name="Alpha")
|
||||
beta = factory.create_conversation(db_session_with_containers, app, account, name="Beta")
|
||||
gamma = factory.create_conversation(db_session_with_containers, app, account, name="Gamma")
|
||||
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=db_session_with_containers,
|
||||
app_model=app,
|
||||
user=account,
|
||||
last_id=beta.id,
|
||||
limit=10,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
sort_by="name",
|
||||
)
|
||||
|
||||
assert alpha.id != beta.id
|
||||
assert [conversation.id for conversation in result.data] == [gamma.id]
|
||||
|
||||
def test_pagination_filters_to_end_user_api_source(self, db_session_with_containers):
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
end_user = factory.create_end_user(db_session_with_containers, app)
|
||||
account_conversation = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
account,
|
||||
name="Console Conversation",
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
end_user_conversation = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
end_user,
|
||||
name="API Conversation",
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
)
|
||||
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=db_session_with_containers,
|
||||
app_model=app,
|
||||
user=end_user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
)
|
||||
|
||||
assert account_conversation.id != end_user_conversation.id
|
||||
assert [conversation.id for conversation in result.data] == [end_user_conversation.id]
|
||||
|
||||
def test_pagination_filters_to_account_console_source(self, db_session_with_containers):
|
||||
factory = ConversationServiceVariableIntegrationFactory
|
||||
app, account = factory.create_app_and_account(db_session_with_containers)
|
||||
end_user = factory.create_end_user(db_session_with_containers, app)
|
||||
account_conversation = factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
account,
|
||||
name="Console Conversation",
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
factory.create_conversation(
|
||||
db_session_with_containers,
|
||||
app,
|
||||
end_user,
|
||||
name="API Conversation",
|
||||
invoke_from=InvokeFrom.SERVICE_API,
|
||||
)
|
||||
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=db_session_with_containers,
|
||||
app_model=app,
|
||||
user=account,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
assert [conversation.id for conversation in result.data] == [account_conversation.id]
|
||||
@ -6,26 +6,15 @@ Tests are organized by functionality and include edge cases, error handling,
|
||||
and both positive and negative test scenarios.
|
||||
"""
|
||||
|
||||
from datetime import timedelta
|
||||
from unittest.mock import MagicMock, Mock, create_autospec, patch
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import asc, desc
|
||||
|
||||
from core.app.entities.app_invoke_entities import InvokeFrom
|
||||
from libs.datetime_utils import naive_utc_now
|
||||
from libs.infinite_scroll_pagination import InfiniteScrollPagination
|
||||
from models import Account, ConversationVariable
|
||||
from models.enums import ConversationFromSource
|
||||
from models.model import App, Conversation, EndUser, Message
|
||||
from services.conversation_service import ConversationService
|
||||
from services.errors.conversation import (
|
||||
ConversationNotExistsError,
|
||||
ConversationVariableNotExistsError,
|
||||
ConversationVariableTypeMismatchError,
|
||||
LastConversationNotExistsError,
|
||||
)
|
||||
from services.errors.message import MessageNotExistsError
|
||||
|
||||
|
||||
class ConversationServiceTestDataFactory:
|
||||
@ -338,330 +327,9 @@ class TestConversationServiceHelpers:
|
||||
assert condition is not None
|
||||
|
||||
|
||||
class TestConversationServiceGetConversation:
|
||||
"""Test conversation retrieval operations."""
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
def test_get_conversation_success_with_account(self, mock_db_session):
|
||||
"""
|
||||
Test successful conversation retrieval with account user.
|
||||
|
||||
Should return conversation when found with proper filters.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock(
|
||||
from_account_id=user.id, from_source=ConversationFromSource.CONSOLE
|
||||
)
|
||||
|
||||
mock_db_session.scalar.return_value = conversation
|
||||
|
||||
# Act
|
||||
result = ConversationService.get_conversation(app_model, "conv-123", user)
|
||||
|
||||
# Assert
|
||||
assert result == conversation
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
def test_get_conversation_success_with_end_user(self, mock_db_session):
|
||||
"""
|
||||
Test successful conversation retrieval with end user.
|
||||
|
||||
Should return conversation when found with proper filters for API user.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_end_user_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock(
|
||||
from_end_user_id=user.id, from_source=ConversationFromSource.API
|
||||
)
|
||||
|
||||
mock_db_session.scalar.return_value = conversation
|
||||
|
||||
# Act
|
||||
result = ConversationService.get_conversation(app_model, "conv-123", user)
|
||||
|
||||
# Assert
|
||||
assert result == conversation
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
def test_get_conversation_not_found_raises_error(self, mock_db_session):
|
||||
"""
|
||||
Test that get_conversation raises error when conversation not found.
|
||||
|
||||
Should raise ConversationNotExistsError when no matching conversation found.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ConversationNotExistsError):
|
||||
ConversationService.get_conversation(app_model, "conv-123", user)
|
||||
|
||||
|
||||
class TestConversationServiceRename:
|
||||
"""Test conversation rename operations."""
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_rename_with_manual_name(self, mock_get_conversation, mock_db_session):
|
||||
"""
|
||||
Test renaming conversation with manual name.
|
||||
|
||||
Should update conversation name and timestamp when auto_generate is False.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Act
|
||||
result = ConversationService.rename(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
user=user,
|
||||
name="New Name",
|
||||
auto_generate=False,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result == conversation
|
||||
assert conversation.name == "New Name"
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
|
||||
class TestConversationServiceAutoGenerateName:
|
||||
"""Test conversation auto-name generation operations."""
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
@patch("services.conversation_service.LLMGenerator")
|
||||
def test_auto_generate_name_success(self, mock_llm_generator, mock_db_session):
|
||||
"""
|
||||
Test successful auto-generation of conversation name.
|
||||
|
||||
Should generate name using LLMGenerator and update conversation.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
message = ConversationServiceTestDataFactory.create_message_mock(
|
||||
conversation_id=conversation.id, app_id=app_model.id
|
||||
)
|
||||
|
||||
# Mock database query to return message
|
||||
mock_db_session.scalar.return_value = message
|
||||
|
||||
# Mock LLM generator
|
||||
mock_llm_generator.generate_conversation_name.return_value = "Generated Name"
|
||||
|
||||
# Act
|
||||
result = ConversationService.auto_generate_name(app_model, conversation)
|
||||
|
||||
# Assert
|
||||
assert result == conversation
|
||||
assert conversation.name == "Generated Name"
|
||||
mock_llm_generator.generate_conversation_name.assert_called_once_with(
|
||||
app_model.tenant_id, message.query, conversation.id, app_model.id
|
||||
)
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
def test_auto_generate_name_no_message_raises_error(self, mock_db_session):
|
||||
"""
|
||||
Test auto-generation fails when no message found.
|
||||
|
||||
Should raise MessageNotExistsError when conversation has no messages.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
# Mock database query to return None
|
||||
mock_db_session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(MessageNotExistsError):
|
||||
ConversationService.auto_generate_name(app_model, conversation)
|
||||
|
||||
@patch("services.conversation_service.db.session")
|
||||
@patch("services.conversation_service.LLMGenerator")
|
||||
def test_auto_generate_name_handles_llm_exception(self, mock_llm_generator, mock_db_session):
|
||||
"""
|
||||
Test auto-generation handles LLM generator exceptions gracefully.
|
||||
|
||||
Should continue without name when LLMGenerator fails.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
message = ConversationServiceTestDataFactory.create_message_mock(
|
||||
conversation_id=conversation.id, app_id=app_model.id
|
||||
)
|
||||
|
||||
# Mock database query to return message
|
||||
mock_db_session.scalar.return_value = message
|
||||
|
||||
# Mock LLM generator to raise exception
|
||||
mock_llm_generator.generate_conversation_name.side_effect = Exception("LLM Error")
|
||||
|
||||
# Act
|
||||
result = ConversationService.auto_generate_name(app_model, conversation)
|
||||
|
||||
# Assert
|
||||
assert result == conversation
|
||||
# Name should remain unchanged due to exception
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
|
||||
class TestConversationServiceDelete:
|
||||
"""Test conversation deletion operations."""
|
||||
|
||||
@patch("services.conversation_service.delete_conversation_related_data")
|
||||
@patch("services.conversation_service.db.session")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_delete_success(self, mock_get_conversation, mock_db_session, mock_delete_task):
|
||||
"""
|
||||
Test successful conversation deletion.
|
||||
|
||||
Should delete conversation and schedule cleanup task.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock(name="Test App")
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Act
|
||||
ConversationService.delete(app_model, "conv-123", user)
|
||||
|
||||
# Assert
|
||||
mock_db_session.delete.assert_called_once_with(conversation)
|
||||
mock_db_session.commit.assert_called_once()
|
||||
mock_delete_task.delay.assert_called_once_with(conversation.id)
|
||||
|
||||
|
||||
class TestConversationServiceConversationalVariable:
|
||||
"""Test conversational variable operations."""
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_get_conversational_variable_success(self, mock_get_conversation, mock_session_factory):
|
||||
"""
|
||||
Test successful retrieval of conversational variables.
|
||||
|
||||
Should return paginated list of variables for conversation.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session and variables
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
variable1 = ConversationServiceTestDataFactory.create_conversation_variable_mock()
|
||||
variable2 = ConversationServiceTestDataFactory.create_conversation_variable_mock(variable_id="var-456")
|
||||
|
||||
mock_session.scalars.return_value.all.return_value = [variable1, variable2]
|
||||
|
||||
# Act
|
||||
result = ConversationService.get_conversational_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
user=user,
|
||||
limit=10,
|
||||
last_id=None,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
assert len(result.data) == 2
|
||||
assert result.limit == 10
|
||||
assert result.has_more is False
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_get_conversational_variable_with_last_id(self, mock_get_conversation, mock_session_factory):
|
||||
"""
|
||||
Test retrieval of variables with last_id pagination.
|
||||
|
||||
Should filter variables created after last_id.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session and variables
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
last_variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(
|
||||
created_at=naive_utc_now() - timedelta(hours=1)
|
||||
)
|
||||
variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(created_at=naive_utc_now())
|
||||
|
||||
mock_session.scalar.return_value = last_variable
|
||||
mock_session.scalars.return_value.all.return_value = [variable]
|
||||
|
||||
# Act
|
||||
result = ConversationService.get_conversational_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
user=user,
|
||||
limit=10,
|
||||
last_id="var-123",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
assert len(result.data) == 1
|
||||
assert result.limit == 10
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_get_conversational_variable_last_id_not_found_raises_error(
|
||||
self, mock_get_conversation, mock_session_factory
|
||||
):
|
||||
"""
|
||||
Test that invalid last_id raises ConversationVariableNotExistsError.
|
||||
|
||||
Should raise error when last_id doesn't exist.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
mock_session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ConversationVariableNotExistsError):
|
||||
ConversationService.get_conversational_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
user=user,
|
||||
limit=10,
|
||||
last_id="invalid-id",
|
||||
)
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
@patch("services.conversation_service.dify_config")
|
||||
@ -698,466 +366,3 @@ class TestConversationServiceConversationalVariable:
|
||||
|
||||
# Assert - JSON filter should be applied
|
||||
assert mock_session.scalars.called
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
@patch("services.conversation_service.dify_config")
|
||||
def test_get_conversational_variable_with_name_filter_postgresql(
|
||||
self, mock_config, mock_get_conversation, mock_session_factory
|
||||
):
|
||||
"""
|
||||
Test variable filtering by name for PostgreSQL databases.
|
||||
|
||||
Should apply JSON extraction filter for variable names.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
mock_config.DB_TYPE = "postgresql"
|
||||
|
||||
# Mock session
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
mock_session.scalars.return_value.all.return_value = []
|
||||
|
||||
# Act
|
||||
ConversationService.get_conversational_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
user=user,
|
||||
limit=10,
|
||||
last_id=None,
|
||||
variable_name="test_var",
|
||||
)
|
||||
|
||||
# Assert - JSON filter should be applied
|
||||
assert mock_session.scalars.called
|
||||
|
||||
|
||||
class TestConversationServiceUpdateVariable:
|
||||
"""Test conversation variable update operations."""
|
||||
|
||||
@patch("services.conversation_service.variable_factory")
|
||||
@patch("services.conversation_service.ConversationVariableUpdater")
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_update_conversation_variable_success(
|
||||
self, mock_get_conversation, mock_session_factory, mock_updater_class, mock_variable_factory
|
||||
):
|
||||
"""
|
||||
Test successful update of conversation variable.
|
||||
|
||||
Should update variable value and return updated data.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session and existing variable
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
existing_variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(value_type="string")
|
||||
mock_session.scalar.return_value = existing_variable
|
||||
|
||||
# Mock variable factory and updater
|
||||
updated_variable = Mock()
|
||||
updated_variable.model_dump.return_value = {"id": "var-123", "name": "test_var", "value": "new_value"}
|
||||
mock_variable_factory.build_conversation_variable_from_mapping.return_value = updated_variable
|
||||
|
||||
mock_updater = MagicMock()
|
||||
mock_updater_class.return_value = mock_updater
|
||||
|
||||
# Act
|
||||
result = ConversationService.update_conversation_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
variable_id="var-123",
|
||||
user=user,
|
||||
new_value="new_value",
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result["id"] == "var-123"
|
||||
assert result["value"] == "new_value"
|
||||
mock_updater.update.assert_called_once_with("conv-123", updated_variable)
|
||||
mock_updater.flush.assert_called_once()
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_update_conversation_variable_not_found_raises_error(self, mock_get_conversation, mock_session_factory):
|
||||
"""
|
||||
Test update fails when variable doesn't exist.
|
||||
|
||||
Should raise ConversationVariableNotExistsError.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
mock_session.scalar.return_value = None
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(ConversationVariableNotExistsError):
|
||||
ConversationService.update_conversation_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
variable_id="invalid-id",
|
||||
user=user,
|
||||
new_value="new_value",
|
||||
)
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_update_conversation_variable_type_mismatch_raises_error(self, mock_get_conversation, mock_session_factory):
|
||||
"""
|
||||
Test update fails when value type doesn't match expected type.
|
||||
|
||||
Should raise ConversationVariableTypeMismatchError.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session and existing variable
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
existing_variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(value_type="number")
|
||||
mock_session.scalar.return_value = existing_variable
|
||||
|
||||
# Act & Assert - Try to set string value for number variable
|
||||
with pytest.raises(ConversationVariableTypeMismatchError):
|
||||
ConversationService.update_conversation_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
variable_id="var-123",
|
||||
user=user,
|
||||
new_value="string_value", # Wrong type
|
||||
)
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
@patch("services.conversation_service.ConversationService.get_conversation")
|
||||
def test_update_conversation_variable_integer_number_compatibility(
|
||||
self, mock_get_conversation, mock_session_factory
|
||||
):
|
||||
"""
|
||||
Test that integer type accepts number values.
|
||||
|
||||
Should allow number values for integer type variables.
|
||||
"""
|
||||
# Arrange
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
|
||||
mock_get_conversation.return_value = conversation
|
||||
|
||||
# Mock session and existing variable
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
existing_variable = ConversationServiceTestDataFactory.create_conversation_variable_mock(value_type="integer")
|
||||
mock_session.scalar.return_value = existing_variable
|
||||
|
||||
# Mock variable factory and updater
|
||||
updated_variable = Mock()
|
||||
updated_variable.model_dump.return_value = {"id": "var-123", "name": "test_var", "value": 42}
|
||||
|
||||
with (
|
||||
patch("services.conversation_service.variable_factory") as mock_variable_factory,
|
||||
patch("services.conversation_service.ConversationVariableUpdater") as mock_updater_class,
|
||||
):
|
||||
mock_variable_factory.build_conversation_variable_from_mapping.return_value = updated_variable
|
||||
mock_updater = MagicMock()
|
||||
mock_updater_class.return_value = mock_updater
|
||||
|
||||
# Act
|
||||
result = ConversationService.update_conversation_variable(
|
||||
app_model=app_model,
|
||||
conversation_id="conv-123",
|
||||
variable_id="var-123",
|
||||
user=user,
|
||||
new_value=42, # Number value for integer type
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert result["value"] == 42
|
||||
mock_updater.update.assert_called_once()
|
||||
|
||||
|
||||
class TestConversationServicePaginationAdvanced:
|
||||
"""Advanced pagination tests for ConversationService."""
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_by_last_id_with_last_id_not_found(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination with invalid last_id raises error.
|
||||
|
||||
Should raise LastConversationNotExistsError when last_id doesn't exist.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
mock_session.scalar.return_value = None
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(LastConversationNotExistsError):
|
||||
ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id="invalid-id",
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_by_last_id_with_exclude_ids(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination with exclude_ids filter.
|
||||
|
||||
Should exclude specified conversation IDs from results.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
mock_session.scalars.return_value.all.return_value = [conversation]
|
||||
mock_session.scalar.return_value = conversation
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
exclude_ids=["excluded-123"],
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
assert len(result.data) == 1
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_by_last_id_has_more_detection(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination has_more detection logic.
|
||||
|
||||
Should set has_more=True when there are more results beyond limit.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
# Return exactly limit items to trigger has_more check
|
||||
conversations = [
|
||||
ConversationServiceTestDataFactory.create_conversation_mock(conversation_id=f"conv-{i}") for i in range(20)
|
||||
]
|
||||
mock_session.scalars.return_value.all.return_value = conversations
|
||||
mock_session.scalar.return_value = conversations[-1]
|
||||
|
||||
# Mock count query to return > 0
|
||||
mock_session.scalar.return_value = 5 # Additional items exist
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
assert result.has_more is True
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_by_last_id_with_different_sort_by(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination with different sort fields.
|
||||
|
||||
Should handle various sort_by parameters correctly.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock()
|
||||
mock_session.scalars.return_value.all.return_value = [conversation]
|
||||
mock_session.scalar.return_value = conversation
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Test different sort fields
|
||||
sort_fields = ["created_at", "-updated_at", "name", "-status"]
|
||||
|
||||
for sort_by in sort_fields:
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
sort_by=sort_by,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
|
||||
|
||||
class TestConversationServiceEdgeCases:
|
||||
"""Test edge cases and error scenarios."""
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_with_end_user_api_source(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination correctly handles EndUser with API source.
|
||||
|
||||
Should use 'api' as from_source for EndUser instances.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock(
|
||||
from_source=ConversationFromSource.API, from_end_user_id="user-123"
|
||||
)
|
||||
mock_session.scalars.return_value.all.return_value = [conversation]
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_end_user_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
|
||||
@patch("services.conversation_service.session_factory")
|
||||
def test_pagination_with_account_console_source(self, mock_session_factory):
|
||||
"""
|
||||
Test pagination correctly handles Account with console source.
|
||||
|
||||
Should use 'console' as from_source for Account instances.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session_factory.create_session.return_value.__enter__.return_value = mock_session
|
||||
|
||||
conversation = ConversationServiceTestDataFactory.create_conversation_mock(
|
||||
from_source=ConversationFromSource.CONSOLE, from_account_id="account-123"
|
||||
)
|
||||
mock_session.scalars.return_value.all.return_value = [conversation]
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
|
||||
def test_pagination_with_include_ids_filter(self):
|
||||
"""
|
||||
Test pagination with include_ids filter.
|
||||
|
||||
Should only return conversations with IDs in include_ids list.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session.scalars.return_value.all.return_value = []
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
include_ids=["conv-123", "conv-456"],
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
# Verify that include_ids filter was applied
|
||||
assert mock_session.scalars.called
|
||||
|
||||
def test_pagination_with_empty_exclude_ids(self):
|
||||
"""
|
||||
Test pagination with empty exclude_ids list.
|
||||
|
||||
Should handle empty exclude_ids gracefully.
|
||||
"""
|
||||
# Arrange
|
||||
mock_session = MagicMock()
|
||||
mock_session.scalars.return_value.all.return_value = []
|
||||
|
||||
app_model = ConversationServiceTestDataFactory.create_app_mock()
|
||||
user = ConversationServiceTestDataFactory.create_account_mock()
|
||||
|
||||
# Act
|
||||
result = ConversationService.pagination_by_last_id(
|
||||
session=mock_session,
|
||||
app_model=app_model,
|
||||
user=user,
|
||||
last_id=None,
|
||||
limit=20,
|
||||
invoke_from=InvokeFrom.WEB_APP,
|
||||
exclude_ids=[],
|
||||
)
|
||||
|
||||
# Assert
|
||||
assert isinstance(result, InfiniteScrollPagination)
|
||||
assert result.has_more is False
|
||||
|
||||
Loading…
Reference in New Issue
Block a user