"""
Unit tests for permission functions and classes.

Focus on testing permission logic with mocks to avoid database hits.
Tests cover hijack permissions, quota permissions, and feature permissions.
"""
import pytest
from unittest.mock import Mock, MagicMock, patch
from django.core.exceptions import PermissionDenied
from rest_framework.exceptions import PermissionDenied as DRFPermissionDenied

from smoothschedule.identity.core.permissions import (
    can_hijack,
    can_hijack_or_403,
    get_hijackable_users,
    validate_hijack_chain,
    HasQuota,
    HasFeaturePermission,
)


class TestCanHijack:
    """Test can_hijack permission function."""

    def test_cannot_hijack_self(self):
        """Should prevent self-hijacking."""
        user = Mock(id=1, role='SUPERUSER')

        result = can_hijack(hijacker=user, hijacked=user)

        assert result is False

    def test_only_superuser_can_hijack_superuser(self):
        """Should prevent non-superusers from hijacking superusers."""
        hijacker = Mock(id=1, role='PLATFORM_SUPPORT')
        hijacked = Mock(id=2, role='SUPERUSER')

        result = can_hijack(hijacker=hijacker, hijacked=hijacked)

        assert result is False

    def test_superuser_can_hijack_superuser(self):
        """Should allow superuser to hijack another superuser."""
        hijacker = Mock(id=1, role='SUPERUSER')
        hijacked = Mock(id=2, role='SUPERUSER')

        result = can_hijack(hijacker=hijacker, hijacked=hijacked)

        assert result is True

    def test_superuser_can_hijack_anyone(self):
        """Should allow superuser to hijack any user."""
        hijacker = Mock(id=1, role='SUPERUSER')

        test_roles = [
            'PLATFORM_SUPPORT',
            'PLATFORM_SALES',
            'TENANT_OWNER',
            'TENANT_MANAGER',
            'TENANT_STAFF',
            'CUSTOMER',
        ]

        for role in test_roles:
            hijacked = Mock(id=2, role=role)
            result = can_hijack(hijacker=hijacker, hijacked=hijacked)
            assert result is True, f"Superuser should be able to hijack {role}"

    def test_platform_support_can_hijack_tenant_users(self):
        """Should allow platform support to hijack tenant-level users."""
        hijacker = Mock(id=1, role='PLATFORM_SUPPORT')

        allowed_roles = ['TENANT_OWNER', 'TENANT_MANAGER', 'TENANT_STAFF', 'CUSTOMER']

        for role in allowed_roles:
            hijacked = Mock(id=2, role=role)
            result = can_hijack(hijacker=hijacker, hijacked=hijacked)
            assert result is True, f"Platform support should be able to hijack {role}"

    def test_platform_support_cannot_hijack_platform_users(self):
        """Should prevent platform support from hijacking other platform users."""
        hijacker = Mock(id=1, role='PLATFORM_SUPPORT')

        forbidden_roles = ['SUPERUSER', 'PLATFORM_MANAGER', 'PLATFORM_SALES']

        for role in forbidden_roles:
            hijacked = Mock(id=2, role=role)
            result = can_hijack(hijacker=hijacker, hijacked=hijacked)
            assert result is False, f"Platform support should NOT be able to hijack {role}"

    def test_platform_sales_can_only_hijack_temporary_users(self):
        """Should allow platform sales to only hijack temporary demo accounts."""
        hijacker = Mock(id=1, role='PLATFORM_SALES')

        # Temporary user - allowed
        hijacked_temp = Mock(id=2, role='TENANT_OWNER', is_temporary=True)
        assert can_hijack(hijacker=hijacker, hijacked=hijacked_temp) is True

        # Non-temporary user - forbidden
        hijacked_perm = Mock(id=3, role='TENANT_OWNER', is_temporary=False)
        assert can_hijack(hijacker=hijacker, hijacked=hijacked_perm) is False

    def test_tenant_owner_can_hijack_within_same_tenant(self):
        """Should allow tenant owner to hijack staff in same tenant."""
        tenant = Mock(id=1)
        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=tenant)

        allowed_roles = ['TENANT_MANAGER', 'TENANT_STAFF', 'CUSTOMER']

        for role in allowed_roles:
            hijacked = Mock(id=2, role=role, tenant=tenant)
            result = can_hijack(hijacker=hijacker, hijacked=hijacked)
            assert result is True, f"Tenant owner should be able to hijack {role} in same tenant"

    def test_tenant_owner_cannot_hijack_different_tenant(self):
        """Should prevent tenant owner from hijacking users in different tenant."""
        tenant1 = Mock(id=1)
        tenant2 = Mock(id=2)

        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=tenant1)
        hijacked = Mock(id=2, role='TENANT_STAFF', tenant=tenant2)

        result = can_hijack(hijacker=hijacker, hijacked=hijacked)

        assert result is False

    def test_tenant_owner_cannot_hijack_without_tenant(self):
        """Should prevent hijacking when tenant is None."""
        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=None)
        hijacked = Mock(id=2, role='TENANT_STAFF', tenant=None)

        result = can_hijack(hijacker=hijacker, hijacked=hijacked)

        assert result is False

    def test_tenant_owner_cannot_hijack_other_owners(self):
        """Should prevent tenant owner from hijacking other owners."""
        tenant = Mock(id=1)

        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=tenant)
        hijacked = Mock(id=2, role='TENANT_OWNER', tenant=tenant)

        result = can_hijack(hijacker=hijacker, hijacked=hijacked)

        assert result is False

    def test_other_roles_cannot_hijack(self):
        """Should deny hijack for roles without permission."""
        forbidden_roles = ['TENANT_MANAGER', 'TENANT_STAFF', 'CUSTOMER']

        for role in forbidden_roles:
            hijacker = Mock(id=1, role=role)
            hijacked = Mock(id=2, role='CUSTOMER')

            result = can_hijack(hijacker=hijacker, hijacked=hijacked)
            assert result is False, f"{role} should not be able to hijack anyone"


class TestCanHijackOr403:
    """Test can_hijack_or_403 function."""

    def test_raises_permission_denied_when_not_allowed(self):
        """Should raise PermissionDenied when hijack not allowed."""
        hijacker = Mock(id=1, role='CUSTOMER', email='customer@example.com')
        hijacker.get_role_display.return_value = 'Customer'

        hijacked = Mock(id=2, role='TENANT_OWNER', email='owner@example.com')
        hijacked.get_role_display.return_value = 'Tenant Owner'

        with pytest.raises(PermissionDenied) as exc_info:
            can_hijack_or_403(hijacker=hijacker, hijacked=hijacked)

        assert 'customer@example.com' in str(exc_info.value)
        assert 'owner@example.com' in str(exc_info.value)

    def test_returns_true_when_allowed(self):
        """Should return True when hijack is allowed."""
        hijacker = Mock(id=1, role='SUPERUSER', email='admin@example.com')
        hijacked = Mock(id=2, role='TENANT_OWNER', email='owner@example.com')

        result = can_hijack_or_403(hijacker=hijacker, hijacked=hijacked)

        assert result is True


class TestGetHijackableUsers:
    """Test get_hijackable_users function."""

    @patch('smoothschedule.identity.users.models.User')
    def test_superuser_can_see_all_users(self, mock_user_model):
        """Superuser should see all users except self."""
        hijacker = Mock(id=1, role='SUPERUSER')
        mock_user_model.Role.SUPERUSER = 'SUPERUSER'

        mock_queryset = Mock()
        mock_user_model.objects.exclude.return_value = mock_queryset

        result = get_hijackable_users(hijacker)

        mock_user_model.objects.exclude.assert_called_once_with(id=1)
        assert result == mock_queryset

    @patch('smoothschedule.identity.users.models.User')
    def test_platform_support_sees_tenant_users(self, mock_user_model):
        """Platform support should see tenant-level users."""
        hijacker = Mock(id=1, role='PLATFORM_SUPPORT')
        mock_user_model.Role.PLATFORM_SUPPORT = 'PLATFORM_SUPPORT'
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'
        mock_user_model.Role.TENANT_MANAGER = 'TENANT_MANAGER'
        mock_user_model.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_user_model.Role.CUSTOMER = 'CUSTOMER'

        mock_queryset = Mock()
        mock_filtered = Mock()
        mock_user_model.objects.exclude.return_value = mock_queryset
        mock_queryset.filter.return_value = mock_filtered

        result = get_hijackable_users(hijacker)

        # Should filter to tenant roles
        mock_queryset.filter.assert_called_once()
        filter_kwargs = mock_queryset.filter.call_args[1]
        assert 'role__in' in filter_kwargs
        roles = filter_kwargs['role__in']
        assert 'TENANT_OWNER' in roles
        assert 'TENANT_MANAGER' in roles
        assert 'TENANT_STAFF' in roles
        assert 'CUSTOMER' in roles

    @patch('smoothschedule.identity.users.models.User')
    def test_platform_sales_sees_temporary_users(self, mock_user_model):
        """Platform sales should only see temporary demo accounts."""
        hijacker = Mock(id=1, role='PLATFORM_SALES')
        mock_user_model.Role.PLATFORM_SALES = 'PLATFORM_SALES'

        mock_queryset = Mock()
        mock_filtered = Mock()
        mock_user_model.objects.exclude.return_value = mock_queryset
        mock_queryset.filter.return_value = mock_filtered

        result = get_hijackable_users(hijacker)

        # Should filter to temporary users
        mock_queryset.filter.assert_called_once_with(is_temporary=True)

    @patch('smoothschedule.identity.users.models.User')
    def test_tenant_owner_sees_same_tenant_users(self, mock_user_model):
        """Tenant owner should see staff in same tenant."""
        tenant = Mock(id=1)
        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=tenant)
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'
        mock_user_model.Role.TENANT_MANAGER = 'TENANT_MANAGER'
        mock_user_model.Role.TENANT_STAFF = 'TENANT_STAFF'
        mock_user_model.Role.CUSTOMER = 'CUSTOMER'

        mock_queryset = Mock()
        mock_filtered = Mock()
        mock_user_model.objects.exclude.return_value = mock_queryset
        mock_queryset.filter.return_value = mock_filtered

        result = get_hijackable_users(hijacker)

        # Should filter to same tenant and allowed roles
        mock_queryset.filter.assert_called_once()
        filter_kwargs = mock_queryset.filter.call_args[1]
        assert filter_kwargs['tenant'] == tenant
        assert 'role__in' in filter_kwargs

    @patch('smoothschedule.identity.users.models.User')
    def test_tenant_owner_without_tenant_sees_none(self, mock_user_model):
        """Tenant owner without tenant should see no users."""
        hijacker = Mock(id=1, role='TENANT_OWNER', tenant=None)
        mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'

        mock_queryset = Mock()
        mock_queryset.none.return_value = 'EMPTY'
        mock_user_model.objects.exclude.return_value = mock_queryset

        result = get_hijackable_users(hijacker)

        assert result == 'EMPTY'

    @patch('smoothschedule.identity.users.models.User')
    def test_other_roles_see_none(self, mock_user_model):
        """Other roles should see no hijackable users."""
        hijacker = Mock(id=1, role='CUSTOMER')
        mock_user_model.Role.CUSTOMER = 'CUSTOMER'

        mock_queryset = Mock()
        mock_queryset.none.return_value = 'EMPTY'
        mock_user_model.objects.exclude.return_value = mock_queryset

        result = get_hijackable_users(hijacker)

        assert result == 'EMPTY'


class TestValidateHijackChain:
    """Test validate_hijack_chain function."""

    def test_allows_hijack_when_under_max_depth(self):
        """Should allow hijack when under max depth."""
        mock_request = Mock()
        mock_request.session = {'hijack_history': [1, 2, 3]}

        result = validate_hijack_chain(mock_request, max_depth=5)

        assert result is True

    def test_allows_hijack_with_empty_history(self):
        """Should allow hijack when no history exists."""
        mock_request = Mock()
        mock_request.session = {}

        result = validate_hijack_chain(mock_request, max_depth=5)

        assert result is True

    def test_raises_when_max_depth_exceeded(self):
        """Should raise PermissionDenied when max depth exceeded."""
        mock_request = Mock()
        mock_request.session = {'hijack_history': [1, 2, 3, 4, 5]}

        with pytest.raises(PermissionDenied) as exc_info:
            validate_hijack_chain(mock_request, max_depth=5)

        assert 'Maximum masquerade depth' in str(exc_info.value)

    def test_uses_default_max_depth(self):
        """Should use default max depth of 5."""
        mock_request = Mock()
        mock_request.session = {'hijack_history': [1, 2, 3, 4, 5]}

        with pytest.raises(PermissionDenied):
            validate_hijack_chain(mock_request)


class TestHasQuota:
    """Test HasQuota permission factory."""

    def test_allows_read_operations(self):
        """Should allow GET, HEAD, OPTIONS without quota check."""
        QuotaPermission = HasQuota('MAX_RESOURCES')
        permission = QuotaPermission()

        read_methods = ['GET', 'HEAD', 'OPTIONS']

        for method in read_methods:
            mock_request = Mock(method=method)
            mock_view = Mock()

            result = permission.has_permission(mock_request, mock_view)

            assert result is True, f"Should allow {method} requests"

    def test_allows_when_no_tenant_in_request(self):
        """Should allow operations when no tenant (public schema)."""
        QuotaPermission = HasQuota('MAX_RESOURCES')
        permission = QuotaPermission()

        mock_request = Mock(method='POST')
        mock_request.tenant = None
        mock_view = Mock()

        result = permission.has_permission(mock_request, mock_view)

        assert result is True

    def test_allows_when_feature_not_mapped(self):
        """Should allow when feature code not in USAGE_MAP."""
        QuotaPermission = HasQuota('UNKNOWN_FEATURE')
        permission = QuotaPermission()

        mock_request = Mock(method='POST')
        mock_request.tenant = Mock(id=1)
        mock_view = Mock()

        result = permission.has_permission(mock_request, mock_view)

        assert result is True

    def test_allows_when_no_limit_defined(self):
        """Should allow when no limit exists (unlimited)."""
        QuotaPermission = HasQuota('MAX_RESOURCES')
        permission = QuotaPermission()

        mock_tenant = Mock(id=1)
        mock_tenant.get_limit.return_value = None  # No limit defined
        mock_request = Mock(method='POST', tenant=mock_tenant)
        mock_view = Mock()

        result = permission.has_permission(mock_request, mock_view)

        assert result is True

    def test_allows_when_under_quota(self):
        """Should allow operation when usage is under limit."""
        with patch('django.apps.apps.get_model') as mock_get_model:
            QuotaPermission = HasQuota('MAX_RESOURCES')
            permission = QuotaPermission()

            mock_tenant = Mock(id=1)
            mock_tenant.get_limit.return_value = 10  # Limit is 10
            mock_request = Mock(method='POST', tenant=mock_tenant)
            mock_view = Mock()

            # Mock model count
            mock_model = Mock()
            mock_queryset = Mock()
            mock_queryset.count.return_value = 5  # Under limit
            mock_model.objects.filter.return_value = mock_queryset
            mock_get_model.return_value = mock_model

            result = permission.has_permission(mock_request, mock_view)

            assert result is True

    def test_denies_when_quota_exceeded(self):
        """Should deny operation when quota exceeded."""
        with patch('django.apps.apps.get_model') as mock_get_model:
            QuotaPermission = HasQuota('MAX_RESOURCES')
            permission = QuotaPermission()

            mock_tenant = Mock(id=1)
            mock_tenant.get_limit.return_value = 10  # Limit is 10
            mock_request = Mock(method='POST', tenant=mock_tenant)
            mock_view = Mock()

            # Mock model count - at limit
            mock_model = Mock()
            mock_queryset = Mock()
            mock_queryset.count.return_value = 10  # At limit
            mock_model.objects.filter.return_value = mock_queryset
            mock_get_model.return_value = mock_model

            with pytest.raises(DRFPermissionDenied) as exc_info:
                permission.has_permission(mock_request, mock_view)

            assert 'Quota exceeded' in str(exc_info.value)
            assert 'plan limit of 10' in str(exc_info.value)

    def test_handles_additional_users_quota(self):
        """Should handle MAX_ADDITIONAL_USERS with special logic."""
        with patch('django.apps.apps.get_model') as mock_get_model:
            with patch('smoothschedule.identity.users.models.User') as mock_user_model:
                QuotaPermission = HasQuota('MAX_ADDITIONAL_USERS')
                permission = QuotaPermission()

                mock_tenant = Mock(id=1)
                mock_tenant.get_limit.return_value = 5  # Limit is 5
                mock_request = Mock(method='POST', tenant=mock_tenant)
                mock_view = Mock()

                # Mock user model and count
                mock_user_model.Role.TENANT_OWNER = 'TENANT_OWNER'
                mock_queryset = Mock()
                mock_queryset.exclude.return_value.count.return_value = 3  # Under limit
                mock_user_model.objects.filter.return_value = mock_queryset
                mock_get_model.return_value = mock_user_model

                result = permission.has_permission(mock_request, mock_view)

                # Should filter correctly for additional users
                mock_user_model.objects.filter.assert_called_once()
                filter_kwargs = mock_user_model.objects.filter.call_args[1]
                assert filter_kwargs['tenant'] == mock_tenant
                assert filter_kwargs['is_archived_by_quota'] is False

                assert result is True

    def test_handles_monthly_appointments_quota(self):
        """Should handle MAX_APPOINTMENTS with monthly filtering."""
        from datetime import datetime
        with patch('django.apps.apps.get_model') as mock_get_model:
            with patch('django.utils.timezone') as mock_timezone:
                QuotaPermission = HasQuota('MAX_APPOINTMENTS')
                permission = QuotaPermission()

                mock_tenant = Mock(id=1)
                mock_tenant.get_limit.return_value = 100  # Limit is 100
                mock_request = Mock(method='POST', tenant=mock_tenant)
                mock_view = Mock()

                # Mock current time
                now = datetime(2024, 6, 15, 10, 0, 0)
                mock_timezone.now.return_value = now

                # Mock event model
                mock_model = Mock()
                mock_queryset = Mock()
                mock_queryset.count.return_value = 50  # Under limit
                mock_model.objects.filter.return_value = mock_queryset
                mock_get_model.return_value = mock_model

                result = permission.has_permission(mock_request, mock_view)

                # Should filter by month
                mock_model.objects.filter.assert_called_once()
                filter_kwargs = mock_model.objects.filter.call_args[1]
                assert 'start_time__gte' in filter_kwargs
                assert 'start_time__lt' in filter_kwargs

                assert result is True


class TestHasFeaturePermission:
    """Test HasFeaturePermission factory."""

    def test_allows_when_no_tenant(self):
        """Should allow when no tenant (platform operations)."""
        FeaturePermission = HasFeaturePermission('can_use_sms_reminders')
        permission = FeaturePermission()

        mock_request = Mock()
        mock_request.tenant = None
        mock_view = Mock()

        result = permission.has_permission(mock_request, mock_view)

        assert result is True

    def test_allows_when_tenant_has_feature(self):
        """Should allow when tenant has the feature."""
        FeaturePermission = HasFeaturePermission('can_use_sms_reminders')
        permission = FeaturePermission()

        mock_tenant = Mock(id=1)
        mock_tenant.has_feature.return_value = True

        mock_request = Mock(tenant=mock_tenant)
        mock_view = Mock()

        result = permission.has_permission(mock_request, mock_view)

        mock_tenant.has_feature.assert_called_once_with('can_use_sms_reminders')
        assert result is True

    def test_denies_when_tenant_lacks_feature(self):
        """Should deny when tenant doesn't have the feature."""
        FeaturePermission = HasFeaturePermission('can_use_masked_phone_numbers')
        permission = FeaturePermission()

        mock_tenant = Mock(id=1)
        mock_tenant.has_feature.return_value = False

        mock_request = Mock(tenant=mock_tenant)
        mock_view = Mock()

        with pytest.raises(DRFPermissionDenied) as exc_info:
            permission.has_permission(mock_request, mock_view)

        assert 'Masked Calling' in str(exc_info.value)
        assert 'upgrade your subscription' in str(exc_info.value)

    def test_uses_custom_feature_name_from_map(self):
        """Should use custom feature name from FEATURE_NAMES map."""
        FeaturePermission = HasFeaturePermission('can_use_webhooks')
        permission = FeaturePermission()

        mock_tenant = Mock(id=1)
        mock_tenant.has_feature.return_value = False

        mock_request = Mock(tenant=mock_tenant)
        mock_view = Mock()

        with pytest.raises(DRFPermissionDenied) as exc_info:
            permission.has_permission(mock_request, mock_view)

        # Should use name from FEATURE_NAMES map
        assert 'Webhooks' in str(exc_info.value)

    def test_generates_feature_name_when_not_in_map(self):
        """Should generate readable name for unmapped features."""
        FeaturePermission = HasFeaturePermission('can_use_custom_feature')
        permission = FeaturePermission()

        mock_tenant = Mock(id=1)
        mock_tenant.has_feature.return_value = False

        mock_request = Mock(tenant=mock_tenant)
        mock_view = Mock()

        with pytest.raises(DRFPermissionDenied) as exc_info:
            permission.has_permission(mock_request, mock_view)

        # Should generate name from key
        assert 'Use Custom Feature' in str(exc_info.value)

    def test_has_object_permission_delegates_to_has_permission(self):
        """Should delegate object permission to has_permission."""
        FeaturePermission = HasFeaturePermission('can_use_custom_domain')
        permission = FeaturePermission()

        mock_tenant = Mock(id=1)
        mock_tenant.has_feature.return_value = True

        mock_request = Mock(tenant=mock_tenant)
        mock_view = Mock()
        mock_obj = Mock()

        result = permission.has_object_permission(mock_request, mock_view, mock_obj)

        assert result is True


class TestQuotaPermissionIntegration:
    """Integration tests for quota permission edge cases."""

    def test_excludes_archived_resources_from_count(self):
        """Should exclude archived resources when counting."""
        with patch('django.apps.apps.get_model') as mock_get_model:
            QuotaPermission = HasQuota('MAX_SERVICES')
            permission = QuotaPermission()

            mock_tenant = Mock(id=1)
            mock_tenant.get_limit.return_value = 10  # Limit is 10
            mock_request = Mock(method='POST', tenant=mock_tenant)
            mock_view = Mock()

            # Mock model with is_archived_by_quota field
            mock_model = Mock()
            mock_model.__name__ = 'Service'
            mock_queryset = Mock()
            mock_queryset.count.return_value = 8  # Under limit
            mock_model.objects.filter.return_value = mock_queryset

            # Model has the field
            def hasattr_side_effect(obj, attr):
                if attr == 'is_archived_by_quota':
                    return True
                return object.__getattribute__(obj, attr)

            mock_get_model.return_value = mock_model

            with patch('builtins.hasattr', side_effect=hasattr_side_effect):
                result = permission.has_permission(mock_request, mock_view)

            # Should filter by is_archived_by_quota
            mock_model.objects.filter.assert_called_once_with(is_archived_by_quota=False)
            assert result is True


class TestPermissionFactoryConfiguration:
    """Test permission factory configuration."""

    def test_has_quota_usage_map_complete(self):
        """Should have complete USAGE_MAP for all quota types."""
        QuotaPermission = HasQuota('MAX_RESOURCES')
        permission = QuotaPermission()

        expected_mappings = {
            'MAX_RESOURCES': 'schedule.Resource',
            'MAX_ADDITIONAL_USERS': 'users.User',
            'MAX_EVENTS_PER_MONTH': 'schedule.Event',
            'MAX_SERVICES': 'schedule.Service',
            'MAX_APPOINTMENTS': 'schedule.Event',
            'MAX_EMAIL_TEMPLATES': 'schedule.EmailTemplate',
            'MAX_AUTOMATED_TASKS': 'schedule.ScheduledTask',
        }

        for quota_type, model_path in expected_mappings.items():
            assert quota_type in permission.USAGE_MAP
            assert permission.USAGE_MAP[quota_type] == model_path

    def test_has_feature_permission_feature_names_map(self):
        """Should have comprehensive FEATURE_NAMES map."""
        FeaturePermission = HasFeaturePermission('can_use_sms_reminders')
        permission = FeaturePermission()

        expected_features = [
            'can_use_sms_reminders',
            'can_use_masked_phone_numbers',
            'can_use_custom_domain',
            'can_white_label',
            'can_create_automations',
            'can_use_webhooks',
            'can_accept_payments',
            'can_api_access',
            'can_manage_oauth_credentials',
            'can_use_calendar_sync',
            'advanced_analytics',
            'advanced_reporting',
        ]

        for feature in expected_features:
            assert feature in permission.FEATURE_NAMES
            assert isinstance(permission.FEATURE_NAMES[feature], str)
            assert len(permission.FEATURE_NAMES[feature]) > 0
