diff --git a/apps/authentication/tests/__init__.py b/apps/authentication/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/api_tests/siginin/test_signin_with_email_and_password.py b/apps/authentication/tests/api_tests/siginin/test_signin_with_email_and_password.py index 34a5322..90e0d27 100644 --- a/apps/authentication/tests/api_tests/siginin/test_signin_with_email_and_password.py +++ b/apps/authentication/tests/api_tests/siginin/test_signin_with_email_and_password.py @@ -1,7 +1,6 @@ import jwt import pytest -from common.config.app_settings import app_settings from tests.api_tests.siginin import config from tests.base.authentication_web import AuthenticationWeb diff --git a/apps/authentication/tests/role_manage_coverage_report.md b/apps/authentication/tests/role_manage_coverage_report.md new file mode 100644 index 0000000..fd3b0d4 --- /dev/null +++ b/apps/authentication/tests/role_manage_coverage_report.md @@ -0,0 +1,28 @@ +# Test Coverage Report (backend modules only) + +--- + +## Coverage Table + +| File Name | Statements | Missed | Coverage | +|------------------------------------------------------------------|------------|--------|----------| +| backend/infra/permission/permission_handler.py | 55 | 0 | 100% | +| backend/infra/permission/role_handler.py | 71 | 0 | 100% | +| backend/infra/permission/user_role_handler.py | 39 | 7 | 82% | +| backend/services/permission/permission_service.py | 20 | 0 | 100% | +| backend/services/permission/role_service.py | 24 | 0 | 100% | +| backend/services/user/user_management_service.py | 39 | 0 | 100% | + +--- + +## Summary + +This test report only includes the test coverage of functions related to role management. + +See the integration tests: +- tests/api_tests/permission/README.md +- tests/api_tests/role/README.md +- tests/api_tests/user/README.md +## TODO + +Add tests for the previous functions. diff --git a/apps/authentication/tests/unit_tests/__init__.py b/apps/authentication/tests/unit_tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/__init__.py b/apps/authentication/tests/unit_tests/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/__init__.py b/apps/authentication/tests/unit_tests/backend/infra/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/__init__.py b/apps/authentication/tests/unit_tests/backend/infra/permission/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/permission_handler/__init__.py b/apps/authentication/tests/unit_tests/backend/infra/permission/permission_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/permission_handler/test_permission_handler.py b/apps/authentication/tests/unit_tests/backend/infra/permission/permission_handler/test_permission_handler.py new file mode 100644 index 0000000..ad3451a --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/infra/permission/permission_handler/test_permission_handler.py @@ -0,0 +1,137 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from fastapi.exceptions import RequestValidationError +from backend.infra.permission.permission_handler import PermissionHandler +from backend.models.permission.models import PermissionDoc, RoleDoc +from beanie import PydanticObjectId + +@pytest.fixture(autouse=True) +def mock_db(): + with patch('backend.infra.permission.permission_handler.PermissionDoc') as MockPermissionDoc, \ + patch('backend.infra.permission.permission_handler.RoleDoc') as MockRoleDoc: + yield MockPermissionDoc, MockRoleDoc + +@pytest.mark.asyncio +class TestPermissionHandler: + @pytest.fixture(autouse=True) + def setup(self, mock_db): + self.MockPermissionDoc, self.MockRoleDoc = mock_db + self.handler = PermissionHandler() + + async def test_create_permission_success(self): + # Test creating a permission successfully + self.MockPermissionDoc.find_one = AsyncMock(side_effect=[None, None]) + mock_doc = MagicMock(spec=PermissionDoc) + self.MockPermissionDoc.return_value = mock_doc + mock_doc.insert = AsyncMock() + result = await self.handler.create_permission('key', 'name', 'desc') + assert result == mock_doc + mock_doc.insert.assert_awaited_once() + + async def test_create_permission_missing_key_or_name(self): + # Test missing permission_key or permission_name raises validation error + with pytest.raises(RequestValidationError): + await self.handler.create_permission('', 'name', 'desc') + with pytest.raises(RequestValidationError): + await self.handler.create_permission('key', '', 'desc') + + async def test_create_permission_duplicate(self): + # Test duplicate permission_key or permission_name raises validation error + self.MockPermissionDoc.find_one = AsyncMock(side_effect=[MagicMock(), None]) + with pytest.raises(RequestValidationError): + await self.handler.create_permission('key', 'name', 'desc') + self.MockPermissionDoc.find_one = AsyncMock(side_effect=[None, MagicMock()]) + with pytest.raises(RequestValidationError): + await self.handler.create_permission('key', 'name', 'desc') + + async def test_update_permission_success(self): + # Test updating a permission successfully + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.is_default = False + self.MockPermissionDoc.get = AsyncMock(return_value=mock_doc) + self.MockPermissionDoc.find_one = AsyncMock(return_value=None) + mock_doc.save = AsyncMock() + result = await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc') + assert result == mock_doc + mock_doc.save.assert_awaited_once() + + async def test_update_permission_missing_args(self): + # Test missing permission_id, permission_key or permission_name raises validation error + with pytest.raises(RequestValidationError): + await self.handler.update_permission(None, 'key', 'name', 'desc') + with pytest.raises(RequestValidationError): + await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), '', 'name', 'desc') + with pytest.raises(RequestValidationError): + await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', '', 'desc') + + async def test_update_permission_not_found(self): + # Test updating a non-existent permission raises validation error + self.MockPermissionDoc.get = AsyncMock(return_value=None) + with pytest.raises(RequestValidationError): + await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc') + + async def test_update_permission_is_default(self): + # Test updating a default permission raises validation error + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.is_default = True + self.MockPermissionDoc.get = AsyncMock(return_value=mock_doc) + with pytest.raises(RequestValidationError): + await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc') + + async def test_update_permission_conflict(self): + # Test updating a permission with duplicate key or name raises validation error + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.is_default = False + self.MockPermissionDoc.get = AsyncMock(return_value=mock_doc) + self.MockPermissionDoc.find_one = AsyncMock(return_value=MagicMock()) + with pytest.raises(RequestValidationError): + await self.handler.update_permission(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc') + + async def test_query_permissions_success(self): + # Test querying permissions returns docs and total + mock_cursor = MagicMock() + mock_cursor.count = AsyncMock(return_value=2) + mock_cursor.skip.return_value = mock_cursor + mock_cursor.limit.return_value = mock_cursor + mock_cursor.to_list = AsyncMock(return_value=['doc1', 'doc2']) + self.MockPermissionDoc.find.return_value = mock_cursor + docs, total = await self.handler.query_permissions('key', 'name', 0, 10) + assert docs == ['doc1', 'doc2'] + assert total == 2 + + async def test_delete_permission_success(self): + # Test deleting a permission successfully + self.MockRoleDoc.find_one = AsyncMock(return_value=None) + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.is_default = False + self.MockPermissionDoc.get = AsyncMock(return_value=mock_doc) + mock_doc.delete = AsyncMock() + await self.handler.delete_permission(PydanticObjectId('507f1f77bcf86cd799439011')) + mock_doc.delete.assert_awaited_once() + + async def test_delete_permission_missing_id(self): + # Test missing permission_id raises validation error + with pytest.raises(RequestValidationError): + await self.handler.delete_permission(None) + + async def test_delete_permission_referenced_by_role(self): + # Test deleting a permission referenced by a role raises validation error + self.MockRoleDoc.find_one = AsyncMock(return_value=MagicMock()) + with pytest.raises(RequestValidationError): + await self.handler.delete_permission(PydanticObjectId('507f1f77bcf86cd799439011')) + + async def test_delete_permission_not_found(self): + # Test deleting a non-existent permission raises validation error + self.MockRoleDoc.find_one = AsyncMock(return_value=None) + self.MockPermissionDoc.get = AsyncMock(return_value=None) + with pytest.raises(RequestValidationError): + await self.handler.delete_permission(PydanticObjectId('507f1f77bcf86cd799439011')) + + async def test_delete_permission_is_default(self): + # Test deleting a default permission raises validation error + self.MockRoleDoc.find_one = AsyncMock(return_value=None) + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.is_default = True + self.MockPermissionDoc.get = AsyncMock(return_value=mock_doc) + with pytest.raises(RequestValidationError): + await self.handler.delete_permission(PydanticObjectId('507f1f77bcf86cd799439011')) \ No newline at end of file diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/role_handler/__init__.py b/apps/authentication/tests/unit_tests/backend/infra/permission/role_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/role_handler/test_role_handler.py b/apps/authentication/tests/unit_tests/backend/infra/permission/role_handler/test_role_handler.py new file mode 100644 index 0000000..e1c553d --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/infra/permission/role_handler/test_role_handler.py @@ -0,0 +1,169 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from fastapi.exceptions import RequestValidationError +from backend.infra.permission.role_handler import RoleHandler +from backend.models.permission.models import RoleDoc, PermissionDoc, UserRoleDoc +from beanie import PydanticObjectId + +@pytest.fixture(autouse=True) +def mock_db(): + with patch('backend.infra.permission.role_handler.RoleDoc') as MockRoleDoc, \ + patch('backend.infra.permission.role_handler.PermissionDoc') as MockPermissionDoc, \ + patch('backend.infra.permission.role_handler.UserRoleDoc') as MockUserRoleDoc: + yield MockRoleDoc, MockPermissionDoc, MockUserRoleDoc + +@pytest.mark.asyncio +class TestRoleHandler: + @pytest.fixture(autouse=True) + def setup(self, mock_db): + self.MockRoleDoc, self.MockPermissionDoc, self.MockUserRoleDoc = mock_db + self.handler = RoleHandler() + + async def test_create_role_success(self): + # Test creating a role successfully + self.MockRoleDoc.find_one = AsyncMock(side_effect=[None, None]) + mock_doc = MagicMock(spec=RoleDoc) + self.MockRoleDoc.return_value = mock_doc + mock_doc.insert = AsyncMock() + result = await self.handler.create_role('key', 'name', 'desc', 1) + assert result == mock_doc + mock_doc.insert.assert_awaited_once() + + async def test_create_role_missing_key_or_name(self): + # Test missing role_key or role_name raises validation error + with pytest.raises(RequestValidationError): + await self.handler.create_role('', 'name', 'desc', 1) + with pytest.raises(RequestValidationError): + await self.handler.create_role('key', '', 'desc', 1) + + async def test_create_role_duplicate(self): + # Test duplicate role_key or role_name raises validation error + self.MockRoleDoc.find_one = AsyncMock(side_effect=[MagicMock(), None]) + with pytest.raises(RequestValidationError): + await self.handler.create_role('key', 'name', 'desc', 1) + self.MockRoleDoc.find_one = AsyncMock(side_effect=[None, MagicMock()]) + with pytest.raises(RequestValidationError): + await self.handler.create_role('key', 'name', 'desc', 1) + + async def test_update_role_success(self): + # Test updating a role successfully + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.is_default = False + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + self.MockRoleDoc.find_one = AsyncMock(return_value=None) + mock_doc.save = AsyncMock() + result = await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc', 1) + assert result == mock_doc + mock_doc.save.assert_awaited_once() + + async def test_update_role_missing_args(self): + # Test missing role_id, role_key or role_name raises validation error + with pytest.raises(RequestValidationError): + await self.handler.update_role(None, 'key', 'name', 'desc', 1) + with pytest.raises(RequestValidationError): + await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), '', 'name', 'desc', 1) + with pytest.raises(RequestValidationError): + await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', '', 'desc', 1) + + async def test_update_role_not_found(self): + # Test updating a non-existent role raises validation error + self.MockRoleDoc.get = AsyncMock(return_value=None) + with pytest.raises(RequestValidationError): + await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc', 1) + + async def test_update_role_is_default(self): + # Test updating a default role raises validation error + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.is_default = True + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + with pytest.raises(RequestValidationError): + await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc', 1) + + async def test_update_role_conflict(self): + # Test updating a role with duplicate key or name raises validation error + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.is_default = False + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + self.MockRoleDoc.find_one = AsyncMock(return_value=MagicMock()) + with pytest.raises(RequestValidationError): + await self.handler.update_role(PydanticObjectId('507f1f77bcf86cd799439011'), 'key', 'name', 'desc', 1) + + async def test_query_roles_success(self): + # Test querying roles returns docs and total + mock_cursor = MagicMock() + mock_cursor.count = AsyncMock(return_value=2) + mock_cursor.skip.return_value = mock_cursor + mock_cursor.limit.return_value = mock_cursor + mock_cursor.to_list = AsyncMock(return_value=['doc1', 'doc2']) + self.MockRoleDoc.find.return_value = mock_cursor + docs, total = await self.handler.query_roles('key', 'name', 0, 10) + assert docs == ['doc1', 'doc2'] + assert total == 2 + + async def test_assign_permissions_to_role_success(self): + # Test assigning permissions to a role successfully + mock_doc = MagicMock(spec=RoleDoc) + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + self.MockPermissionDoc.get = AsyncMock(return_value=MagicMock()) + mock_doc.save = AsyncMock() + result = await self.handler.assign_permissions_to_role(PydanticObjectId('507f1f77bcf86cd799439011'), ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439011']) + assert result == mock_doc + mock_doc.save.assert_awaited_once() + + async def test_assign_permissions_to_role_missing_args(self): + # Test missing role_id or permission_ids raises validation error + with pytest.raises(RequestValidationError): + await self.handler.assign_permissions_to_role(None, ['pid1']) + with pytest.raises(RequestValidationError): + await self.handler.assign_permissions_to_role(PydanticObjectId('507f1f77bcf86cd799439011'), None) + + async def test_assign_permissions_to_role_role_not_found(self): + # Test assigning permissions to a non-existent role raises validation error + self.MockRoleDoc.get = AsyncMock(return_value=None) + with pytest.raises(RequestValidationError): + await self.handler.assign_permissions_to_role(PydanticObjectId('507f1f77bcf86cd799439011'), ['507f1f77bcf86cd799439011']) + + async def test_assign_permissions_to_role_permission_not_found(self): + # Test assigning a non-existent permission raises validation error + mock_doc = MagicMock(spec=RoleDoc) + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + self.MockPermissionDoc.get = AsyncMock(side_effect=[None, MagicMock()]) + with pytest.raises(RequestValidationError): + await self.handler.assign_permissions_to_role(PydanticObjectId('507f1f77bcf86cd799439011'), ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439011']) + + async def test_delete_role_success(self): + # Test deleting a role successfully + self.MockUserRoleDoc.find_one = AsyncMock(return_value=None) + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.is_default = False + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + mock_doc.delete = AsyncMock() + await self.handler.delete_role(PydanticObjectId('507f1f77bcf86cd799439011')) + mock_doc.delete.assert_awaited_once() + + async def test_delete_role_missing_id(self): + # Test missing role_id raises validation error + with pytest.raises(RequestValidationError): + await self.handler.delete_role(None) + + async def test_delete_role_referenced_by_user(self): + # Test deleting a role referenced by a user raises validation error + self.MockUserRoleDoc.find_one = AsyncMock(return_value=MagicMock()) + with pytest.raises(RequestValidationError): + await self.handler.delete_role(PydanticObjectId('507f1f77bcf86cd799439011')) + + async def test_delete_role_not_found(self): + # Test deleting a non-existent role raises validation error + self.MockUserRoleDoc.find_one = AsyncMock(return_value=None) + self.MockRoleDoc.get = AsyncMock(return_value=None) + with pytest.raises(RequestValidationError): + await self.handler.delete_role(PydanticObjectId('507f1f77bcf86cd799439011')) + + async def test_delete_role_is_default(self): + # Test deleting a default role raises validation error + self.MockUserRoleDoc.find_one = AsyncMock(return_value=None) + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.is_default = True + self.MockRoleDoc.get = AsyncMock(return_value=mock_doc) + with pytest.raises(RequestValidationError): + await self.handler.delete_role(PydanticObjectId('507f1f77bcf86cd799439011')) \ No newline at end of file diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/user_role_handler/__init__.py b/apps/authentication/tests/unit_tests/backend/infra/permission/user_role_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/infra/permission/user_role_handler/test_user_role_handler.py b/apps/authentication/tests/unit_tests/backend/infra/permission/user_role_handler/test_user_role_handler.py new file mode 100644 index 0000000..2dcc04e --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/infra/permission/user_role_handler/test_user_role_handler.py @@ -0,0 +1,43 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from backend.infra.permission.user_role_handler import UserRoleHandler +from backend.models.permission.models import RoleDoc, UserRoleDoc, PermissionDoc + +@pytest.fixture(autouse=True) +def mock_db(): + with patch('backend.infra.permission.user_role_handler.RoleDoc') as MockRoleDoc, \ + patch('backend.infra.permission.user_role_handler.UserRoleDoc') as MockUserRoleDoc, \ + patch('backend.infra.permission.user_role_handler.PermissionDoc') as MockPermissionDoc: + yield MockRoleDoc, MockUserRoleDoc, MockPermissionDoc + +@pytest.mark.asyncio +class TestUserRoleHandler: + @pytest.fixture(autouse=True) + def setup(self, mock_db): + self.MockRoleDoc, self.MockUserRoleDoc, self.MockPermissionDoc = mock_db + self.handler = UserRoleHandler() + + async def test_assign_roles_to_user_success(self): + # Test assigning roles to a user when no UserRoleDoc exists (create new) + self.MockRoleDoc.get = AsyncMock(return_value=MagicMock()) + self.MockUserRoleDoc.find_one = AsyncMock(return_value=None) + mock_user_role = MagicMock(spec=UserRoleDoc) + self.MockUserRoleDoc.return_value = mock_user_role + mock_user_role.insert = AsyncMock() + result = await self.handler.assign_roles_to_user('507f1f77bcf86cd799439011', ['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012']) + assert result == mock_user_role + mock_user_role.insert.assert_awaited_once() + + async def test_get_role_and_permission_by_user_id_with_roles_and_permissions(self): + # Test getting roles and permissions when user has roles and permissions + self.MockUserRoleDoc.find_one = AsyncMock(return_value=MagicMock(role_ids=['507f1f77bcf86cd799439010', '507f1f77bcf86cd799439017'])) + mock_role1 = MagicMock(role_name='role1', permission_ids=['507f1f77bcf86cd799439011', '507f1f77bcf86cd799439012']) + mock_role2 = MagicMock(role_name='role2', permission_ids=['507f1f77bcf86cd799439014', '507f1f77bcf86cd799439013']) + self.MockRoleDoc.find.return_value.to_list = AsyncMock(return_value=[mock_role1, mock_role2]) + mock_perm1 = MagicMock(permission_key='perm1') + mock_perm2 = MagicMock(permission_key='perm2') + mock_perm3 = MagicMock(permission_key='perm3') + self.MockPermissionDoc.find.return_value.to_list = AsyncMock(return_value=[mock_perm1, mock_perm2, mock_perm3]) + result = await self.handler.get_role_and_permission_by_user_id('uid') + assert set(result[0]) == {'role1', 'role2'} + assert set(result[1]) == {'perm1', 'perm2', 'perm3'} \ No newline at end of file diff --git a/apps/authentication/tests/unit_tests/backend/permission/__init__.py b/apps/authentication/tests/unit_tests/backend/permission/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/permission/permission_service/__init__.py b/apps/authentication/tests/unit_tests/backend/permission/permission_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/permission/permission_service/test_permission_service.py b/apps/authentication/tests/unit_tests/backend/permission/permission_service/test_permission_service.py new file mode 100644 index 0000000..2087462 --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/permission/permission_service/test_permission_service.py @@ -0,0 +1,152 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from fastapi.exceptions import RequestValidationError +from backend.services.permission.permission_service import PermissionService +from backend.models.permission.models import PermissionDoc + +import datetime + +@pytest.fixture +def mock_permission_handler(): + # Fixture to patch PermissionHandler for isolation and mocking + with patch('backend.services.permission.permission_service.PermissionHandler') as MockHandler: + yield MockHandler + +@pytest.mark.asyncio +class TestPermissionService: + @pytest.fixture(autouse=True) + def setup(self, mock_permission_handler): + # Automatically set up a PermissionService with a mocked handler for each test + self.mock_handler = mock_permission_handler.return_value + self.service = PermissionService() + self.service.permission_handler = self.mock_handler + + async def test_create_permission_success(self): + # Test creating a permission successfully returns the expected PermissionDoc + mock_doc = MagicMock(spec=PermissionDoc) + self.mock_handler.create_permission = AsyncMock(return_value=mock_doc) + result = await self.service.create_permission('key', 'name', 'desc') + assert result == mock_doc + self.mock_handler.create_permission.assert_awaited_once_with('key', 'name', 'desc') + + async def test_create_permission_fail(self): + # Test that an exception is raised if the handler fails to create a permission + self.mock_handler.create_permission = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.create_permission('key', 'name', 'desc') + + async def test_create_permission_with_none_description(self): + # Test creating a permission with None as description + mock_doc = MagicMock(spec=PermissionDoc) + self.mock_handler.create_permission = AsyncMock(return_value=mock_doc) + result = await self.service.create_permission('key', 'name', None) + assert result == mock_doc + self.mock_handler.create_permission.assert_awaited_once_with('key', 'name', None) + + async def test_create_permission_with_empty_description(self): + # Test creating a permission with empty string as description + mock_doc = MagicMock(spec=PermissionDoc) + self.mock_handler.create_permission = AsyncMock(return_value=mock_doc) + result = await self.service.create_permission('key', 'name', '') + assert result == mock_doc + self.mock_handler.create_permission.assert_awaited_once_with('key', 'name', '') + + async def test_create_permission_handler_unexpected_exception(self): + # Test handler raises unexpected exception during creation + self.mock_handler.create_permission = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.create_permission('key', 'name', 'desc') + + async def test_update_permission_success(self): + # Test updating a permission successfully returns the expected PermissionDoc + mock_doc = MagicMock(spec=PermissionDoc) + self.mock_handler.update_permission = AsyncMock(return_value=mock_doc) + result = await self.service.update_permission('507f1f77bcf86cd799439011', 'key', 'name', 'desc') + assert result == mock_doc + self.mock_handler.update_permission.assert_awaited_once() + + async def test_update_permission_fail(self): + # Test that an exception is raised if the handler fails to update a permission + self.mock_handler.update_permission = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.update_permission('507f1f77bcf86cd799439011', 'key', 'name', 'desc') + + async def test_update_permission_with_invalid_id(self): + # Test updating a permission with invalid id (empty string) + self.mock_handler.update_permission = AsyncMock(side_effect=RequestValidationError('invalid id')) + with pytest.raises(RequestValidationError): + await self.service.update_permission('507f1f77bcf86cd799439011', 'key', 'name', 'desc') + + async def test_update_permission_handler_unexpected_exception(self): + # Test handler raises unexpected exception during update + self.mock_handler.update_permission = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.update_permission('507f1f77bcf86cd799439011', 'key', 'name', 'desc') + + async def test_update_permission_partial_args(self): + # Test updating a permission with only key provided + mock_doc = MagicMock(spec=PermissionDoc) + self.mock_handler.update_permission = AsyncMock(return_value=mock_doc) + result = await self.service.update_permission('507f1f77bcf86cd799439011', 'key', None, None) + assert result == mock_doc + self.mock_handler.update_permission.assert_awaited_once() + + async def test_query_permissions_success(self): + # Test querying permissions returns a paginated result with correct items and meta + mock_doc = MagicMock(spec=PermissionDoc) + mock_doc.dict.return_value = {'permission_key': 'key', 'permission_name': 'name'} + self.mock_handler.query_permissions = AsyncMock(return_value=([mock_doc], 1)) + result = await self.service.query_permissions('key', 'name', 1, 10) + assert result['items'] == [{'permission_key': 'key', 'permission_name': 'name'}] + assert result['total'] == 1 + assert result['page'] == 1 + assert result['page_size'] == 10 + self.mock_handler.query_permissions.assert_awaited_once_with('key', 'name', 0, 10) + + async def test_query_permissions_handler_exception(self): + # Test handler raises exception during query + self.mock_handler.query_permissions = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.query_permissions('key', 'name', 1, 10) + + async def test_query_permissions_empty_result(self): + # Test query returns empty list + self.mock_handler.query_permissions = AsyncMock(return_value=([], 0)) + result = await self.service.query_permissions('key', 'name', 1, 10) + assert result['items'] == [] + assert result['total'] == 0 + assert result['page'] == 1 + assert result['page_size'] == 10 + + async def test_query_permissions_with_none_and_special_chars(self): + # Test query with None and special characters + self.mock_handler.query_permissions = AsyncMock(return_value=([], 0)) + result = await self.service.query_permissions(None, None, 1, 10) + assert result['items'] == [] + result2 = await self.service.query_permissions('!@#$', '', 1, 10) + assert result2['items'] == [] + + @pytest.mark.parametrize('page,page_size', [(0, 10), (1, 0), (0, 0), (-1, 10), (1, -1)]) + async def test_query_permissions_invalid_page(self, page, page_size): + # Test that invalid page or page_size raises a validation error + with pytest.raises(RequestValidationError): + await self.service.query_permissions('key', 'name', page, page_size) + + async def test_delete_permission_success(self): + # Test deleting a permission successfully returns None + self.mock_handler.delete_permission = AsyncMock(return_value=None) + result = await self.service.delete_permission('507f1f77bcf86cd799439011') + assert result is None + self.mock_handler.delete_permission.assert_awaited_once() + + async def test_delete_permission_fail(self): + # Test that an exception is raised if the handler fails to delete a permission + self.mock_handler.delete_permission = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.delete_permission('507f1f77bcf86cd799439011') + + async def test_delete_permission_handler_unexpected_exception(self): + # Test handler raises unexpected exception during delete + self.mock_handler.delete_permission = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.delete_permission('507f1f77bcf86cd799439011') \ No newline at end of file diff --git a/apps/authentication/tests/unit_tests/backend/permission/role_service/__init__.py b/apps/authentication/tests/unit_tests/backend/permission/role_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/permission/role_service/test_role_service.py b/apps/authentication/tests/unit_tests/backend/permission/role_service/test_role_service.py new file mode 100644 index 0000000..77d858e --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/permission/role_service/test_role_service.py @@ -0,0 +1,172 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from fastapi.exceptions import RequestValidationError +from backend.services.permission.role_service import RoleService +from backend.models.permission.models import RoleDoc + +@pytest.fixture +def mock_role_handler(): + # Fixture to patch RoleHandler for isolation and mocking + with patch('backend.services.permission.role_service.RoleHandler') as MockHandler: + yield MockHandler + +@pytest.mark.asyncio +class TestRoleService: + @pytest.fixture(autouse=True) + def setup(self, mock_role_handler): + # Automatically set up a RoleService with a mocked handler for each test + self.mock_handler = mock_role_handler.return_value + self.service = RoleService() + self.service.role_handler = self.mock_handler + + async def test_create_role_success(self): + # Test creating a role successfully returns the expected RoleDoc + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.create_role = AsyncMock(return_value=mock_doc) + result = await self.service.create_role('key', 'name', 'desc', 1) + assert result == mock_doc + self.mock_handler.create_role.assert_awaited_once_with('key', 'name', 'desc', 1) + + async def test_create_role_fail(self): + # Test that an exception is raised if the handler fails to create a role + self.mock_handler.create_role = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.create_role('key', 'name', 'desc', 1) + + async def test_create_role_with_none_description(self): + # Test creating a role with None as description + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.create_role = AsyncMock(return_value=mock_doc) + result = await self.service.create_role('key', 'name', None, 1) + assert result == mock_doc + self.mock_handler.create_role.assert_awaited_once_with('key', 'name', None, 1) + + async def test_create_role_with_empty_description(self): + # Test creating a role with empty string as description + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.create_role = AsyncMock(return_value=mock_doc) + result = await self.service.create_role('key', 'name', '', 1) + assert result == mock_doc + self.mock_handler.create_role.assert_awaited_once_with('key', 'name', '', 1) + + async def test_create_role_handler_unexpected_exception(self): + # Test handler raises unexpected exception during creation + self.mock_handler.create_role = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.create_role('key', 'name', 'desc', 1) + + async def test_update_role_success(self): + # Test updating a role successfully returns the expected RoleDoc + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.update_role = AsyncMock(return_value=mock_doc) + result = await self.service.update_role('507f1f77bcf86cd799439011', 'key', 'name', 'desc', 1) + assert result == mock_doc + self.mock_handler.update_role.assert_awaited_once() + + async def test_update_role_fail(self): + # Test that an exception is raised if the handler fails to update a role + self.mock_handler.update_role = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.update_role('507f1f77bcf86cd799439011', 'key', 'name', 'desc', 1) + + async def test_update_role_handler_unexpected_exception(self): + # Test handler raises unexpected exception during update + self.mock_handler.update_role = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.update_role('507f1f77bcf86cd799439011', 'key', 'name', 'desc', 1) + + async def test_update_role_partial_args(self): + # Test updating a role with only key provided + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.update_role = AsyncMock(return_value=mock_doc) + result = await self.service.update_role('507f1f77bcf86cd799439011', 'key', None, None, 1) + assert result == mock_doc + self.mock_handler.update_role.assert_awaited_once() + + async def test_query_roles_success(self): + # Test querying roles returns a paginated result with correct items and meta + mock_doc = MagicMock(spec=RoleDoc) + mock_doc.dict.return_value = {'role_key': 'key', 'role_name': 'name'} + self.mock_handler.query_roles = AsyncMock(return_value=([mock_doc], 1)) + result = await self.service.query_roles('key', 'name', 1, 10) + assert result['items'] == [{'role_key': 'key', 'role_name': 'name'}] + assert result['total'] == 1 + assert result['page'] == 1 + assert result['page_size'] == 10 + self.mock_handler.query_roles.assert_awaited_once_with('key', 'name', 0, 10) + + async def test_query_roles_handler_exception(self): + # Test handler raises exception during query + self.mock_handler.query_roles = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.query_roles('key', 'name', 1, 10) + + async def test_query_roles_empty_result(self): + # Test query returns empty list + self.mock_handler.query_roles = AsyncMock(return_value=([], 0)) + result = await self.service.query_roles('key', 'name', 1, 10) + assert result['items'] == [] + assert result['total'] == 0 + assert result['page'] == 1 + assert result['page_size'] == 10 + + async def test_query_roles_with_none_and_special_chars(self): + # Test query with None and special characters + self.mock_handler.query_roles = AsyncMock(return_value=([], 0)) + result = await self.service.query_roles(None, None, 1, 10) + assert result['items'] == [] + result2 = await self.service.query_roles('!@#$', '', 1, 10) + assert result2['items'] == [] + + @pytest.mark.parametrize('page,page_size', [(0, 10), (1, 0), (0, 0), (-1, 10), (1, -1)]) + async def test_query_roles_invalid_page(self, page, page_size): + # Test that invalid page or page_size raises a validation error + with pytest.raises(RequestValidationError): + await self.service.query_roles('key', 'name', page, page_size) + + async def test_assign_permissions_to_role_success(self): + # Test assigning permissions to a role returns the updated RoleDoc + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.assign_permissions_to_role = AsyncMock(return_value=mock_doc) + result = await self.service.assign_permissions_to_role('507f1f77bcf86cd799439011', ['pid1', 'pid2']) + assert result == mock_doc + self.mock_handler.assign_permissions_to_role.assert_awaited_once() + + async def test_assign_permissions_to_role_fail(self): + # Test that an exception is raised if the handler fails to assign permissions + self.mock_handler.assign_permissions_to_role = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.assign_permissions_to_role('507f1f77bcf86cd799439011', ['pid1', 'pid2']) + + async def test_assign_permissions_to_role_empty_list(self): + # Test assigning permissions to a role with empty permission_ids list + mock_doc = MagicMock(spec=RoleDoc) + self.mock_handler.assign_permissions_to_role = AsyncMock(return_value=mock_doc) + result = await self.service.assign_permissions_to_role('507f1f77bcf86cd799439011', []) + assert result == mock_doc + self.mock_handler.assign_permissions_to_role.assert_awaited_once() + + async def test_assign_permissions_to_role_handler_unexpected_exception(self): + # Test handler raises unexpected exception during assign + self.mock_handler.assign_permissions_to_role = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.assign_permissions_to_role('507f1f77bcf86cd799439011', ['pid1']) + + async def test_delete_role_success(self): + # Test deleting a role successfully returns None + self.mock_handler.delete_role = AsyncMock(return_value=None) + result = await self.service.delete_role('507f1f77bcf86cd799439011') + assert result is None + self.mock_handler.delete_role.assert_awaited_once() + + async def test_delete_role_fail(self): + # Test that an exception is raised if the handler fails to delete a role + self.mock_handler.delete_role = AsyncMock(side_effect=RequestValidationError('error')) + with pytest.raises(RequestValidationError): + await self.service.delete_role('507f1f77bcf86cd799439011') + + async def test_delete_role_handler_unexpected_exception(self): + # Test handler raises unexpected exception during delete + self.mock_handler.delete_role = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.delete_role('507f1f77bcf86cd799439011') \ No newline at end of file diff --git a/apps/authentication/tests/unit_tests/backend/permission/user_role_handler/__init__.py b/apps/authentication/tests/unit_tests/backend/permission/user_role_handler/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/user/__init__.py b/apps/authentication/tests/unit_tests/backend/user/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/user/user_management_service/__init__.py b/apps/authentication/tests/unit_tests/backend/user/user_management_service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/authentication/tests/unit_tests/backend/user/user_management_service/test_user_management_service.py b/apps/authentication/tests/unit_tests/backend/user/user_management_service/test_user_management_service.py new file mode 100644 index 0000000..8787839 --- /dev/null +++ b/apps/authentication/tests/unit_tests/backend/user/user_management_service/test_user_management_service.py @@ -0,0 +1,172 @@ +import pytest +from unittest.mock import AsyncMock, patch, MagicMock +from backend.services.user.user_management_service import UserManagementService +from backend.models.user.models import UserAccountDoc +from backend.models.permission.models import UserRoleDoc +from backend.models.user.constants import NewUserMethod +from common.constants.region import UserRegion + +@pytest.fixture +def mock_handlers(): + with patch('backend.services.user.user_management_service.UserProfileHandler') as MockProfileHandler, \ + patch('backend.services.user.user_management_service.UserAuthHandler') as MockAuthHandler, \ + patch('backend.services.user.user_management_service.UserRoleHandler') as MockRoleHandler: + yield MockProfileHandler, MockAuthHandler, MockRoleHandler + +@pytest.mark.asyncio +class TestUserManagementService: + @pytest.fixture(autouse=True) + def setup(self, mock_handlers): + # Automatically set up a UserManagementService with mocked handlers for each test + MockProfileHandler, MockAuthHandler, MockRoleHandler = mock_handlers + self.mock_profile_handler = MockProfileHandler.return_value + self.mock_auth_handler = MockAuthHandler.return_value + self.mock_role_handler = MockRoleHandler.return_value + self.service = UserManagementService() + self.service.user_profile_handler = self.mock_profile_handler + self.service.user_auth_handler = self.mock_auth_handler + self.service.user_role_handler = self.mock_role_handler + + async def test_create_new_user_account_email(self): + # Test creating a new user account with EMAIL method + mock_account = MagicMock(spec=UserAccountDoc) + self.mock_profile_handler.create_new_user_account = AsyncMock(return_value=mock_account) + result = await self.service.create_new_user_account(NewUserMethod.EMAIL, UserRegion.ZH_CN) + assert result == mock_account + self.mock_profile_handler.create_new_user_account.assert_awaited_once() + + async def test_create_new_user_account_mobile(self): + # Test creating a new user account with MOBILE method + mock_account = MagicMock(spec=UserAccountDoc) + self.mock_profile_handler.create_new_user_account = AsyncMock(return_value=mock_account) + result = await self.service.create_new_user_account(NewUserMethod.MOBILE, UserRegion.ZH_CN) + assert result == mock_account + self.mock_profile_handler.create_new_user_account.assert_awaited_once() + + + async def test_create_new_user_account_handler_exception(self): + # Test handler exception is propagated when creating a new user account + self.mock_profile_handler.create_new_user_account = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.create_new_user_account(NewUserMethod.EMAIL, UserRegion.ZH_CN) + + async def test_initialize_new_user_data_email(self): + # Test initializing new user data with EMAIL method + self.mock_profile_handler.create_basic_profile = AsyncMock() + self.mock_auth_handler.save_email_auth_method = AsyncMock() + self.mock_profile_handler.create_provider_profile = AsyncMock() + result = await self.service.initialize_new_user_data('uid', NewUserMethod.EMAIL, email_address='a@b.com') + assert result is True + self.mock_profile_handler.create_basic_profile.assert_awaited_once() + self.mock_auth_handler.save_email_auth_method.assert_awaited_once() + self.mock_profile_handler.create_provider_profile.assert_awaited_once() + + async def test_initialize_new_user_data_mobile(self): + # Test initializing new user data with MOBILE method + self.mock_profile_handler.create_basic_profile = AsyncMock() + self.mock_profile_handler.create_provider_profile = AsyncMock() + result = await self.service.initialize_new_user_data('uid', NewUserMethod.MOBILE, mobile_number='123456') + assert result is True + self.mock_profile_handler.create_basic_profile.assert_awaited_once() + self.mock_profile_handler.create_provider_profile.assert_awaited_once() + + async def test_initialize_new_user_data_other(self): + # Test initializing new user data with unsupported method returns False + result = await self.service.initialize_new_user_data('uid', 'OTHER') + assert result is False + + async def test_initialize_new_user_data_email_handler_exception(self): + # Test exception in create_basic_profile is propagated + self.mock_profile_handler.create_basic_profile = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.initialize_new_user_data('uid', NewUserMethod.EMAIL, email_address='a@b.com') + + async def test_initialize_new_user_data_mobile_handler_exception(self): + # Test exception in create_basic_profile for MOBILE is propagated + self.mock_profile_handler.create_basic_profile = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.initialize_new_user_data('uid', NewUserMethod.MOBILE, mobile_number='123456') + + async def test_initialize_new_user_data_email_missing_email(self): + # Test initializing new user data with EMAIL method but missing email_address + self.mock_profile_handler.create_basic_profile = AsyncMock() + self.mock_auth_handler.save_email_auth_method = AsyncMock() + self.mock_profile_handler.create_provider_profile = AsyncMock() + # Should still call with None, but may not be valid in real logic + result = await self.service.initialize_new_user_data('uid', NewUserMethod.EMAIL) + assert result is True + self.mock_profile_handler.create_basic_profile.assert_awaited_once() + self.mock_auth_handler.save_email_auth_method.assert_awaited_once() + self.mock_profile_handler.create_provider_profile.assert_awaited_once() + + async def test_initialize_new_user_data_mobile_missing_mobile(self): + # Test initializing new user data with MOBILE method but missing mobile_number + self.mock_profile_handler.create_basic_profile = AsyncMock() + self.mock_profile_handler.create_provider_profile = AsyncMock() + result = await self.service.initialize_new_user_data('uid', NewUserMethod.MOBILE) + assert result is True + self.mock_profile_handler.create_basic_profile.assert_awaited_once() + self.mock_profile_handler.create_provider_profile.assert_awaited_once() + + async def test_initialize_new_user_data_provider_profile_exception(self): + # Test exception in create_provider_profile is propagated + self.mock_profile_handler.create_basic_profile = AsyncMock() + self.mock_auth_handler.save_email_auth_method = AsyncMock() + self.mock_profile_handler.create_provider_profile = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.initialize_new_user_data('uid', NewUserMethod.EMAIL, email_address='a@b.com') + + async def test_get_account_by_id(self): + # Test getting account by user id + mock_account = MagicMock(spec=UserAccountDoc) + self.mock_profile_handler.get_account_by_id = AsyncMock(return_value=mock_account) + result = await self.service.get_account_by_id('uid') + assert result == mock_account + self.mock_profile_handler.get_account_by_id.assert_awaited_once_with('uid') + + async def test_get_account_by_id_none(self): + # Test getting account by user id returns None if not found + self.mock_profile_handler.get_account_by_id = AsyncMock(return_value=None) + result = await self.service.get_account_by_id('uid') + assert result is None + self.mock_profile_handler.get_account_by_id.assert_awaited_once_with('uid') + + async def test_get_account_by_id_exception(self): + # Test exception in get_account_by_id is propagated + self.mock_profile_handler.get_account_by_id = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.get_account_by_id('uid') + + async def test_assign_roles_to_user(self): + # Test assigning roles to user + mock_user_role = MagicMock(spec=UserRoleDoc) + self.mock_role_handler.assign_roles_to_user = AsyncMock(return_value=mock_user_role) + result = await self.service.assign_roles_to_user('uid', ['rid1', 'rid2']) + assert result == mock_user_role + self.mock_role_handler.assign_roles_to_user.assert_awaited_once_with('uid', ['rid1', 'rid2']) + + async def test_assign_roles_to_user_handler_exception(self): + # Test exception in assign_roles_to_user is propagated + self.mock_role_handler.assign_roles_to_user = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.assign_roles_to_user('uid', ['rid1', 'rid2']) + + async def test_get_role_and_permission_by_user_id(self): + # Test getting role and permission by user id + self.mock_role_handler.get_role_and_permission_by_user_id = AsyncMock(return_value=(['role1'], ['perm1'])) + result = await self.service.get_role_and_permission_by_user_id('uid') + assert result == (['role1'], ['perm1']) + self.mock_role_handler.get_role_and_permission_by_user_id.assert_awaited_once_with('uid') + + async def test_get_role_and_permission_by_user_id_empty(self): + # Test getting role and permission by user id returns empty lists if no roles/permissions + self.mock_role_handler.get_role_and_permission_by_user_id = AsyncMock(return_value=([], [])) + result = await self.service.get_role_and_permission_by_user_id('uid') + assert result == ([], []) + self.mock_role_handler.get_role_and_permission_by_user_id.assert_awaited_once_with('uid') + + async def test_get_role_and_permission_by_user_id_exception(self): + # Test exception in get_role_and_permission_by_user_id is propagated + self.mock_role_handler.get_role_and_permission_by_user_id = AsyncMock(side_effect=Exception('db error')) + with pytest.raises(Exception): + await self.service.get_role_and_permission_by_user_id('uid') \ No newline at end of file diff --git a/apps/authentication/webapi/__init__.py b/apps/authentication/webapi/__init__.py new file mode 100644 index 0000000..e69de29