mirror of
https://github.com/langgenius/dify.git
synced 2026-03-07 08:35:58 +08:00
test: migrate dataset permission service SQL tests to testcontainers (#32546)
Co-authored-by: KinomotoMio <200703522+KinomotoMio@users.noreply.github.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
@ -0,0 +1,497 @@
|
||||
"""
|
||||
Container-backed integration tests for dataset permission services on the real SQL path.
|
||||
|
||||
This module exercises persisted DatasetPermission rows and dataset permission
|
||||
checks with testcontainers-backed infrastructure instead of database-chain mocks.
|
||||
"""
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from extensions.ext_database import db
|
||||
from models import Account, Tenant, TenantAccountJoin, TenantAccountRole
|
||||
from models.dataset import (
|
||||
Dataset,
|
||||
DatasetPermission,
|
||||
DatasetPermissionEnum,
|
||||
)
|
||||
from services.dataset_service import DatasetPermissionService, DatasetService
|
||||
from services.errors.account import NoPermissionError
|
||||
|
||||
|
||||
class DatasetPermissionTestDataFactory:
|
||||
"""Create persisted entities and request payloads for dataset permission integration tests."""
|
||||
|
||||
@staticmethod
|
||||
def create_account_with_tenant(
|
||||
role: TenantAccountRole = TenantAccountRole.NORMAL,
|
||||
tenant: Tenant | None = None,
|
||||
) -> tuple[Account, Tenant]:
|
||||
"""Create a real account and tenant with specified role."""
|
||||
account = Account(
|
||||
email=f"{uuid4()}@example.com",
|
||||
name=f"user-{uuid4()}",
|
||||
interface_language="en-US",
|
||||
status="active",
|
||||
)
|
||||
if tenant is None:
|
||||
tenant = Tenant(name=f"tenant-{uuid4()}", status="normal")
|
||||
db.session.add_all([account, tenant])
|
||||
else:
|
||||
db.session.add(account)
|
||||
|
||||
db.session.flush()
|
||||
|
||||
join = TenantAccountJoin(
|
||||
tenant_id=tenant.id,
|
||||
account_id=account.id,
|
||||
role=role,
|
||||
current=True,
|
||||
)
|
||||
db.session.add(join)
|
||||
db.session.commit()
|
||||
|
||||
account.current_tenant = tenant
|
||||
return account, tenant
|
||||
|
||||
@staticmethod
|
||||
def create_dataset(
|
||||
tenant_id: str,
|
||||
created_by: str,
|
||||
permission: DatasetPermissionEnum = DatasetPermissionEnum.ONLY_ME,
|
||||
name: str = "Test Dataset",
|
||||
) -> Dataset:
|
||||
"""Create a real dataset with specified attributes."""
|
||||
dataset = Dataset(
|
||||
tenant_id=tenant_id,
|
||||
name=name,
|
||||
description="desc",
|
||||
data_source_type="upload_file",
|
||||
indexing_technique="high_quality",
|
||||
created_by=created_by,
|
||||
permission=permission,
|
||||
provider="vendor",
|
||||
retrieval_model={"top_k": 2},
|
||||
)
|
||||
db.session.add(dataset)
|
||||
db.session.commit()
|
||||
return dataset
|
||||
|
||||
@staticmethod
|
||||
def create_dataset_permission(
|
||||
dataset_id: str,
|
||||
account_id: str,
|
||||
tenant_id: str,
|
||||
has_permission: bool = True,
|
||||
) -> DatasetPermission:
|
||||
"""Create a real DatasetPermission instance."""
|
||||
permission = DatasetPermission(
|
||||
dataset_id=dataset_id,
|
||||
account_id=account_id,
|
||||
tenant_id=tenant_id,
|
||||
has_permission=has_permission,
|
||||
)
|
||||
db.session.add(permission)
|
||||
db.session.commit()
|
||||
return permission
|
||||
|
||||
@staticmethod
|
||||
def build_user_list_payload(user_ids: list[str]) -> list[dict[str, str]]:
|
||||
"""Build the request payload shape used by partial-member list updates."""
|
||||
return [{"user_id": user_id} for user_id in user_ids]
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceGetPartialMemberList:
|
||||
"""Verify partial-member list reads against persisted DatasetPermission rows."""
|
||||
|
||||
def test_get_dataset_partial_member_list_with_members(self, db_session_with_containers):
|
||||
"""
|
||||
Test retrieving partial member list with multiple members.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
user_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
user_3, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
|
||||
expected_account_ids = [user_1.id, user_2.id, user_3.id]
|
||||
for account_id in expected_account_ids:
|
||||
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, account_id, tenant.id)
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
assert set(result) == set(expected_account_ids)
|
||||
assert len(result) == 3
|
||||
|
||||
def test_get_dataset_partial_member_list_with_single_member(self, db_session_with_containers):
|
||||
"""
|
||||
Test retrieving partial member list with single member.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
|
||||
expected_account_ids = [user.id]
|
||||
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
assert set(result) == set(expected_account_ids)
|
||||
assert len(result) == 1
|
||||
|
||||
def test_get_dataset_partial_member_list_empty(self, db_session_with_containers):
|
||||
"""
|
||||
Test retrieving partial member list when no members exist.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
assert result == []
|
||||
assert len(result) == 0
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceUpdatePartialMemberList:
|
||||
"""Verify partial-member list updates against persisted DatasetPermission rows."""
|
||||
|
||||
def test_update_partial_member_list_add_new_members(self, db_session_with_containers):
|
||||
"""
|
||||
Test adding new partial members to a dataset.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
user_list = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, user_list)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert set(result) == {member_1.id, member_2.id}
|
||||
|
||||
def test_update_partial_member_list_replace_existing(self, db_session_with_containers):
|
||||
"""
|
||||
Test replacing existing partial members with new ones.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
old_member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
old_member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
new_member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
new_member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
|
||||
old_users = DatasetPermissionTestDataFactory.build_user_list_payload([old_member_1.id, old_member_2.id])
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, old_users)
|
||||
|
||||
new_users = DatasetPermissionTestDataFactory.build_user_list_payload([new_member_1.id, new_member_2.id])
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, new_users)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert set(result) == {new_member_1.id, new_member_2.id}
|
||||
|
||||
def test_update_partial_member_list_empty_list(self, db_session_with_containers):
|
||||
"""
|
||||
Test updating with empty member list (clearing all members).
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, [])
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert result == []
|
||||
|
||||
def test_update_partial_member_list_database_error_rollback(self, db_session_with_containers):
|
||||
"""
|
||||
Test error handling and rollback on database error.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
existing_member, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
replacement_member, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
DatasetPermissionService.update_partial_member_list(
|
||||
tenant.id,
|
||||
dataset.id,
|
||||
DatasetPermissionTestDataFactory.build_user_list_payload([existing_member.id]),
|
||||
)
|
||||
user_list = DatasetPermissionTestDataFactory.build_user_list_payload([replacement_member.id])
|
||||
rollback_called = {"count": 0}
|
||||
original_rollback = db.session.rollback
|
||||
|
||||
# Act / Assert
|
||||
with pytest.MonkeyPatch.context() as mp:
|
||||
|
||||
def _raise_commit():
|
||||
raise Exception("Database connection error")
|
||||
|
||||
def _rollback_and_mark():
|
||||
rollback_called["count"] += 1
|
||||
original_rollback()
|
||||
|
||||
mp.setattr("services.dataset_service.db.session.commit", _raise_commit)
|
||||
mp.setattr("services.dataset_service.db.session.rollback", _rollback_and_mark)
|
||||
with pytest.raises(Exception, match="Database connection error"):
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, user_list)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert rollback_called["count"] == 1
|
||||
assert result == [existing_member.id]
|
||||
assert db_session_with_containers.query(DatasetPermission).filter_by(dataset_id=dataset.id).count() == 1
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceClearPartialMemberList:
|
||||
"""Verify partial-member clearing against persisted DatasetPermission rows."""
|
||||
|
||||
def test_clear_partial_member_list_success(self, db_session_with_containers):
|
||||
"""
|
||||
Test successful clearing of partial member list.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.clear_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert result == []
|
||||
|
||||
def test_clear_partial_member_list_empty_list(self, db_session_with_containers):
|
||||
"""
|
||||
Test clearing partial member list when no members exist.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.clear_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert result == []
|
||||
|
||||
def test_clear_partial_member_list_database_error_rollback(self, db_session_with_containers):
|
||||
"""
|
||||
Test error handling and rollback on database error.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
member_1, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
member_2, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(tenant.id, owner.id)
|
||||
users = DatasetPermissionTestDataFactory.build_user_list_payload([member_1.id, member_2.id])
|
||||
DatasetPermissionService.update_partial_member_list(tenant.id, dataset.id, users)
|
||||
rollback_called = {"count": 0}
|
||||
original_rollback = db.session.rollback
|
||||
|
||||
# Act / Assert
|
||||
with pytest.MonkeyPatch.context() as mp:
|
||||
|
||||
def _raise_commit():
|
||||
raise Exception("Database connection error")
|
||||
|
||||
def _rollback_and_mark():
|
||||
rollback_called["count"] += 1
|
||||
original_rollback()
|
||||
|
||||
mp.setattr("services.dataset_service.db.session.commit", _raise_commit)
|
||||
mp.setattr("services.dataset_service.db.session.rollback", _rollback_and_mark)
|
||||
with pytest.raises(Exception, match="Database connection error"):
|
||||
DatasetPermissionService.clear_partial_member_list(dataset.id)
|
||||
|
||||
# Assert
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert rollback_called["count"] == 1
|
||||
assert set(result) == {member_1.id, member_2.id}
|
||||
assert db_session_with_containers.query(DatasetPermission).filter_by(dataset_id=dataset.id).count() == 2
|
||||
|
||||
|
||||
class TestDatasetServiceCheckDatasetPermission:
|
||||
"""Verify dataset access checks against persisted partial-member permissions."""
|
||||
|
||||
def test_check_dataset_permission_partial_members_with_permission_success(self, db_session_with_containers):
|
||||
"""
|
||||
Test that user with explicit permission can access partial_members dataset.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(
|
||||
tenant.id,
|
||||
owner.id,
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
)
|
||||
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
|
||||
|
||||
# Act (should not raise)
|
||||
DatasetService.check_dataset_permission(dataset, user)
|
||||
|
||||
# Assert
|
||||
permissions = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert user.id in permissions
|
||||
|
||||
def test_check_dataset_permission_partial_members_without_permission_error(self, db_session_with_containers):
|
||||
"""
|
||||
Test error when user without permission tries to access partial_members dataset.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(
|
||||
tenant.id,
|
||||
owner.id,
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
)
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_permission(dataset, user)
|
||||
|
||||
|
||||
class TestDatasetServiceCheckDatasetOperatorPermission:
|
||||
"""Verify operator permission checks against persisted partial-member permissions."""
|
||||
|
||||
def test_check_dataset_operator_permission_partial_members_with_permission_success(
|
||||
self, db_session_with_containers
|
||||
):
|
||||
"""
|
||||
Test that user with explicit permission can access partial_members dataset.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(
|
||||
tenant.id,
|
||||
owner.id,
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
)
|
||||
DatasetPermissionTestDataFactory.create_dataset_permission(dataset.id, user.id, tenant.id)
|
||||
|
||||
# Act (should not raise)
|
||||
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
|
||||
|
||||
# Assert
|
||||
permissions = DatasetPermissionService.get_dataset_partial_member_list(dataset.id)
|
||||
assert user.id in permissions
|
||||
|
||||
def test_check_dataset_operator_permission_partial_members_without_permission_error(
|
||||
self, db_session_with_containers
|
||||
):
|
||||
"""
|
||||
Test error when user without permission tries to access partial_members dataset.
|
||||
"""
|
||||
# Arrange
|
||||
owner, tenant = DatasetPermissionTestDataFactory.create_account_with_tenant(role=TenantAccountRole.OWNER)
|
||||
user, _ = DatasetPermissionTestDataFactory.create_account_with_tenant(
|
||||
role=TenantAccountRole.NORMAL,
|
||||
tenant=tenant,
|
||||
)
|
||||
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset(
|
||||
tenant.id,
|
||||
owner.id,
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
)
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
|
||||
@ -258,323 +258,6 @@ class DatasetPermissionTestDataFactory:
|
||||
return [{"user_id": user_id} for user_id in user_ids]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests for get_dataset_partial_member_list
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceGetPartialMemberList:
|
||||
"""
|
||||
Comprehensive unit tests for DatasetPermissionService.get_dataset_partial_member_list method.
|
||||
|
||||
This test class covers the retrieval of partial member lists for datasets,
|
||||
which returns a list of account IDs that have explicit permissions for
|
||||
a given dataset.
|
||||
|
||||
The get_dataset_partial_member_list method:
|
||||
1. Queries DatasetPermission table for the dataset ID
|
||||
2. Selects account_id values
|
||||
3. Returns list of account IDs
|
||||
|
||||
Test scenarios include:
|
||||
- Retrieving list with multiple members
|
||||
- Retrieving list with single member
|
||||
- Retrieving empty list (no partial members)
|
||||
- Database query validation
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_session(self):
|
||||
"""
|
||||
Mock database session for testing.
|
||||
|
||||
Provides a mocked database session that can be used to verify
|
||||
query construction and execution.
|
||||
"""
|
||||
with patch("services.dataset_service.db.session") as mock_db:
|
||||
yield mock_db
|
||||
|
||||
def test_get_dataset_partial_member_list_with_members(self, mock_db_session):
|
||||
"""
|
||||
Test retrieving partial member list with multiple members.
|
||||
|
||||
Verifies that when a dataset has multiple partial members, all
|
||||
account IDs are returned correctly.
|
||||
|
||||
This test ensures:
|
||||
- Query is constructed correctly
|
||||
- All account IDs are returned
|
||||
- Database query is executed
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
expected_account_ids = ["user-456", "user-789", "user-012"]
|
||||
|
||||
# Mock the scalars query to return account IDs
|
||||
mock_scalars_result = Mock()
|
||||
mock_scalars_result.all.return_value = expected_account_ids
|
||||
mock_db_session.scalars.return_value = mock_scalars_result
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset_id)
|
||||
|
||||
# Assert
|
||||
assert result == expected_account_ids
|
||||
assert len(result) == 3
|
||||
|
||||
# Verify query was executed
|
||||
mock_db_session.scalars.assert_called_once()
|
||||
|
||||
def test_get_dataset_partial_member_list_with_single_member(self, mock_db_session):
|
||||
"""
|
||||
Test retrieving partial member list with single member.
|
||||
|
||||
Verifies that when a dataset has only one partial member, the
|
||||
single account ID is returned correctly.
|
||||
|
||||
This test ensures:
|
||||
- Query works correctly for single member
|
||||
- Result is a list with one element
|
||||
- Database query is executed
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
expected_account_ids = ["user-456"]
|
||||
|
||||
# Mock the scalars query to return single account ID
|
||||
mock_scalars_result = Mock()
|
||||
mock_scalars_result.all.return_value = expected_account_ids
|
||||
mock_db_session.scalars.return_value = mock_scalars_result
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset_id)
|
||||
|
||||
# Assert
|
||||
assert result == expected_account_ids
|
||||
assert len(result) == 1
|
||||
|
||||
# Verify query was executed
|
||||
mock_db_session.scalars.assert_called_once()
|
||||
|
||||
def test_get_dataset_partial_member_list_empty(self, mock_db_session):
|
||||
"""
|
||||
Test retrieving partial member list when no members exist.
|
||||
|
||||
Verifies that when a dataset has no partial members, an empty
|
||||
list is returned.
|
||||
|
||||
This test ensures:
|
||||
- Empty list is returned correctly
|
||||
- Query is executed even when no results
|
||||
- No errors are raised
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
|
||||
# Mock the scalars query to return empty list
|
||||
mock_scalars_result = Mock()
|
||||
mock_scalars_result.all.return_value = []
|
||||
mock_db_session.scalars.return_value = mock_scalars_result
|
||||
|
||||
# Act
|
||||
result = DatasetPermissionService.get_dataset_partial_member_list(dataset_id)
|
||||
|
||||
# Assert
|
||||
assert result == []
|
||||
assert len(result) == 0
|
||||
|
||||
# Verify query was executed
|
||||
mock_db_session.scalars.assert_called_once()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests for update_partial_member_list
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceUpdatePartialMemberList:
|
||||
"""
|
||||
Comprehensive unit tests for DatasetPermissionService.update_partial_member_list method.
|
||||
|
||||
This test class covers the update of partial member lists for datasets,
|
||||
which replaces the existing partial member list with a new one.
|
||||
|
||||
The update_partial_member_list method:
|
||||
1. Deletes all existing DatasetPermission records for the dataset
|
||||
2. Creates new DatasetPermission records for each user in the list
|
||||
3. Adds all new permissions to the session
|
||||
4. Commits the transaction
|
||||
5. Rolls back on error
|
||||
|
||||
Test scenarios include:
|
||||
- Adding new partial members
|
||||
- Updating existing partial members
|
||||
- Replacing entire member list
|
||||
- Handling empty member list
|
||||
- Database transaction handling
|
||||
- Error handling and rollback
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_session(self):
|
||||
"""
|
||||
Mock database session for testing.
|
||||
|
||||
Provides a mocked database session that can be used to verify
|
||||
database operations including queries, adds, commits, and rollbacks.
|
||||
"""
|
||||
with patch("services.dataset_service.db.session") as mock_db:
|
||||
yield mock_db
|
||||
|
||||
def test_update_partial_member_list_add_new_members(self, mock_db_session):
|
||||
"""
|
||||
Test adding new partial members to a dataset.
|
||||
|
||||
Verifies that when updating with new members, the old members
|
||||
are deleted and new members are added correctly.
|
||||
|
||||
This test ensures:
|
||||
- Old permissions are deleted
|
||||
- New permissions are created
|
||||
- All permissions are added to session
|
||||
- Transaction is committed
|
||||
"""
|
||||
# Arrange
|
||||
tenant_id = "tenant-123"
|
||||
dataset_id = "dataset-123"
|
||||
user_list = DatasetPermissionTestDataFactory.create_user_list_mock(["user-456", "user-789"])
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant_id, dataset_id, user_list)
|
||||
|
||||
# Assert
|
||||
# Verify old permissions were deleted
|
||||
mock_db_session.query.assert_called()
|
||||
mock_query.where.assert_called()
|
||||
|
||||
# Verify new permissions were added
|
||||
mock_db_session.add_all.assert_called_once()
|
||||
|
||||
# Verify transaction was committed
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
# Verify no rollback occurred
|
||||
mock_db_session.rollback.assert_not_called()
|
||||
|
||||
def test_update_partial_member_list_replace_existing(self, mock_db_session):
|
||||
"""
|
||||
Test replacing existing partial members with new ones.
|
||||
|
||||
Verifies that when updating with a different member list, the
|
||||
old members are removed and new members are added.
|
||||
|
||||
This test ensures:
|
||||
- Old permissions are deleted
|
||||
- New permissions replace old ones
|
||||
- Transaction is committed successfully
|
||||
"""
|
||||
# Arrange
|
||||
tenant_id = "tenant-123"
|
||||
dataset_id = "dataset-123"
|
||||
user_list = DatasetPermissionTestDataFactory.create_user_list_mock(["user-999", "user-888"])
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant_id, dataset_id, user_list)
|
||||
|
||||
# Assert
|
||||
# Verify old permissions were deleted
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
# Verify new permissions were added
|
||||
mock_db_session.add_all.assert_called_once()
|
||||
|
||||
# Verify transaction was committed
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
def test_update_partial_member_list_empty_list(self, mock_db_session):
|
||||
"""
|
||||
Test updating with empty member list (clearing all members).
|
||||
|
||||
Verifies that when updating with an empty list, all existing
|
||||
permissions are deleted and no new permissions are added.
|
||||
|
||||
This test ensures:
|
||||
- Old permissions are deleted
|
||||
- No new permissions are added
|
||||
- Transaction is committed
|
||||
"""
|
||||
# Arrange
|
||||
tenant_id = "tenant-123"
|
||||
dataset_id = "dataset-123"
|
||||
user_list = []
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.update_partial_member_list(tenant_id, dataset_id, user_list)
|
||||
|
||||
# Assert
|
||||
# Verify old permissions were deleted
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
# Verify add_all was called with empty list
|
||||
mock_db_session.add_all.assert_called_once_with([])
|
||||
|
||||
# Verify transaction was committed
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
def test_update_partial_member_list_database_error_rollback(self, mock_db_session):
|
||||
"""
|
||||
Test error handling and rollback on database error.
|
||||
|
||||
Verifies that when a database error occurs during the update,
|
||||
the transaction is rolled back and the error is re-raised.
|
||||
|
||||
This test ensures:
|
||||
- Error is caught and handled
|
||||
- Transaction is rolled back
|
||||
- Error is re-raised
|
||||
- No commit occurs after error
|
||||
"""
|
||||
# Arrange
|
||||
tenant_id = "tenant-123"
|
||||
dataset_id = "dataset-123"
|
||||
user_list = DatasetPermissionTestDataFactory.create_user_list_mock(["user-456"])
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Mock commit to raise an error
|
||||
database_error = Exception("Database connection error")
|
||||
mock_db_session.commit.side_effect = database_error
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(Exception, match="Database connection error"):
|
||||
DatasetPermissionService.update_partial_member_list(tenant_id, dataset_id, user_list)
|
||||
|
||||
# Verify rollback was called
|
||||
mock_db_session.rollback.assert_called_once()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests for check_permission
|
||||
# ============================================================================
|
||||
@ -776,144 +459,6 @@ class TestDatasetPermissionServiceCheckPermission:
|
||||
mock_get_partial_member_list.assert_called_once_with(dataset.id)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests for clear_partial_member_list
|
||||
# ============================================================================
|
||||
|
||||
|
||||
class TestDatasetPermissionServiceClearPartialMemberList:
|
||||
"""
|
||||
Comprehensive unit tests for DatasetPermissionService.clear_partial_member_list method.
|
||||
|
||||
This test class covers the clearing of partial member lists, which removes
|
||||
all DatasetPermission records for a given dataset.
|
||||
|
||||
The clear_partial_member_list method:
|
||||
1. Deletes all DatasetPermission records for the dataset
|
||||
2. Commits the transaction
|
||||
3. Rolls back on error
|
||||
|
||||
Test scenarios include:
|
||||
- Clearing list with existing members
|
||||
- Clearing empty list (no members)
|
||||
- Database transaction handling
|
||||
- Error handling and rollback
|
||||
"""
|
||||
|
||||
@pytest.fixture
|
||||
def mock_db_session(self):
|
||||
"""
|
||||
Mock database session for testing.
|
||||
|
||||
Provides a mocked database session that can be used to verify
|
||||
database operations including queries, deletes, commits, and rollbacks.
|
||||
"""
|
||||
with patch("services.dataset_service.db.session") as mock_db:
|
||||
yield mock_db
|
||||
|
||||
def test_clear_partial_member_list_success(self, mock_db_session):
|
||||
"""
|
||||
Test successful clearing of partial member list.
|
||||
|
||||
Verifies that when clearing a partial member list, all permissions
|
||||
are deleted and the transaction is committed.
|
||||
|
||||
This test ensures:
|
||||
- All permissions are deleted
|
||||
- Transaction is committed
|
||||
- No errors are raised
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.clear_partial_member_list(dataset_id)
|
||||
|
||||
# Assert
|
||||
# Verify query was executed
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
# Verify delete was called
|
||||
mock_query.where.assert_called()
|
||||
mock_query.delete.assert_called_once()
|
||||
|
||||
# Verify transaction was committed
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
# Verify no rollback occurred
|
||||
mock_db_session.rollback.assert_not_called()
|
||||
|
||||
def test_clear_partial_member_list_empty_list(self, mock_db_session):
|
||||
"""
|
||||
Test clearing partial member list when no members exist.
|
||||
|
||||
Verifies that when clearing an already empty list, the operation
|
||||
completes successfully without errors.
|
||||
|
||||
This test ensures:
|
||||
- Operation works correctly for empty lists
|
||||
- Transaction is committed
|
||||
- No errors are raised
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act
|
||||
DatasetPermissionService.clear_partial_member_list(dataset_id)
|
||||
|
||||
# Assert
|
||||
# Verify query was executed
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
# Verify transaction was committed
|
||||
mock_db_session.commit.assert_called_once()
|
||||
|
||||
def test_clear_partial_member_list_database_error_rollback(self, mock_db_session):
|
||||
"""
|
||||
Test error handling and rollback on database error.
|
||||
|
||||
Verifies that when a database error occurs during clearing,
|
||||
the transaction is rolled back and the error is re-raised.
|
||||
|
||||
This test ensures:
|
||||
- Error is caught and handled
|
||||
- Transaction is rolled back
|
||||
- Error is re-raised
|
||||
- No commit occurs after error
|
||||
"""
|
||||
# Arrange
|
||||
dataset_id = "dataset-123"
|
||||
|
||||
# Mock the query delete operation
|
||||
mock_query = Mock()
|
||||
mock_query.where.return_value = mock_query
|
||||
mock_query.delete.return_value = None
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Mock commit to raise an error
|
||||
database_error = Exception("Database connection error")
|
||||
mock_db_session.commit.side_effect = database_error
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(Exception, match="Database connection error"):
|
||||
DatasetPermissionService.clear_partial_member_list(dataset_id)
|
||||
|
||||
# Verify rollback was called
|
||||
mock_db_session.rollback.assert_called_once()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Tests for DatasetService.check_dataset_permission
|
||||
# ============================================================================
|
||||
@ -1047,72 +592,6 @@ class TestDatasetServiceCheckDatasetPermission:
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_permission(dataset, user)
|
||||
|
||||
def test_check_dataset_permission_partial_members_with_permission_success(self, mock_db_session):
|
||||
"""
|
||||
Test that user with explicit permission can access partial_members dataset.
|
||||
|
||||
Verifies that when a user has an explicit DatasetPermission record
|
||||
for a partial_members dataset, they can access it successfully.
|
||||
|
||||
This test ensures:
|
||||
- Explicit permissions are checked correctly
|
||||
- Users with permissions can access
|
||||
- Database query is executed
|
||||
"""
|
||||
# Arrange
|
||||
user = DatasetPermissionTestDataFactory.create_user_mock(user_id="user-123", role=TenantAccountRole.NORMAL)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
||||
tenant_id="tenant-123",
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
created_by="other-user-456", # Not the creator
|
||||
)
|
||||
|
||||
# Mock permission query to return permission record
|
||||
mock_permission = DatasetPermissionTestDataFactory.create_dataset_permission_mock(
|
||||
dataset_id=dataset.id, account_id=user.id
|
||||
)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = mock_permission
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act (should not raise)
|
||||
DatasetService.check_dataset_permission(dataset, user)
|
||||
|
||||
# Assert
|
||||
# Verify permission query was executed
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
def test_check_dataset_permission_partial_members_without_permission_error(self, mock_db_session):
|
||||
"""
|
||||
Test error when user without permission tries to access partial_members dataset.
|
||||
|
||||
Verifies that when a user does not have an explicit DatasetPermission
|
||||
record for a partial_members dataset, a NoPermissionError is raised.
|
||||
|
||||
This test ensures:
|
||||
- Missing permissions are detected
|
||||
- Error message is clear
|
||||
- Error type is correct
|
||||
"""
|
||||
# Arrange
|
||||
user = DatasetPermissionTestDataFactory.create_user_mock(user_id="user-123", role=TenantAccountRole.NORMAL)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
||||
tenant_id="tenant-123",
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
created_by="other-user-456", # Not the creator
|
||||
)
|
||||
|
||||
# Mock permission query to return None (no permission)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.first.return_value = None # No permission found
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_permission(dataset, user)
|
||||
|
||||
def test_check_dataset_permission_partial_members_creator_success(self, mock_db_session):
|
||||
"""
|
||||
Test that creator can access partial_members dataset without explicit permission.
|
||||
@ -1311,72 +790,6 @@ class TestDatasetServiceCheckDatasetOperatorPermission:
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
|
||||
|
||||
def test_check_dataset_operator_permission_partial_members_with_permission_success(self, mock_db_session):
|
||||
"""
|
||||
Test that user with explicit permission can access partial_members dataset.
|
||||
|
||||
Verifies that when a user has an explicit DatasetPermission record
|
||||
for a partial_members dataset, they can access it successfully.
|
||||
|
||||
This test ensures:
|
||||
- Explicit permissions are checked correctly
|
||||
- Users with permissions can access
|
||||
- Database query is executed
|
||||
"""
|
||||
# Arrange
|
||||
user = DatasetPermissionTestDataFactory.create_user_mock(user_id="user-123", role=TenantAccountRole.NORMAL)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
||||
tenant_id="tenant-123",
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
created_by="other-user-456", # Not the creator
|
||||
)
|
||||
|
||||
# Mock permission query to return permission records
|
||||
mock_permission = DatasetPermissionTestDataFactory.create_dataset_permission_mock(
|
||||
dataset_id=dataset.id, account_id=user.id
|
||||
)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.all.return_value = [mock_permission] # User has permission
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act (should not raise)
|
||||
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
|
||||
|
||||
# Assert
|
||||
# Verify permission query was executed
|
||||
mock_db_session.query.assert_called()
|
||||
|
||||
def test_check_dataset_operator_permission_partial_members_without_permission_error(self, mock_db_session):
|
||||
"""
|
||||
Test error when user without permission tries to access partial_members dataset.
|
||||
|
||||
Verifies that when a user does not have an explicit DatasetPermission
|
||||
record for a partial_members dataset, a NoPermissionError is raised.
|
||||
|
||||
This test ensures:
|
||||
- Missing permissions are detected
|
||||
- Error message is clear
|
||||
- Error type is correct
|
||||
"""
|
||||
# Arrange
|
||||
user = DatasetPermissionTestDataFactory.create_user_mock(user_id="user-123", role=TenantAccountRole.NORMAL)
|
||||
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
||||
tenant_id="tenant-123",
|
||||
permission=DatasetPermissionEnum.PARTIAL_TEAM,
|
||||
created_by="other-user-456", # Not the creator
|
||||
)
|
||||
|
||||
# Mock permission query to return empty list (no permission)
|
||||
mock_query = Mock()
|
||||
mock_query.filter_by.return_value = mock_query
|
||||
mock_query.all.return_value = [] # No permissions found
|
||||
mock_db_session.query.return_value = mock_query
|
||||
|
||||
# Act & Assert
|
||||
with pytest.raises(NoPermissionError, match="You do not have permission to access this dataset"):
|
||||
DatasetService.check_dataset_operator_permission(user=user, dataset=dataset)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Additional Documentation and Notes
|
||||
|
||||
Reference in New Issue
Block a user