test: complete remaining deps.py phases — platform, module, merchant, customer, permissions

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 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 17:49:42 +01:00
parent 93731b7173
commit ffa12f0255

View File

@@ -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 == []