From ffa12f02556f2341c427fce5cf1388d70abb58aa Mon Sep 17 00:00:00 2001 From: Samir Boulahtit Date: Thu, 19 Feb 2026 17:49:42 +0100 Subject: [PATCH] =?UTF-8?q?test:=20complete=20remaining=20deps.py=20phases?= =?UTF-8?q?=20=E2=80=94=20platform,=20module,=20merchant,=20customer,=20pe?= =?UTF-8?q?rmissions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 16 tests covering: require_platform_access (super admin bypass, platform admin with/without access), get_admin_with_platform_context, require_module_access (super admin bypass, enabled/disabled module), and get_user_permissions (owner gets all, member gets specific, empty). Total: 89 tests for app/api/deps.py (all 31 functions covered). Co-Authored-By: Claude Opus 4.6 --- tests/unit/api/test_deps.py | 280 ++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) diff --git a/tests/unit/api/test_deps.py b/tests/unit/api/test_deps.py index 89396dbd..cd25aecc 100644 --- a/tests/unit/api/test_deps.py +++ b/tests/unit/api/test_deps.py @@ -18,6 +18,7 @@ from app.api.deps import ( _get_user_model, _validate_customer_token, _validate_user_token, + get_admin_with_platform_context, get_current_admin_api, get_current_admin_from_cookie_or_header, get_current_admin_optional, @@ -32,9 +33,12 @@ from app.api.deps import ( get_current_store_optional, get_current_super_admin, get_current_super_admin_api, + get_user_permissions, get_user_store, require_all_store_permissions, require_any_store_permission, + require_module_access, + require_platform_access, require_store_owner, require_store_permission, ) @@ -1300,3 +1304,279 @@ class TestGetUserStore: other_ctx = UserContext.from_user(other_user) with pytest.raises(UnauthorizedStoreAccessException): get_user_store(store.store_code, other_ctx, db) + + +# ============================================================================ +# Phase 2 (advanced): Platform Access & Context +# ============================================================================ + + +@pytest.mark.unit +@pytest.mark.auth +class TestRequirePlatformAccess: + """Test require_platform_access factory.""" + + def test_super_admin_can_access_any_platform(self, db, auth_manager, test_super_admin): + """Super admin (accessible_platform_ids=None) can access any platform.""" + token_data = auth_manager.create_access_token(user=test_super_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/platforms/42/stores") + + checker = require_platform_access(42) + result = checker(request, creds, None, db) + assert result.id == test_super_admin.id + assert result.accessible_platform_ids is None + + def test_platform_admin_with_access(self, db, auth_manager, test_platform_admin): + """Platform admin with matching platform_id is accepted.""" + token_data = auth_manager.create_access_token(user=test_platform_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/platforms/1/stores") + + checker = require_platform_access(1) + + # Patch get_current_admin to return context with platform access + admin_ctx = UserContext.from_user(test_platform_admin) + admin_ctx.accessible_platform_ids = [1, 2, 3] + + with patch( + "app.api.deps.get_current_admin_from_cookie_or_header", + return_value=admin_ctx, + ): + result = checker(request, creds, None, db) + assert result.id == test_platform_admin.id + + def test_platform_admin_without_access(self, db, auth_manager, test_platform_admin): + """Platform admin without matching platform_id is rejected.""" + token_data = auth_manager.create_access_token(user=test_platform_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/platforms/9999/stores") + + checker = require_platform_access(9999) + + admin_ctx = UserContext.from_user(test_platform_admin) + admin_ctx.accessible_platform_ids = [1, 2, 3] + + with patch( + "app.api.deps.get_current_admin_from_cookie_or_header", + return_value=admin_ctx, + ): + with pytest.raises(InsufficientPermissionsException, match="Access denied to platform"): + checker(request, creds, None, db) + + def test_rejects_non_admin(self, db, auth_manager, test_store_user): + """Non-admin user rejected before platform check.""" + token_data = auth_manager.create_access_token(user=test_store_user) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/platforms/1/stores") + + checker = require_platform_access(1) + with pytest.raises(AdminRequiredException): + checker(request, creds, None, db) + + +@pytest.mark.unit +@pytest.mark.auth +class TestGetAdminWithPlatformContext: + """Test get_admin_with_platform_context.""" + + def test_super_admin_bypasses_platform_context(self, db, auth_manager, test_super_admin): + """Super admin returns context without platform check.""" + token_data = auth_manager.create_access_token(user=test_super_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/dashboard") + + result = get_admin_with_platform_context(request, creds, None, db) + assert result.id == test_super_admin.id + assert result.is_super_admin is True + + def test_rejects_non_admin(self, db, auth_manager, test_store_user): + """Non-admin user rejected.""" + token_data = auth_manager.create_access_token(user=test_store_user) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/dashboard") + + with pytest.raises(AdminRequiredException): + get_admin_with_platform_context(request, creds, None, db) + + def test_rejects_without_token(self, db): + """No token raises InvalidTokenException.""" + request = _make_request("/admin/dashboard") + + with pytest.raises(InvalidTokenException): + get_admin_with_platform_context(request, None, None, db) + + +# ============================================================================ +# Phase 6 (advanced): Module & Menu Access Control +# ============================================================================ + + +@pytest.mark.unit +@pytest.mark.auth +class TestRequireModuleAccess: + """Test require_module_access factory.""" + + def test_super_admin_bypasses_module_check(self, db, auth_manager, test_super_admin): + """Super admin bypasses module enablement check entirely.""" + from app.modules.enums import FrontendType + + token_data = auth_manager.create_access_token(user=test_super_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/billing") + + checker = require_module_access("billing", FrontendType.ADMIN) + result = checker(request, creds, None, None, None, db) + assert result.id == test_super_admin.id + + def test_store_user_with_enabled_module(self, db, auth_manager, test_store_user): + """Store user can access enabled module.""" + from app.modules.enums import FrontendType + + token_data = auth_manager.create_access_token(user=test_store_user) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/store/inventory") + request.state.store = None + + checker = require_module_access("inventory", FrontendType.STORE) + # No platform context → access is allowed (module check requires platform) + result = checker(request, creds, None, token_data["access_token"], None, db) + assert result.id == test_store_user.id + + def test_rejects_disabled_module(self, db, auth_manager, test_platform_admin): + """Platform admin blocked when module is disabled.""" + from app.modules.enums import FrontendType + + token_data = auth_manager.create_access_token(user=test_platform_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/billing") + # Set platform context on request state + mock_platform = MagicMock() + mock_platform.id = 1 + request.state.admin_platform = mock_platform + + checker = require_module_access("billing", FrontendType.ADMIN) + with patch( + "app.modules.service.module_service.is_module_enabled", return_value=False + ): + with pytest.raises( + InsufficientPermissionsException, match="not enabled" + ): + checker(request, creds, None, None, None, db) + + def test_allows_enabled_module(self, db, auth_manager, test_platform_admin): + """Platform admin can access enabled module.""" + from app.modules.enums import FrontendType + + token_data = auth_manager.create_access_token(user=test_platform_admin) + creds = _make_credentials(token_data["access_token"]) + request = _make_request("/admin/billing") + mock_platform = MagicMock() + mock_platform.id = 1 + request.state.admin_platform = mock_platform + + checker = require_module_access("billing", FrontendType.ADMIN) + with patch( + "app.modules.service.module_service.is_module_enabled", return_value=True + ): + result = checker(request, creds, None, None, None, db) + assert result.id == test_platform_admin.id + + def test_no_auth_raises(self, db): + """No valid authentication raises InvalidTokenException.""" + from app.modules.enums import FrontendType + + request = _make_request("/admin/billing") + + checker = require_module_access("billing", FrontendType.ADMIN) + with pytest.raises(InvalidTokenException, match="Authentication required"): + checker(request, None, None, None, None, db) + + +# ============================================================================ +# Phase 7 remaining: get_user_permissions +# ============================================================================ + + +@pytest.mark.unit +@pytest.mark.auth +class TestGetUserPermissions: + """Test get_user_permissions.""" + + def test_returns_empty_for_no_store_context(self, db, auth_manager, test_store_user): + """Returns empty list if token has no store context.""" + request = _make_request("/store/dashboard") + user_ctx = UserContext.from_user(test_store_user) + user_ctx.token_store_id = None + + result = get_user_permissions(request, db, user_ctx) + assert result == [] + + def test_owner_gets_all_permissions(self, db, auth_manager, test_store_user): + """Store owner gets all available permissions.""" + mock_store = MagicMock() + mock_store.id = 1 + mock_store.store_code = "TEST" + + request = _make_request("/store/dashboard") + user_ctx = UserContext.from_user(test_store_user) + user_ctx.token_store_id = 1 + + all_perms = ["products.view", "products.edit", "orders.view", "orders.edit"] + + with ( + patch("app.api.deps.store_service.get_store_by_id", return_value=mock_store), + patch.object(User, "is_owner_of", return_value=True), + patch( + "app.modules.tenancy.services.permission_discovery_service.permission_discovery_service.get_all_permission_ids", + return_value=all_perms, + ), + ): + result = get_user_permissions(request, db, user_ctx) + assert result == all_perms + + def test_non_owner_gets_membership_permissions(self, db, auth_manager, test_store_user): + """Non-owner team member gets permissions from their membership.""" + mock_store = MagicMock() + mock_store.id = 1 + mock_store.store_code = "TEST" + + request = _make_request("/store/dashboard") + user_ctx = UserContext.from_user(test_store_user) + user_ctx.token_store_id = 1 + + # Mock a store membership with specific permissions + mock_membership = MagicMock() + mock_membership.store_id = 1 + mock_membership.is_active = True + mock_membership.get_all_permissions.return_value = ["products.view", "orders.view"] + + with ( + patch("app.api.deps.store_service.get_store_by_id", return_value=mock_store), + patch.object(User, "is_owner_of", return_value=False), + patch.object( + User, "store_memberships", new_callable=lambda: property(lambda self: [mock_membership]) + ), + ): + result = get_user_permissions(request, db, user_ctx) + assert result == ["products.view", "orders.view"] + + def test_non_member_gets_empty_list(self, db, auth_manager, test_store_user): + """Non-owner with no active membership gets empty list.""" + mock_store = MagicMock() + mock_store.id = 1 + mock_store.store_code = "TEST" + + request = _make_request("/store/dashboard") + user_ctx = UserContext.from_user(test_store_user) + user_ctx.token_store_id = 1 + + with ( + patch("app.api.deps.store_service.get_store_by_id", return_value=mock_store), + patch.object(User, "is_owner_of", return_value=False), + patch.object( + User, "store_memberships", new_callable=lambda: property(lambda self: []) + ), + ): + result = get_user_permissions(request, db, user_ctx) + assert result == []