mirror of
https://github.com/langgenius/dify.git
synced 2026-03-14 03:18:36 +08:00
842 lines
31 KiB
Python
842 lines
31 KiB
Python
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
|
|
from core.app.entities.app_invoke_entities import InvokeFrom
|
|
from models.model import App, DefaultEndUserSessionID, EndUser
|
|
from services.end_user_service import EndUserService
|
|
|
|
|
|
class TestEndUserServiceFactory:
|
|
"""Factory class for creating test data and mock objects for end user service tests."""
|
|
|
|
@staticmethod
|
|
def create_app_mock(
|
|
app_id: str = "app-123",
|
|
tenant_id: str = "tenant-456",
|
|
name: str = "Test App",
|
|
) -> MagicMock:
|
|
"""Create a mock App object."""
|
|
app = MagicMock(spec=App)
|
|
app.id = app_id
|
|
app.tenant_id = tenant_id
|
|
app.name = name
|
|
return app
|
|
|
|
@staticmethod
|
|
def create_end_user_mock(
|
|
user_id: str = "user-789",
|
|
tenant_id: str = "tenant-456",
|
|
app_id: str = "app-123",
|
|
session_id: str = "session-001",
|
|
type: InvokeFrom = InvokeFrom.SERVICE_API,
|
|
is_anonymous: bool = False,
|
|
) -> MagicMock:
|
|
"""Create a mock EndUser object."""
|
|
end_user = MagicMock(spec=EndUser)
|
|
end_user.id = user_id
|
|
end_user.tenant_id = tenant_id
|
|
end_user.app_id = app_id
|
|
end_user.session_id = session_id
|
|
end_user.type = type
|
|
end_user.is_anonymous = is_anonymous
|
|
end_user.external_user_id = session_id
|
|
return end_user
|
|
|
|
|
|
class TestEndUserServiceGetEndUserById:
|
|
"""Unit tests for EndUserService.get_end_user_by_id method."""
|
|
|
|
@pytest.fixture
|
|
def factory(self):
|
|
"""Provide test data factory."""
|
|
return TestEndUserServiceFactory()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_get_end_user_by_id_success(self, mock_db, mock_session_class, factory):
|
|
"""Test successful retrieval of end user by ID."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
end_user_id = "user-789"
|
|
|
|
mock_end_user = factory.create_end_user_mock(user_id=end_user_id, tenant_id=tenant_id, app_id=app_id)
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.first.return_value = mock_end_user
|
|
|
|
# Act
|
|
result = EndUserService.get_end_user_by_id(tenant_id=tenant_id, app_id=app_id, end_user_id=end_user_id)
|
|
|
|
# Assert
|
|
assert result == mock_end_user
|
|
mock_session.query.assert_called_once_with(EndUser)
|
|
mock_query.where.assert_called_once()
|
|
mock_query.first.assert_called_once()
|
|
mock_context.__enter__.assert_called_once()
|
|
mock_context.__exit__.assert_called_once()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_get_end_user_by_id_not_found(self, mock_db, mock_session_class):
|
|
"""Test retrieval of non-existent end user returns None."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
end_user_id = "user-789"
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.first.return_value = None
|
|
|
|
# Act
|
|
result = EndUserService.get_end_user_by_id(tenant_id=tenant_id, app_id=app_id, end_user_id=end_user_id)
|
|
|
|
# Assert
|
|
assert result is None
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_get_end_user_by_id_query_parameters(self, mock_db, mock_session_class):
|
|
"""Test that query parameters are correctly applied."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
end_user_id = "user-789"
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.first.return_value = None
|
|
|
|
# Act
|
|
EndUserService.get_end_user_by_id(tenant_id=tenant_id, app_id=app_id, end_user_id=end_user_id)
|
|
|
|
# Assert
|
|
# Verify the where clause was called with the correct conditions
|
|
call_args = mock_query.where.call_args[0]
|
|
assert len(call_args) == 3
|
|
# Check that the conditions match the expected filters
|
|
# (We can't easily test the exact conditions without importing SQLAlchemy)
|
|
|
|
|
|
class TestEndUserServiceGetOrCreateEndUser:
|
|
"""Unit tests for EndUserService.get_or_create_end_user method."""
|
|
|
|
@pytest.fixture
|
|
def factory(self):
|
|
"""Provide test data factory."""
|
|
return TestEndUserServiceFactory()
|
|
|
|
@patch("services.end_user_service.EndUserService.get_or_create_end_user_by_type")
|
|
def test_get_or_create_end_user_with_user_id(self, mock_get_or_create_by_type, factory):
|
|
"""Test get_or_create_end_user with specific user_id."""
|
|
# Arrange
|
|
app_mock = factory.create_app_mock()
|
|
user_id = "user-123"
|
|
expected_end_user = factory.create_end_user_mock()
|
|
mock_get_or_create_by_type.return_value = expected_end_user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user(app_mock, user_id)
|
|
|
|
# Assert
|
|
assert result == expected_end_user
|
|
mock_get_or_create_by_type.assert_called_once_with(
|
|
InvokeFrom.SERVICE_API, app_mock.tenant_id, app_mock.id, user_id
|
|
)
|
|
|
|
@patch("services.end_user_service.EndUserService.get_or_create_end_user_by_type")
|
|
def test_get_or_create_end_user_without_user_id(self, mock_get_or_create_by_type, factory):
|
|
"""Test get_or_create_end_user without user_id (None)."""
|
|
# Arrange
|
|
app_mock = factory.create_app_mock()
|
|
expected_end_user = factory.create_end_user_mock()
|
|
mock_get_or_create_by_type.return_value = expected_end_user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user(app_mock, None)
|
|
|
|
# Assert
|
|
assert result == expected_end_user
|
|
mock_get_or_create_by_type.assert_called_once_with(
|
|
InvokeFrom.SERVICE_API, app_mock.tenant_id, app_mock.id, None
|
|
)
|
|
|
|
|
|
class TestEndUserServiceGetOrCreateEndUserByType:
|
|
"""
|
|
Unit tests for EndUserService.get_or_create_end_user_by_type method.
|
|
|
|
This test suite covers:
|
|
- Creating end users with different InvokeFrom types
|
|
- Type migration for legacy users
|
|
- Query ordering and prioritization
|
|
- Session management
|
|
"""
|
|
|
|
@pytest.fixture
|
|
def factory(self):
|
|
"""Provide test data factory."""
|
|
return TestEndUserServiceFactory()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_new_end_user_with_user_id(self, mock_db, mock_session_class, factory):
|
|
"""Test creating a new end user with specific user_id."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = None # No existing user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user_by_type(
|
|
type=type_enum, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
# Verify new EndUser was created with correct parameters
|
|
mock_session.add.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|
|
added_user = mock_session.add.call_args[0][0]
|
|
assert added_user.tenant_id == tenant_id
|
|
assert added_user.app_id == app_id
|
|
assert added_user.type == type_enum
|
|
assert added_user.session_id == user_id
|
|
assert added_user.external_user_id == user_id
|
|
assert added_user._is_anonymous is False
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_new_end_user_default_session(self, mock_db, mock_session_class, factory):
|
|
"""Test creating a new end user with default session ID."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = None
|
|
type_enum = InvokeFrom.WEB_APP
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = None # No existing user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user_by_type(
|
|
type=type_enum, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
added_user = mock_session.add.call_args[0][0]
|
|
assert added_user.session_id == DefaultEndUserSessionID.DEFAULT_SESSION_ID
|
|
assert added_user.external_user_id == DefaultEndUserSessionID.DEFAULT_SESSION_ID
|
|
assert added_user._is_anonymous is True
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
@patch("services.end_user_service.logger")
|
|
def test_existing_user_same_type(self, mock_logger, mock_db, mock_session_class, factory):
|
|
"""Test retrieving existing user with same type."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
existing_user = factory.create_end_user_mock(
|
|
tenant_id=tenant_id, app_id=app_id, session_id=user_id, type=type_enum
|
|
)
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = existing_user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user_by_type(
|
|
type=type_enum, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert result == existing_user
|
|
mock_session.add.assert_not_called()
|
|
mock_session.commit.assert_not_called()
|
|
mock_logger.info.assert_not_called()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
@patch("services.end_user_service.logger")
|
|
def test_existing_user_different_type_upgrade(self, mock_logger, mock_db, mock_session_class, factory):
|
|
"""Test upgrading existing user with different type."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
old_type = InvokeFrom.WEB_APP
|
|
new_type = InvokeFrom.SERVICE_API
|
|
|
|
existing_user = factory.create_end_user_mock(
|
|
tenant_id=tenant_id, app_id=app_id, session_id=user_id, type=old_type
|
|
)
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = existing_user
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user_by_type(
|
|
type=new_type, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert result == existing_user
|
|
assert existing_user.type == new_type
|
|
mock_session.commit.assert_called_once()
|
|
mock_logger.info.assert_called_once()
|
|
logger_call_args = mock_logger.info.call_args[0]
|
|
assert "Upgrading legacy EndUser" in logger_call_args[0]
|
|
# The old and new types are passed as separate arguments
|
|
assert mock_logger.info.call_args[0][1] == existing_user.id
|
|
assert mock_logger.info.call_args[0][2] == old_type
|
|
assert mock_logger.info.call_args[0][3] == new_type
|
|
assert mock_logger.info.call_args[0][4] == user_id
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_query_ordering_prioritizes_exact_type_match(self, mock_db, mock_session_class, factory):
|
|
"""Test that query ordering prioritizes exact type matches."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
target_type = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = None
|
|
|
|
# Act
|
|
EndUserService.get_or_create_end_user_by_type(
|
|
type=target_type, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
mock_query.order_by.assert_called_once()
|
|
# Verify that case statement is used for ordering
|
|
order_by_call = mock_query.order_by.call_args[0][0]
|
|
# The exact structure depends on SQLAlchemy's case implementation
|
|
# but we can verify it was called
|
|
|
|
# Test 10: Session context manager properly closes
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_session_context_manager_closes(self, mock_db, mock_session_class, factory):
|
|
"""Test that Session context manager is properly used."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = None
|
|
|
|
# Act
|
|
EndUserService.get_or_create_end_user_by_type(
|
|
type=InvokeFrom.SERVICE_API,
|
|
tenant_id=tenant_id,
|
|
app_id=app_id,
|
|
user_id=user_id,
|
|
)
|
|
|
|
# Assert
|
|
# Verify context manager was entered and exited
|
|
mock_context.__enter__.assert_called_once()
|
|
mock_context.__exit__.assert_called_once()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_all_invokefrom_types_supported(self, mock_db, mock_session_class):
|
|
"""Test that all InvokeFrom enum values are supported."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_id = "app-456"
|
|
user_id = "user-789"
|
|
|
|
for invoke_type in InvokeFrom:
|
|
with patch("services.end_user_service.Session") as mock_session_class:
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.order_by.return_value = mock_query
|
|
mock_query.first.return_value = None
|
|
|
|
# Act
|
|
result = EndUserService.get_or_create_end_user_by_type(
|
|
type=invoke_type, tenant_id=tenant_id, app_id=app_id, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
added_user = mock_session.add.call_args[0][0]
|
|
assert added_user.type == invoke_type
|
|
|
|
|
|
class TestEndUserServiceCreateEndUserBatch:
|
|
"""Unit tests for EndUserService.create_end_user_batch method."""
|
|
|
|
@pytest.fixture
|
|
def factory(self):
|
|
"""Provide test data factory."""
|
|
return TestEndUserServiceFactory()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_empty_app_ids(self, mock_db, mock_session_class):
|
|
"""Test batch creation with empty app_ids list."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids: list[str] = []
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert result == {}
|
|
mock_session_class.assert_not_called()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_default_session_id(self, mock_db, mock_session_class):
|
|
"""Test batch creation with empty user_id (uses default session)."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789"]
|
|
user_id = ""
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert len(result) == 2
|
|
for app_id, end_user in result.items():
|
|
assert end_user.session_id == DefaultEndUserSessionID.DEFAULT_SESSION_ID
|
|
assert end_user.external_user_id == DefaultEndUserSessionID.DEFAULT_SESSION_ID
|
|
assert end_user._is_anonymous is True
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_deduplicate_app_ids(self, mock_db, mock_session_class):
|
|
"""Test that duplicate app_ids are deduplicated while preserving order."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789", "app-456", "app-123", "app-789"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
# Should have 3 unique app_ids in original order
|
|
assert len(result) == 3
|
|
assert "app-456" in result
|
|
assert "app-789" in result
|
|
assert "app-123" in result
|
|
|
|
# Verify the order is preserved
|
|
added_users = mock_session.add_all.call_args[0][0]
|
|
assert len(added_users) == 3
|
|
assert added_users[0].app_id == "app-456"
|
|
assert added_users[1].app_id == "app-789"
|
|
assert added_users[2].app_id == "app-123"
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_all_existing_users(self, mock_db, mock_session_class, factory):
|
|
"""Test batch creation when all users already exist."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
existing_user1 = factory.create_end_user_mock(
|
|
tenant_id=tenant_id, app_id="app-456", session_id=user_id, type=type_enum
|
|
)
|
|
existing_user2 = factory.create_end_user_mock(
|
|
tenant_id=tenant_id, app_id="app-789", session_id=user_id, type=type_enum
|
|
)
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [existing_user1, existing_user2]
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert len(result) == 2
|
|
assert result["app-456"] == existing_user1
|
|
assert result["app-789"] == existing_user2
|
|
mock_session.add_all.assert_not_called()
|
|
mock_session.commit.assert_not_called()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_partial_existing_users(self, mock_db, mock_session_class, factory):
|
|
"""Test batch creation with some existing and some new users."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789", "app-123"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
existing_user1 = factory.create_end_user_mock(
|
|
tenant_id=tenant_id, app_id="app-456", session_id=user_id, type=type_enum
|
|
)
|
|
# app-789 and app-123 don't exist
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [existing_user1]
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert len(result) == 3
|
|
assert result["app-456"] == existing_user1
|
|
assert "app-789" in result
|
|
assert "app-123" in result
|
|
|
|
# Should create 2 new users
|
|
mock_session.add_all.assert_called_once()
|
|
added_users = mock_session.add_all.call_args[0][0]
|
|
assert len(added_users) == 2
|
|
|
|
mock_session.commit.assert_called_once()
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_handles_duplicates_in_existing(self, mock_db, mock_session_class, factory):
|
|
"""Test batch creation handles duplicates in existing users gracefully."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
# Simulate duplicate records in database
|
|
existing_user1 = factory.create_end_user_mock(
|
|
user_id="user-1", tenant_id=tenant_id, app_id="app-456", session_id=user_id, type=type_enum
|
|
)
|
|
existing_user2 = factory.create_end_user_mock(
|
|
user_id="user-2", tenant_id=tenant_id, app_id="app-456", session_id=user_id, type=type_enum
|
|
)
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [existing_user1, existing_user2]
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert len(result) == 1
|
|
# Should prefer the first one found
|
|
assert result["app-456"] == existing_user1
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_all_invokefrom_types(self, mock_db, mock_session_class):
|
|
"""Test batch creation with all InvokeFrom types."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456"]
|
|
user_id = "user-789"
|
|
|
|
for invoke_type in InvokeFrom:
|
|
with patch("services.end_user_service.Session") as mock_session_class:
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=invoke_type, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
added_user = mock_session.add_all.call_args[0][0][0]
|
|
assert added_user.type == invoke_type
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_single_app_id(self, mock_db, mock_session_class, factory):
|
|
"""Test batch creation with single app_id."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
result = EndUserService.create_end_user_batch(
|
|
type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id
|
|
)
|
|
|
|
# Assert
|
|
assert len(result) == 1
|
|
assert "app-456" in result
|
|
mock_session.add_all.assert_called_once()
|
|
added_users = mock_session.add_all.call_args[0][0]
|
|
assert len(added_users) == 1
|
|
assert added_users[0].app_id == "app-456"
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_anonymous_vs_authenticated(self, mock_db, mock_session_class):
|
|
"""Test batch creation correctly sets anonymous flag."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789"]
|
|
|
|
# Test with regular user ID
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act - authenticated user
|
|
result = EndUserService.create_end_user_batch(
|
|
type=InvokeFrom.SERVICE_API, tenant_id=tenant_id, app_ids=app_ids, user_id="user-789"
|
|
)
|
|
|
|
# Assert
|
|
added_users = mock_session.add_all.call_args[0][0]
|
|
for user in added_users:
|
|
assert user._is_anonymous is False
|
|
|
|
# Test with default session ID
|
|
mock_session.reset_mock()
|
|
mock_query.reset_mock()
|
|
mock_query.all.return_value = []
|
|
|
|
# Act - anonymous user
|
|
result = EndUserService.create_end_user_batch(
|
|
type=InvokeFrom.SERVICE_API,
|
|
tenant_id=tenant_id,
|
|
app_ids=app_ids,
|
|
user_id=DefaultEndUserSessionID.DEFAULT_SESSION_ID,
|
|
)
|
|
|
|
# Assert
|
|
added_users = mock_session.add_all.call_args[0][0]
|
|
for user in added_users:
|
|
assert user._is_anonymous is True
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_efficient_single_query(self, mock_db, mock_session_class):
|
|
"""Test that batch creation uses efficient single query for existing users."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456", "app-789", "app-123"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
EndUserService.create_end_user_batch(type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id)
|
|
|
|
# Assert
|
|
# Should make exactly one query to check for existing users
|
|
mock_session.query.assert_called_once_with(EndUser)
|
|
mock_query.where.assert_called_once()
|
|
mock_query.all.assert_called_once()
|
|
|
|
# Verify the where clause uses .in_() for app_ids
|
|
where_call = mock_query.where.call_args[0]
|
|
# The exact structure depends on SQLAlchemy implementation
|
|
# but we can verify it was called with the right parameters
|
|
|
|
@patch("services.end_user_service.Session")
|
|
@patch("services.end_user_service.db")
|
|
def test_create_batch_session_context_manager(self, mock_db, mock_session_class):
|
|
"""Test that batch creation properly uses session context manager."""
|
|
# Arrange
|
|
tenant_id = "tenant-123"
|
|
app_ids = ["app-456"]
|
|
user_id = "user-789"
|
|
type_enum = InvokeFrom.SERVICE_API
|
|
|
|
mock_session = MagicMock()
|
|
mock_context = MagicMock()
|
|
mock_context.__enter__.return_value = mock_session
|
|
mock_session_class.return_value = mock_context
|
|
|
|
mock_query = MagicMock()
|
|
mock_session.query.return_value = mock_query
|
|
mock_query.where.return_value = mock_query
|
|
mock_query.all.return_value = [] # No existing users
|
|
|
|
# Act
|
|
EndUserService.create_end_user_batch(type=type_enum, tenant_id=tenant_id, app_ids=app_ids, user_id=user_id)
|
|
|
|
# Assert
|
|
mock_context.__enter__.assert_called_once()
|
|
mock_context.__exit__.assert_called_once()
|
|
mock_session.commit.assert_called_once()
|