2025-05-27 15:33:11 +09:00
|
|
|
from unittest.mock import Mock, patch
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from models.account import Account, TenantAccountRole
|
|
|
|
from models.dataset import Dataset, DatasetPermission, DatasetPermissionEnum
|
|
|
|
from services.dataset_service import DatasetService
|
|
|
|
from services.errors.account import NoPermissionError
|
|
|
|
|
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
class DatasetPermissionTestDataFactory:
|
|
|
|
"""Factory class for creating test data and mock objects for dataset permission tests."""
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def create_dataset_mock(
|
|
|
|
dataset_id: str = "dataset-123",
|
|
|
|
tenant_id: str = "test-tenant-123",
|
|
|
|
created_by: str = "creator-456",
|
|
|
|
permission: DatasetPermissionEnum = DatasetPermissionEnum.ONLY_ME,
|
|
|
|
**kwargs,
|
|
|
|
) -> Mock:
|
|
|
|
"""Create a mock dataset with specified attributes."""
|
|
|
|
dataset = Mock(spec=Dataset)
|
|
|
|
dataset.id = dataset_id
|
|
|
|
dataset.tenant_id = tenant_id
|
|
|
|
dataset.created_by = created_by
|
|
|
|
dataset.permission = permission
|
|
|
|
for key, value in kwargs.items():
|
|
|
|
setattr(dataset, key, value)
|
|
|
|
return dataset
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def create_user_mock(
|
|
|
|
user_id: str = "user-789",
|
|
|
|
tenant_id: str = "test-tenant-123",
|
|
|
|
role: TenantAccountRole = TenantAccountRole.NORMAL,
|
|
|
|
**kwargs,
|
|
|
|
) -> Mock:
|
|
|
|
"""Create a mock user with specified attributes."""
|
|
|
|
user = Mock(spec=Account)
|
|
|
|
user.id = user_id
|
|
|
|
user.current_tenant_id = tenant_id
|
|
|
|
user.current_role = role
|
|
|
|
for key, value in kwargs.items():
|
|
|
|
setattr(user, key, value)
|
|
|
|
return user
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
def create_dataset_permission_mock(
|
|
|
|
dataset_id: str = "dataset-123",
|
|
|
|
account_id: str = "user-789",
|
|
|
|
**kwargs,
|
|
|
|
) -> Mock:
|
|
|
|
"""Create a mock dataset permission record."""
|
|
|
|
permission = Mock(spec=DatasetPermission)
|
|
|
|
permission.dataset_id = dataset_id
|
|
|
|
permission.account_id = account_id
|
|
|
|
for key, value in kwargs.items():
|
|
|
|
setattr(permission, key, value)
|
|
|
|
return permission
|
|
|
|
|
|
|
|
|
2025-05-27 15:33:11 +09:00
|
|
|
class TestDatasetPermissionService:
|
2025-06-26 10:07:54 +08:00
|
|
|
"""
|
|
|
|
Comprehensive unit tests for DatasetService.check_dataset_permission method.
|
|
|
|
|
|
|
|
This test suite covers all permission scenarios including:
|
|
|
|
- Cross-tenant access restrictions
|
|
|
|
- Owner privilege checks
|
|
|
|
- Different permission levels (ONLY_ME, ALL_TEAM, PARTIAL_TEAM)
|
|
|
|
- Explicit permission checks for PARTIAL_TEAM
|
|
|
|
- Error conditions and logging
|
|
|
|
"""
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def mock_dataset_service_dependencies(self):
|
|
|
|
"""Common mock setup for dataset service dependencies."""
|
|
|
|
with patch("services.dataset_service.db.session") as mock_session:
|
|
|
|
yield {
|
|
|
|
"db_session": mock_session,
|
|
|
|
}
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
def mock_logging_dependencies(self):
|
|
|
|
"""Mock setup for logging tests."""
|
|
|
|
with patch("services.dataset_service.logging") as mock_logging:
|
|
|
|
yield {
|
|
|
|
"logging": mock_logging,
|
|
|
|
}
|
|
|
|
|
|
|
|
def _assert_permission_check_passes(self, dataset: Mock, user: Mock):
|
|
|
|
"""Helper method to verify that permission check passes without raising exceptions."""
|
|
|
|
# Should not raise any exception
|
|
|
|
DatasetService.check_dataset_permission(dataset, user)
|
|
|
|
|
|
|
|
def _assert_permission_check_fails(
|
|
|
|
self, dataset: Mock, user: Mock, expected_message: str = "You do not have permission to access this dataset."
|
|
|
|
):
|
|
|
|
"""Helper method to verify that permission check fails with expected error."""
|
|
|
|
with pytest.raises(NoPermissionError, match=expected_message):
|
|
|
|
DatasetService.check_dataset_permission(dataset, user)
|
|
|
|
|
|
|
|
def _assert_database_query_called(self, mock_session: Mock, dataset_id: str, account_id: str):
|
|
|
|
"""Helper method to verify database query calls for permission checks."""
|
|
|
|
mock_session.query().filter_by.assert_called_with(dataset_id=dataset_id, account_id=account_id)
|
|
|
|
|
|
|
|
def _assert_database_query_not_called(self, mock_session: Mock):
|
|
|
|
"""Helper method to verify that database query was not called."""
|
|
|
|
mock_session.query.assert_not_called()
|
|
|
|
|
|
|
|
# ==================== Cross-Tenant Access Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_permission_check_different_tenant_should_fail(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test that users from different tenants cannot access dataset regardless of other permissions."""
|
|
|
|
# Create dataset and user from different tenants
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
tenant_id="tenant-123", permission=DatasetPermissionEnum.ALL_TEAM
|
|
|
|
)
|
|
|
|
user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="user-789", tenant_id="different-tenant-456", role=TenantAccountRole.EDITOR
|
|
|
|
)
|
|
|
|
|
|
|
|
# Should fail due to different tenant
|
|
|
|
self._assert_permission_check_fails(dataset, user)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# ==================== Owner Privilege Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_owner_can_access_any_dataset(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test that tenant owners can access any dataset regardless of permission level."""
|
|
|
|
# Create dataset with restrictive permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ONLY_ME)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Create owner user
|
|
|
|
owner_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="owner-999", role=TenantAccountRole.OWNER
|
|
|
|
)
|
|
|
|
|
|
|
|
# Owner should have access regardless of dataset permission
|
|
|
|
self._assert_permission_check_passes(dataset, owner_user)
|
|
|
|
|
|
|
|
# ==================== ONLY_ME Permission Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_only_me_permission_creator_can_access(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test ONLY_ME permission allows only the dataset creator to access."""
|
|
|
|
# Create dataset with ONLY_ME permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME
|
|
|
|
)
|
|
|
|
|
|
|
|
# Create creator user
|
|
|
|
creator_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="creator-456", role=TenantAccountRole.EDITOR
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
# Creator should be able to access
|
2025-06-26 10:07:54 +08:00
|
|
|
self._assert_permission_check_passes(dataset, creator_user)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_only_me_permission_others_cannot_access(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test ONLY_ME permission denies access to non-creators."""
|
|
|
|
# Create dataset with ONLY_ME permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME
|
|
|
|
)
|
|
|
|
|
|
|
|
# Create normal user (not the creator)
|
|
|
|
normal_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="normal-789", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Non-creator should be denied access
|
|
|
|
self._assert_permission_check_fails(dataset, normal_user)
|
|
|
|
|
|
|
|
# ==================== ALL_TEAM Permission Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_all_team_permission_allows_access(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test ALL_TEAM permission allows any team member to access the dataset."""
|
|
|
|
# Create dataset with ALL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ALL_TEAM)
|
|
|
|
|
|
|
|
# Create different types of team members
|
|
|
|
normal_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="normal-789", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
|
|
|
editor_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="editor-456", role=TenantAccountRole.EDITOR
|
|
|
|
)
|
|
|
|
|
|
|
|
# All team members should have access
|
|
|
|
self._assert_permission_check_passes(dataset, normal_user)
|
|
|
|
self._assert_permission_check_passes(dataset, editor_user)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# ==================== PARTIAL_TEAM Permission Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
def test_partial_team_permission_creator_can_access(self, mock_dataset_service_dependencies):
|
|
|
|
"""Test PARTIAL_TEAM permission allows creator to access without database query."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Create creator user
|
|
|
|
creator_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="creator-456", role=TenantAccountRole.EDITOR
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Creator should have access without database query
|
|
|
|
self._assert_permission_check_passes(dataset, creator_user)
|
|
|
|
self._assert_database_query_not_called(mock_dataset_service_dependencies["db_session"])
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
def test_partial_team_permission_with_explicit_permission(self, mock_dataset_service_dependencies):
|
|
|
|
"""Test PARTIAL_TEAM permission allows users with explicit permission records."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
|
|
|
|
|
|
|
|
# Create normal user (not the creator)
|
|
|
|
normal_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="normal-789", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
# Mock database query to return a permission record
|
2025-06-26 10:07:54 +08:00
|
|
|
mock_permission = DatasetPermissionTestDataFactory.create_dataset_permission_mock(
|
|
|
|
dataset_id=dataset.id, account_id=normal_user.id
|
|
|
|
)
|
|
|
|
mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = mock_permission
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# User with explicit permission should have access
|
|
|
|
self._assert_permission_check_passes(dataset, normal_user)
|
|
|
|
self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
def test_partial_team_permission_without_explicit_permission(self, mock_dataset_service_dependencies):
|
|
|
|
"""Test PARTIAL_TEAM permission denies users without explicit permission records."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Create normal user (not the creator)
|
|
|
|
normal_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="normal-789", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
# Mock database query to return None (no permission record)
|
2025-06-26 10:07:54 +08:00
|
|
|
mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# User without explicit permission should be denied access
|
|
|
|
self._assert_permission_check_fails(dataset, normal_user)
|
|
|
|
self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
def test_partial_team_permission_non_creator_without_permission_fails(self, mock_dataset_service_dependencies):
|
|
|
|
"""Test that non-creators without explicit permission are denied access to PARTIAL_TEAM datasets."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
# Create a different user (not the creator)
|
2025-06-26 10:07:54 +08:00
|
|
|
other_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="other-user-123", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
# Mock database query to return None (no permission record)
|
2025-06-26 10:07:54 +08:00
|
|
|
mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
|
|
|
|
|
|
|
|
# Non-creator without explicit permission should be denied access
|
|
|
|
self._assert_permission_check_fails(dataset, other_user)
|
|
|
|
self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, other_user.id)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# ==================== Enum Usage Tests ====================
|
2025-05-27 15:33:11 +09:00
|
|
|
|
|
|
|
def test_partial_team_permission_uses_correct_enum(self):
|
2025-06-26 10:07:54 +08:00
|
|
|
"""Test that the method correctly uses DatasetPermissionEnum.PARTIAL_TEAM instead of string literals."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission using enum
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
|
|
|
|
created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
|
|
|
|
)
|
|
|
|
|
|
|
|
# Create creator user
|
|
|
|
creator_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="creator-456", role=TenantAccountRole.EDITOR
|
|
|
|
)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Creator should always have access regardless of permission level
|
|
|
|
self._assert_permission_check_passes(dataset, creator_user)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# ==================== Logging Tests ====================
|
|
|
|
|
|
|
|
def test_permission_denied_logs_debug_message(self, mock_dataset_service_dependencies, mock_logging_dependencies):
|
|
|
|
"""Test that permission denied events are properly logged for debugging purposes."""
|
|
|
|
# Create dataset with PARTIAL_TEAM permission
|
|
|
|
dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
|
|
|
|
|
|
|
|
# Create normal user (not the creator)
|
|
|
|
normal_user = DatasetPermissionTestDataFactory.create_user_mock(
|
|
|
|
user_id="normal-789", role=TenantAccountRole.NORMAL
|
|
|
|
)
|
|
|
|
|
|
|
|
# Mock database query to return None (no permission record)
|
|
|
|
mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Attempt permission check (should fail)
|
2025-05-27 15:33:11 +09:00
|
|
|
with pytest.raises(NoPermissionError):
|
2025-06-26 10:07:54 +08:00
|
|
|
DatasetService.check_dataset_permission(dataset, normal_user)
|
2025-05-27 15:33:11 +09:00
|
|
|
|
2025-06-26 10:07:54 +08:00
|
|
|
# Verify debug message was logged with correct user and dataset information
|
|
|
|
mock_logging_dependencies["logging"].debug.assert_called_with(
|
|
|
|
f"User {normal_user.id} does not have permission to access dataset {dataset.id}"
|
2025-05-27 15:33:11 +09:00
|
|
|
)
|