You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

test_dataset_permission.py 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. from unittest.mock import Mock, patch
  2. import pytest
  3. from models.account import Account, TenantAccountRole
  4. from models.dataset import Dataset, DatasetPermission, DatasetPermissionEnum
  5. from services.dataset_service import DatasetService
  6. from services.errors.account import NoPermissionError
  7. class DatasetPermissionTestDataFactory:
  8. """Factory class for creating test data and mock objects for dataset permission tests."""
  9. @staticmethod
  10. def create_dataset_mock(
  11. dataset_id: str = "dataset-123",
  12. tenant_id: str = "test-tenant-123",
  13. created_by: str = "creator-456",
  14. permission: DatasetPermissionEnum = DatasetPermissionEnum.ONLY_ME,
  15. **kwargs,
  16. ) -> Mock:
  17. """Create a mock dataset with specified attributes."""
  18. dataset = Mock(spec=Dataset)
  19. dataset.id = dataset_id
  20. dataset.tenant_id = tenant_id
  21. dataset.created_by = created_by
  22. dataset.permission = permission
  23. for key, value in kwargs.items():
  24. setattr(dataset, key, value)
  25. return dataset
  26. @staticmethod
  27. def create_user_mock(
  28. user_id: str = "user-789",
  29. tenant_id: str = "test-tenant-123",
  30. role: TenantAccountRole = TenantAccountRole.NORMAL,
  31. **kwargs,
  32. ) -> Mock:
  33. """Create a mock user with specified attributes."""
  34. user = Mock(spec=Account)
  35. user.id = user_id
  36. user.current_tenant_id = tenant_id
  37. user.current_role = role
  38. for key, value in kwargs.items():
  39. setattr(user, key, value)
  40. return user
  41. @staticmethod
  42. def create_dataset_permission_mock(
  43. dataset_id: str = "dataset-123",
  44. account_id: str = "user-789",
  45. **kwargs,
  46. ) -> Mock:
  47. """Create a mock dataset permission record."""
  48. permission = Mock(spec=DatasetPermission)
  49. permission.dataset_id = dataset_id
  50. permission.account_id = account_id
  51. for key, value in kwargs.items():
  52. setattr(permission, key, value)
  53. return permission
  54. class TestDatasetPermissionService:
  55. """
  56. Comprehensive unit tests for DatasetService.check_dataset_permission method.
  57. This test suite covers all permission scenarios including:
  58. - Cross-tenant access restrictions
  59. - Owner privilege checks
  60. - Different permission levels (ONLY_ME, ALL_TEAM, PARTIAL_TEAM)
  61. - Explicit permission checks for PARTIAL_TEAM
  62. - Error conditions and logging
  63. """
  64. @pytest.fixture
  65. def mock_dataset_service_dependencies(self):
  66. """Common mock setup for dataset service dependencies."""
  67. with patch("services.dataset_service.db.session") as mock_session:
  68. yield {
  69. "db_session": mock_session,
  70. }
  71. @pytest.fixture
  72. def mock_logging_dependencies(self):
  73. """Mock setup for logging tests."""
  74. with patch("services.dataset_service.logging") as mock_logging:
  75. yield {
  76. "logging": mock_logging,
  77. }
  78. def _assert_permission_check_passes(self, dataset: Mock, user: Mock):
  79. """Helper method to verify that permission check passes without raising exceptions."""
  80. # Should not raise any exception
  81. DatasetService.check_dataset_permission(dataset, user)
  82. def _assert_permission_check_fails(
  83. self, dataset: Mock, user: Mock, expected_message: str = "You do not have permission to access this dataset."
  84. ):
  85. """Helper method to verify that permission check fails with expected error."""
  86. with pytest.raises(NoPermissionError, match=expected_message):
  87. DatasetService.check_dataset_permission(dataset, user)
  88. def _assert_database_query_called(self, mock_session: Mock, dataset_id: str, account_id: str):
  89. """Helper method to verify database query calls for permission checks."""
  90. mock_session.query().filter_by.assert_called_with(dataset_id=dataset_id, account_id=account_id)
  91. def _assert_database_query_not_called(self, mock_session: Mock):
  92. """Helper method to verify that database query was not called."""
  93. mock_session.query.assert_not_called()
  94. # ==================== Cross-Tenant Access Tests ====================
  95. def test_permission_check_different_tenant_should_fail(self):
  96. """Test that users from different tenants cannot access dataset regardless of other permissions."""
  97. # Create dataset and user from different tenants
  98. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  99. tenant_id="tenant-123", permission=DatasetPermissionEnum.ALL_TEAM
  100. )
  101. user = DatasetPermissionTestDataFactory.create_user_mock(
  102. user_id="user-789", tenant_id="different-tenant-456", role=TenantAccountRole.EDITOR
  103. )
  104. # Should fail due to different tenant
  105. self._assert_permission_check_fails(dataset, user)
  106. # ==================== Owner Privilege Tests ====================
  107. def test_owner_can_access_any_dataset(self):
  108. """Test that tenant owners can access any dataset regardless of permission level."""
  109. # Create dataset with restrictive permission
  110. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ONLY_ME)
  111. # Create owner user
  112. owner_user = DatasetPermissionTestDataFactory.create_user_mock(
  113. user_id="owner-999", role=TenantAccountRole.OWNER
  114. )
  115. # Owner should have access regardless of dataset permission
  116. self._assert_permission_check_passes(dataset, owner_user)
  117. # ==================== ONLY_ME Permission Tests ====================
  118. def test_only_me_permission_creator_can_access(self):
  119. """Test ONLY_ME permission allows only the dataset creator to access."""
  120. # Create dataset with ONLY_ME permission
  121. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  122. created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME
  123. )
  124. # Create creator user
  125. creator_user = DatasetPermissionTestDataFactory.create_user_mock(
  126. user_id="creator-456", role=TenantAccountRole.EDITOR
  127. )
  128. # Creator should be able to access
  129. self._assert_permission_check_passes(dataset, creator_user)
  130. def test_only_me_permission_others_cannot_access(self):
  131. """Test ONLY_ME permission denies access to non-creators."""
  132. # Create dataset with ONLY_ME permission
  133. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  134. created_by="creator-456", permission=DatasetPermissionEnum.ONLY_ME
  135. )
  136. # Create normal user (not the creator)
  137. normal_user = DatasetPermissionTestDataFactory.create_user_mock(
  138. user_id="normal-789", role=TenantAccountRole.NORMAL
  139. )
  140. # Non-creator should be denied access
  141. self._assert_permission_check_fails(dataset, normal_user)
  142. # ==================== ALL_TEAM Permission Tests ====================
  143. def test_all_team_permission_allows_access(self):
  144. """Test ALL_TEAM permission allows any team member to access the dataset."""
  145. # Create dataset with ALL_TEAM permission
  146. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.ALL_TEAM)
  147. # Create different types of team members
  148. normal_user = DatasetPermissionTestDataFactory.create_user_mock(
  149. user_id="normal-789", role=TenantAccountRole.NORMAL
  150. )
  151. editor_user = DatasetPermissionTestDataFactory.create_user_mock(
  152. user_id="editor-456", role=TenantAccountRole.EDITOR
  153. )
  154. # All team members should have access
  155. self._assert_permission_check_passes(dataset, normal_user)
  156. self._assert_permission_check_passes(dataset, editor_user)
  157. # ==================== PARTIAL_TEAM Permission Tests ====================
  158. def test_partial_team_permission_creator_can_access(self, mock_dataset_service_dependencies):
  159. """Test PARTIAL_TEAM permission allows creator to access without database query."""
  160. # Create dataset with PARTIAL_TEAM permission
  161. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  162. created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
  163. )
  164. # Create creator user
  165. creator_user = DatasetPermissionTestDataFactory.create_user_mock(
  166. user_id="creator-456", role=TenantAccountRole.EDITOR
  167. )
  168. # Creator should have access without database query
  169. self._assert_permission_check_passes(dataset, creator_user)
  170. self._assert_database_query_not_called(mock_dataset_service_dependencies["db_session"])
  171. def test_partial_team_permission_with_explicit_permission(self, mock_dataset_service_dependencies):
  172. """Test PARTIAL_TEAM permission allows users with explicit permission records."""
  173. # Create dataset with PARTIAL_TEAM permission
  174. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
  175. # Create normal user (not the creator)
  176. normal_user = DatasetPermissionTestDataFactory.create_user_mock(
  177. user_id="normal-789", role=TenantAccountRole.NORMAL
  178. )
  179. # Mock database query to return a permission record
  180. mock_permission = DatasetPermissionTestDataFactory.create_dataset_permission_mock(
  181. dataset_id=dataset.id, account_id=normal_user.id
  182. )
  183. mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = mock_permission
  184. # User with explicit permission should have access
  185. self._assert_permission_check_passes(dataset, normal_user)
  186. self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id)
  187. def test_partial_team_permission_without_explicit_permission(self, mock_dataset_service_dependencies):
  188. """Test PARTIAL_TEAM permission denies users without explicit permission records."""
  189. # Create dataset with PARTIAL_TEAM permission
  190. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
  191. # Create normal user (not the creator)
  192. normal_user = DatasetPermissionTestDataFactory.create_user_mock(
  193. user_id="normal-789", role=TenantAccountRole.NORMAL
  194. )
  195. # Mock database query to return None (no permission record)
  196. mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
  197. # User without explicit permission should be denied access
  198. self._assert_permission_check_fails(dataset, normal_user)
  199. self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, normal_user.id)
  200. def test_partial_team_permission_non_creator_without_permission_fails(self, mock_dataset_service_dependencies):
  201. """Test that non-creators without explicit permission are denied access to PARTIAL_TEAM datasets."""
  202. # Create dataset with PARTIAL_TEAM permission
  203. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  204. created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
  205. )
  206. # Create a different user (not the creator)
  207. other_user = DatasetPermissionTestDataFactory.create_user_mock(
  208. user_id="other-user-123", role=TenantAccountRole.NORMAL
  209. )
  210. # Mock database query to return None (no permission record)
  211. mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
  212. # Non-creator without explicit permission should be denied access
  213. self._assert_permission_check_fails(dataset, other_user)
  214. self._assert_database_query_called(mock_dataset_service_dependencies["db_session"], dataset.id, other_user.id)
  215. # ==================== Enum Usage Tests ====================
  216. def test_partial_team_permission_uses_correct_enum(self):
  217. """Test that the method correctly uses DatasetPermissionEnum.PARTIAL_TEAM instead of string literals."""
  218. # Create dataset with PARTIAL_TEAM permission using enum
  219. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(
  220. created_by="creator-456", permission=DatasetPermissionEnum.PARTIAL_TEAM
  221. )
  222. # Create creator user
  223. creator_user = DatasetPermissionTestDataFactory.create_user_mock(
  224. user_id="creator-456", role=TenantAccountRole.EDITOR
  225. )
  226. # Creator should always have access regardless of permission level
  227. self._assert_permission_check_passes(dataset, creator_user)
  228. # ==================== Logging Tests ====================
  229. def test_permission_denied_logs_debug_message(self, mock_dataset_service_dependencies, mock_logging_dependencies):
  230. """Test that permission denied events are properly logged for debugging purposes."""
  231. # Create dataset with PARTIAL_TEAM permission
  232. dataset = DatasetPermissionTestDataFactory.create_dataset_mock(permission=DatasetPermissionEnum.PARTIAL_TEAM)
  233. # Create normal user (not the creator)
  234. normal_user = DatasetPermissionTestDataFactory.create_user_mock(
  235. user_id="normal-789", role=TenantAccountRole.NORMAL
  236. )
  237. # Mock database query to return None (no permission record)
  238. mock_dataset_service_dependencies["db_session"].query().filter_by().first.return_value = None
  239. # Attempt permission check (should fail)
  240. with pytest.raises(NoPermissionError):
  241. DatasetService.check_dataset_permission(dataset, normal_user)
  242. # Verify debug message was logged with correct user and dataset information
  243. mock_logging_dependencies["logging"].debug.assert_called_with(
  244. f"User {normal_user.id} does not have permission to access dataset {dataset.id}"
  245. )