From 1cce4647de9fa84e65fce1ea2490a469c8259ef0 Mon Sep 17 00:00:00 2001 From: Shuvy Date: Wed, 10 Sep 2025 17:17:42 +0300 Subject: [PATCH 1/4] Approve user invite. --- permit/api/users.py | 32 ++++ tests/test_user_invite_approve_simple.py | 179 +++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 tests/test_user_invite_approve_simple.py diff --git a/permit/api/users.py b/permit/api/users.py index 4d4f7f3..47f0cc7 100644 --- a/permit/api/users.py +++ b/permit/api/users.py @@ -14,6 +14,8 @@ ) from .context import ApiContextLevel, ApiKeyAccessLevel from .models import ( + ElementsUserInviteApprove, + ElementsUserInviteRead, PaginatedResultUserRead, RoleAssignmentCreate, RoleAssignmentRead, @@ -58,6 +60,12 @@ def __bulk_operations(self) -> SimpleHttpClient: f"/v2/facts/{self.config.api_context.project}/{self.config.api_context.environment}/bulk/users" ) + @property + def __user_invites(self) -> SimpleHttpClient: + return self._build_http_client( + f"/v2/facts/{self.config.api_context.project}/{self.config.api_context.environment}/user_invites" + ) + @validate_arguments # type: ignore[operator] async def list(self, page: int = 1, per_page: int = 100) -> PaginatedResultUserRead: """ @@ -374,3 +382,27 @@ async def get_assigned_roles( model=List[RoleAssignmentRead], params=params, ) + + @validate_arguments # type: ignore[operator] + async def approve(self, user_invite_id: str, approve_data: ElementsUserInviteApprove) -> ElementsUserInviteRead: + """ + Approves a user invite. + + Args: + user_invite_id: The ID of the user invite to approve. + approve_data: The approval data for the user invite. + + Returns: + the approved user invite. + + Raises: + PermitApiError: If the API returns an error HTTP status code. + PermitContextError: If the configured ApiContext does not match the required endpoint context. + """ + await self._ensure_access_level(ApiKeyAccessLevel.ENVIRONMENT_LEVEL_API_KEY) + await self._ensure_context(ApiContextLevel.ENVIRONMENT) + return await self.__user_invites.post( + f"/{user_invite_id}/approve", + model=ElementsUserInviteRead, + json=approve_data, + ) diff --git a/tests/test_user_invite_approve_simple.py b/tests/test_user_invite_approve_simple.py new file mode 100644 index 0000000..aa0c4d6 --- /dev/null +++ b/tests/test_user_invite_approve_simple.py @@ -0,0 +1,179 @@ +""" +Simple tests for user invite approve functionality. +These tests focus on the core implementation without complex API mocking. +""" + +import uuid +import pytest +from unittest.mock import Mock, AsyncMock, patch + +from permit.api.models import ElementsUserInviteApprove, ElementsUserInviteRead, UserInviteStatus +from permit.api.users import UsersApi + + +class TestUserInviteApproveSimple: + """Simple test suite for user invite approve functionality.""" + + def test_approve_method_exists(self): + """Test that the approve method exists in UsersApi.""" + assert hasattr(UsersApi, 'approve') + assert callable(UsersApi.approve) + + def test_approve_method_signature(self): + """Test that the approve method has the correct signature.""" + import inspect + + method = getattr(UsersApi, 'approve') + signature = inspect.signature(method) + + # Check parameter names + params = list(signature.parameters.keys()) + assert 'self' in params + assert 'user_invite_id' in params + assert 'approve_data' in params + + # Check return type annotation + assert signature.return_annotation.__name__ == 'ElementsUserInviteRead' + + def test_approve_data_model_validation(self): + """Test ElementsUserInviteApprove model validation.""" + # Valid data + valid_data = ElementsUserInviteApprove( + email="test@example.com", + key="valid-key-123", + attributes={"role": "admin"} + ) + assert valid_data.email == "test@example.com" + assert valid_data.key == "valid-key-123" + assert valid_data.attributes == {"role": "admin"} + + def test_approve_data_model_email_validation(self): + """Test ElementsUserInviteApprove email validation.""" + # Test email validation + with pytest.raises(ValueError): + ElementsUserInviteApprove( + email="invalid-email", + key="valid-key-123", + attributes={} + ) + + def test_approve_data_model_key_validation(self): + """Test ElementsUserInviteApprove key validation.""" + # Test key validation (regex pattern) + with pytest.raises(ValueError): + ElementsUserInviteApprove( + email="test@example.com", + key="invalid key with spaces", # Should fail regex validation + attributes={} + ) + + def test_approve_data_model_with_complex_attributes(self): + """Test ElementsUserInviteApprove with complex attributes.""" + complex_attributes = { + "department": "Engineering", + "location": "San Francisco", + "role": "Developer", + "level": "Senior", + "permissions": ["read", "write"], + "metadata": { + "hire_date": "2024-01-01", + "manager": "john@example.com" + } + } + + approve_data = ElementsUserInviteApprove( + email="test@example.com", + key="test-key-123", + attributes=complex_attributes + ) + + assert approve_data.attributes == complex_attributes + assert approve_data.attributes["permissions"] == ["read", "write"] + assert approve_data.attributes["metadata"]["hire_date"] == "2024-01-01" + + def test_approve_data_model_with_empty_attributes(self): + """Test ElementsUserInviteApprove with empty attributes.""" + approve_data = ElementsUserInviteApprove( + email="test@example.com", + key="test-key-123", + attributes={} # Empty attributes should be allowed + ) + + assert approve_data.attributes == {} + + def test_user_invite_status_enum(self): + """Test UserInviteStatus enum values.""" + assert UserInviteStatus.pending == 'pending' + assert UserInviteStatus.approved == 'approved' + + def test_elements_user_invite_read_model(self): + """Test ElementsUserInviteRead model creation.""" + invite_data = ElementsUserInviteRead( + id=uuid.uuid4(), + organization_id=uuid.uuid4(), + project_id=uuid.uuid4(), + environment_id=uuid.uuid4(), + key="test-invite-key-123", + status=UserInviteStatus.approved, + email="test@example.com", + first_name="John", + last_name="Doe", + role_id=uuid.uuid4(), + tenant_id=uuid.uuid4(), + created_at="2024-01-01T00:00:00Z", + updated_at="2024-01-01T01:00:00Z" + ) + + assert invite_data.status == UserInviteStatus.approved + assert invite_data.email == "test@example.com" + assert invite_data.key == "test-invite-key-123" + + + def test_approve_parameter_types(self): + """Test that the approve method parameters have correct types.""" + import inspect + from typing import get_type_hints + + method = getattr(UsersApi, 'approve') + type_hints = get_type_hints(method) + + # Check parameter types + assert type_hints.get('user_invite_id') == str + assert type_hints.get('approve_data').__name__ == 'ElementsUserInviteApprove' + assert type_hints.get('return').__name__ == 'ElementsUserInviteRead' + + def test_user_invite_models_exist(self): + """Test that all required models are available.""" + # Test that we can import all necessary models + from permit.api.models import ( + ElementsUserInviteApprove, + ElementsUserInviteRead, + UserInviteStatus + ) + + assert ElementsUserInviteApprove is not None + assert ElementsUserInviteRead is not None + assert UserInviteStatus is not None + + def test_approve_data_serialization(self): + """Test that ElementsUserInviteApprove data can be serialized.""" + approve_data = ElementsUserInviteApprove( + email="test@example.com", + key="test-key-123", + attributes={"role": "admin", "department": "Engineering"} + ) + + # Test dict() method + data_dict = approve_data.dict() + assert data_dict['email'] == "test@example.com" + assert data_dict['key'] == "test-key-123" + assert data_dict['attributes']['role'] == "admin" + assert data_dict['attributes']['department'] == "Engineering" + + # Test JSON serialization + import json + json_str = approve_data.json() + parsed_data = json.loads(json_str) + assert parsed_data['email'] == "test@example.com" + assert parsed_data['key'] == "test-key-123" + From 9174f43c924fad338223cf57240b28eb4ff890ef Mon Sep 17 00:00:00 2001 From: Shuvy Date: Wed, 10 Sep 2025 17:39:44 +0300 Subject: [PATCH 2/4] Fix pre commit --- tests/test_user_invite_approve_simple.py | 101 +++++++++-------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/tests/test_user_invite_approve_simple.py b/tests/test_user_invite_approve_simple.py index aa0c4d6..93913f4 100644 --- a/tests/test_user_invite_approve_simple.py +++ b/tests/test_user_invite_approve_simple.py @@ -4,8 +4,8 @@ """ import uuid + import pytest -from unittest.mock import Mock, AsyncMock, patch from permit.api.models import ElementsUserInviteApprove, ElementsUserInviteRead, UserInviteStatus from permit.api.users import UsersApi @@ -16,32 +16,30 @@ class TestUserInviteApproveSimple: def test_approve_method_exists(self): """Test that the approve method exists in UsersApi.""" - assert hasattr(UsersApi, 'approve') + assert hasattr(UsersApi, "approve") assert callable(UsersApi.approve) def test_approve_method_signature(self): """Test that the approve method has the correct signature.""" import inspect - - method = getattr(UsersApi, 'approve') + + method = UsersApi.approve signature = inspect.signature(method) - + # Check parameter names params = list(signature.parameters.keys()) - assert 'self' in params - assert 'user_invite_id' in params - assert 'approve_data' in params - + assert "self" in params + assert "user_invite_id" in params + assert "approve_data" in params + # Check return type annotation - assert signature.return_annotation.__name__ == 'ElementsUserInviteRead' + assert signature.return_annotation.__name__ == "ElementsUserInviteRead" def test_approve_data_model_validation(self): """Test ElementsUserInviteApprove model validation.""" # Valid data valid_data = ElementsUserInviteApprove( - email="test@example.com", - key="valid-key-123", - attributes={"role": "admin"} + email="test@example.com", key="valid-key-123", attributes={"role": "admin"} ) assert valid_data.email == "test@example.com" assert valid_data.key == "valid-key-123" @@ -51,11 +49,7 @@ def test_approve_data_model_email_validation(self): """Test ElementsUserInviteApprove email validation.""" # Test email validation with pytest.raises(ValueError): - ElementsUserInviteApprove( - email="invalid-email", - key="valid-key-123", - attributes={} - ) + ElementsUserInviteApprove(email="invalid-email", key="valid-key-123", attributes={}) def test_approve_data_model_key_validation(self): """Test ElementsUserInviteApprove key validation.""" @@ -64,7 +58,7 @@ def test_approve_data_model_key_validation(self): ElementsUserInviteApprove( email="test@example.com", key="invalid key with spaces", # Should fail regex validation - attributes={} + attributes={}, ) def test_approve_data_model_with_complex_attributes(self): @@ -75,18 +69,13 @@ def test_approve_data_model_with_complex_attributes(self): "role": "Developer", "level": "Senior", "permissions": ["read", "write"], - "metadata": { - "hire_date": "2024-01-01", - "manager": "john@example.com" - } + "metadata": {"hire_date": "2024-01-01", "manager": "john@example.com"}, } - + approve_data = ElementsUserInviteApprove( - email="test@example.com", - key="test-key-123", - attributes=complex_attributes + email="test@example.com", key="test-key-123", attributes=complex_attributes ) - + assert approve_data.attributes == complex_attributes assert approve_data.attributes["permissions"] == ["read", "write"] assert approve_data.attributes["metadata"]["hire_date"] == "2024-01-01" @@ -96,15 +85,15 @@ def test_approve_data_model_with_empty_attributes(self): approve_data = ElementsUserInviteApprove( email="test@example.com", key="test-key-123", - attributes={} # Empty attributes should be allowed + attributes={}, # Empty attributes should be allowed ) - + assert approve_data.attributes == {} def test_user_invite_status_enum(self): """Test UserInviteStatus enum values.""" - assert UserInviteStatus.pending == 'pending' - assert UserInviteStatus.approved == 'approved' + assert UserInviteStatus.pending == "pending" + assert UserInviteStatus.approved == "approved" def test_elements_user_invite_read_model(self): """Test ElementsUserInviteRead model creation.""" @@ -121,36 +110,30 @@ def test_elements_user_invite_read_model(self): role_id=uuid.uuid4(), tenant_id=uuid.uuid4(), created_at="2024-01-01T00:00:00Z", - updated_at="2024-01-01T01:00:00Z" + updated_at="2024-01-01T01:00:00Z", ) - + assert invite_data.status == UserInviteStatus.approved assert invite_data.email == "test@example.com" assert invite_data.key == "test-invite-key-123" - def test_approve_parameter_types(self): """Test that the approve method parameters have correct types.""" - import inspect from typing import get_type_hints - - method = getattr(UsersApi, 'approve') + + method = UsersApi.approve type_hints = get_type_hints(method) - + # Check parameter types - assert type_hints.get('user_invite_id') == str - assert type_hints.get('approve_data').__name__ == 'ElementsUserInviteApprove' - assert type_hints.get('return').__name__ == 'ElementsUserInviteRead' + assert type_hints.get("user_invite_id") is str + assert type_hints.get("approve_data").__name__ == "ElementsUserInviteApprove" + assert type_hints.get("return").__name__ == "ElementsUserInviteRead" def test_user_invite_models_exist(self): """Test that all required models are available.""" # Test that we can import all necessary models - from permit.api.models import ( - ElementsUserInviteApprove, - ElementsUserInviteRead, - UserInviteStatus - ) - + from permit.api.models import ElementsUserInviteApprove, ElementsUserInviteRead, UserInviteStatus + assert ElementsUserInviteApprove is not None assert ElementsUserInviteRead is not None assert UserInviteStatus is not None @@ -158,22 +141,20 @@ def test_user_invite_models_exist(self): def test_approve_data_serialization(self): """Test that ElementsUserInviteApprove data can be serialized.""" approve_data = ElementsUserInviteApprove( - email="test@example.com", - key="test-key-123", - attributes={"role": "admin", "department": "Engineering"} + email="test@example.com", key="test-key-123", attributes={"role": "admin", "department": "Engineering"} ) - + # Test dict() method data_dict = approve_data.dict() - assert data_dict['email'] == "test@example.com" - assert data_dict['key'] == "test-key-123" - assert data_dict['attributes']['role'] == "admin" - assert data_dict['attributes']['department'] == "Engineering" - + assert data_dict["email"] == "test@example.com" + assert data_dict["key"] == "test-key-123" + assert data_dict["attributes"]["role"] == "admin" + assert data_dict["attributes"]["department"] == "Engineering" + # Test JSON serialization import json + json_str = approve_data.json() parsed_data = json.loads(json_str) - assert parsed_data['email'] == "test@example.com" - assert parsed_data['key'] == "test-key-123" - + assert parsed_data["email"] == "test@example.com" + assert parsed_data["key"] == "test-key-123" From c78ed5e16de81193cd8d3a2905d2473da8fe61a5 Mon Sep 17 00:00:00 2001 From: Shuvy Date: Wed, 10 Sep 2025 22:22:23 +0300 Subject: [PATCH 3/4] Add user_invites change approve function users.py --> user_invites.py --- permit/api/api_client.py | 10 ++ permit/api/user_invites.py | 51 ++++++++ permit/api/users.py | 32 ----- tests/test_user_invite_approve_simple.py | 146 +++++++++++++++++++++-- 4 files changed, 199 insertions(+), 40 deletions(-) create mode 100644 permit/api/user_invites.py diff --git a/permit/api/api_client.py b/permit/api/api_client.py index dbc68e8..478b22d 100644 --- a/permit/api/api_client.py +++ b/permit/api/api_client.py @@ -15,6 +15,7 @@ from .role_assignments import RoleAssignmentsApi from .roles import RolesApi from .tenants import TenantsApi +from .user_invites import UserInvitesApi from .users import UsersApi @@ -43,6 +44,7 @@ def __init__(self, config: PermitConfig): self._relationship_tuples = RelationshipTuplesApi(config) self._roles = RolesApi(config) self._tenants = TenantsApi(config) + self._user_invites = UserInvitesApi(config) self._users = UsersApi(config) @property @@ -165,6 +167,14 @@ def tenants(self) -> TenantsApi: """ return self._tenants + @property + def user_invites(self) -> UserInvitesApi: + """ + API for managing user invites. + See: https://api.permit.io/v2/redoc#tag/User-Invites + """ + return self._user_invites + @property def users(self) -> UsersApi: """ diff --git a/permit/api/user_invites.py b/permit/api/user_invites.py new file mode 100644 index 0000000..9142982 --- /dev/null +++ b/permit/api/user_invites.py @@ -0,0 +1,51 @@ +from typing import List, Optional, Union + +from ..utils.pydantic_version import PYDANTIC_VERSION + +if PYDANTIC_VERSION < (2, 0): + from pydantic import validate_arguments +else: + from pydantic.v1 import validate_arguments + +from .base import ( + BasePermitApi, + SimpleHttpClient, + pagination_params, +) +from .context import ApiContextLevel, ApiKeyAccessLevel +from .models import ( + ElementsUserInviteApprove, + ElementsUserInviteRead, +) + + +class UserInvitesApi(BasePermitApi): + @property + def __user_invites(self) -> SimpleHttpClient: + return self._build_http_client( + f"/v2/facts/{self.config.api_context.project}/{self.config.api_context.environment}/user_invites" + ) + + @validate_arguments # type: ignore[operator] + async def approve(self, user_invite_id: str, approve_data: ElementsUserInviteApprove) -> ElementsUserInviteRead: + """ + Approves a user invite. + + Args: + user_invite_id: The ID of the user invite to approve. + approve_data: The approval data for the user invite. + + Returns: + the approved user invite. + + Raises: + PermitApiError: If the API returns an error HTTP status code. + PermitContextError: If the configured ApiContext does not match the required endpoint context. + """ + await self._ensure_access_level(ApiKeyAccessLevel.ENVIRONMENT_LEVEL_API_KEY) + await self._ensure_context(ApiContextLevel.ENVIRONMENT) + return await self.__user_invites.post( + f"/{user_invite_id}/approve", + model=ElementsUserInviteRead, + json=approve_data, + ) \ No newline at end of file diff --git a/permit/api/users.py b/permit/api/users.py index 47f0cc7..4d4f7f3 100644 --- a/permit/api/users.py +++ b/permit/api/users.py @@ -14,8 +14,6 @@ ) from .context import ApiContextLevel, ApiKeyAccessLevel from .models import ( - ElementsUserInviteApprove, - ElementsUserInviteRead, PaginatedResultUserRead, RoleAssignmentCreate, RoleAssignmentRead, @@ -60,12 +58,6 @@ def __bulk_operations(self) -> SimpleHttpClient: f"/v2/facts/{self.config.api_context.project}/{self.config.api_context.environment}/bulk/users" ) - @property - def __user_invites(self) -> SimpleHttpClient: - return self._build_http_client( - f"/v2/facts/{self.config.api_context.project}/{self.config.api_context.environment}/user_invites" - ) - @validate_arguments # type: ignore[operator] async def list(self, page: int = 1, per_page: int = 100) -> PaginatedResultUserRead: """ @@ -382,27 +374,3 @@ async def get_assigned_roles( model=List[RoleAssignmentRead], params=params, ) - - @validate_arguments # type: ignore[operator] - async def approve(self, user_invite_id: str, approve_data: ElementsUserInviteApprove) -> ElementsUserInviteRead: - """ - Approves a user invite. - - Args: - user_invite_id: The ID of the user invite to approve. - approve_data: The approval data for the user invite. - - Returns: - the approved user invite. - - Raises: - PermitApiError: If the API returns an error HTTP status code. - PermitContextError: If the configured ApiContext does not match the required endpoint context. - """ - await self._ensure_access_level(ApiKeyAccessLevel.ENVIRONMENT_LEVEL_API_KEY) - await self._ensure_context(ApiContextLevel.ENVIRONMENT) - return await self.__user_invites.post( - f"/{user_invite_id}/approve", - model=ElementsUserInviteRead, - json=approve_data, - ) diff --git a/tests/test_user_invite_approve_simple.py b/tests/test_user_invite_approve_simple.py index 93913f4..35b6d18 100644 --- a/tests/test_user_invite_approve_simple.py +++ b/tests/test_user_invite_approve_simple.py @@ -4,26 +4,28 @@ """ import uuid +from unittest.mock import AsyncMock, patch import pytest +from permit import Permit, PermitConfig from permit.api.models import ElementsUserInviteApprove, ElementsUserInviteRead, UserInviteStatus -from permit.api.users import UsersApi +from permit.api.user_invites import UserInvitesApi class TestUserInviteApproveSimple: """Simple test suite for user invite approve functionality.""" def test_approve_method_exists(self): - """Test that the approve method exists in UsersApi.""" - assert hasattr(UsersApi, "approve") - assert callable(UsersApi.approve) + """Test that the approve method exists in UserInvitesApi.""" + assert hasattr(UserInvitesApi, "approve") + assert callable(UserInvitesApi.approve) def test_approve_method_signature(self): """Test that the approve method has the correct signature.""" import inspect - method = UsersApi.approve + method = UserInvitesApi.approve signature = inspect.signature(method) # Check parameter names @@ -57,8 +59,8 @@ def test_approve_data_model_key_validation(self): with pytest.raises(ValueError): ElementsUserInviteApprove( email="test@example.com", - key="invalid key with spaces", # Should fail regex validation - attributes={}, + key="invalid key with spaces", + attributes={}, # Should fail regex validation ) def test_approve_data_model_with_complex_attributes(self): @@ -121,7 +123,7 @@ def test_approve_parameter_types(self): """Test that the approve method parameters have correct types.""" from typing import get_type_hints - method = UsersApi.approve + method = UserInvitesApi.approve type_hints = get_type_hints(method) # Check parameter types @@ -158,3 +160,131 @@ def test_approve_data_serialization(self): parsed_data = json.loads(json_str) assert parsed_data["email"] == "test@example.com" assert parsed_data["key"] == "test-key-123" + + def test_permit_instance_has_user_invites_api(self): + """Test that Permit instance exposes user_invites API correctly.""" + # Create a Permit instance with mock configuration + config = PermitConfig(token="test-token-123", api_url="http://localhost:8000", pdp="http://localhost:7766") + permit = Permit(config) + + # Test that user_invites API is accessible + assert hasattr(permit.api, "user_invites") + assert permit.api.user_invites is not None + + # Test that the approve method is available + assert hasattr(permit.api.user_invites, "approve") + assert callable(permit.api.user_invites.approve) + + @pytest.mark.asyncio + async def test_permit_user_invites_approve_usage_pattern(self): + """ + Test the actual usage pattern: permit.api.user_invites.approve() + This test mimics how users will actually use the function. + """ + # Create a Permit instance with mock configuration + config = PermitConfig(token="test-token-123", api_url="http://localhost:8000", pdp="http://localhost:7766") + permit = Permit(config) + + # Create test data - this is how users will use it + user_invite_id = "test-invite-uuid-123" + approve_data = ElementsUserInviteApprove( + email="newuser@company.com", + key="new-user-invite-key", + attributes={ + "department": "Engineering", + "role": "Developer", + "start_date": "2024-01-15", + "manager": "manager@company.com", + }, + ) + + # Mock the entire approve method to avoid HTTP calls + mock_response = ElementsUserInviteRead( + id=uuid.uuid4(), + organization_id=uuid.uuid4(), + project_id=uuid.uuid4(), + environment_id=uuid.uuid4(), + key="new-user-invite-key", + status=UserInviteStatus.approved, + email="newuser@company.com", + first_name="New", + last_name="User", + role_id=uuid.uuid4(), + tenant_id=uuid.uuid4(), + created_at="2024-01-01T00:00:00Z", + updated_at="2024-01-01T01:00:00Z", + ) + + # Mock just the approve method + with patch.object(permit.api.user_invites, "approve", new_callable=AsyncMock) as mock_approve: + mock_approve.return_value = mock_response + + # This is the actual usage pattern that users will use! + result = await permit.api.user_invites.approve(user_invite_id, approve_data) + + # Verify the result + assert isinstance(result, ElementsUserInviteRead) + assert result.status == UserInviteStatus.approved + assert result.email == "newuser@company.com" + assert result.key == "new-user-invite-key" + + # Verify the method was called with correct parameters + mock_approve.assert_called_once_with(user_invite_id, approve_data) + + def test_user_invites_api_integration_in_permit_client(self): + """Test that the UserInvitesApi is properly integrated into the Permit client.""" + config = PermitConfig(token="test-token-123", api_url="http://localhost:8000", pdp="http://localhost:7766") + permit = Permit(config) + + # Test the complete integration + assert hasattr(permit, "api") + assert hasattr(permit.api, "user_invites") + assert isinstance(permit.api.user_invites, UserInvitesApi) + + # Test that it's different from users API + assert hasattr(permit.api, "users") + assert permit.api.user_invites is not permit.api.users + assert type(permit.api.user_invites).__name__ == "UserInvitesApi" + assert type(permit.api.users).__name__ == "UsersApi" + + def test_actual_usage_pattern_structure(self): + """ + Test that the actual usage pattern permit.api.user_invites.approve() is available. + This verifies the complete structure that users will interact with. + """ + # Create a real Permit instance (no mocking) + config = PermitConfig(token="test-token-123", api_url="http://localhost:8000", pdp="http://localhost:7766") + permit = Permit(config) + + # Create real test data as users would + approve_data = ElementsUserInviteApprove( + email="newuser@company.com", + key="new-user-invite-key", + attributes={"department": "Engineering", "role": "Developer", "start_date": "2024-01-15"}, + ) + + # Verify the complete call chain exists (this is what users will use) + assert hasattr(permit, "api"), "permit.api should exist" + assert hasattr(permit.api, "user_invites"), "permit.api.user_invites should exist" + assert hasattr(permit.api.user_invites, "approve"), "permit.api.user_invites.approve should exist" + assert callable(permit.api.user_invites.approve), "permit.api.user_invites.approve should be callable" + + # Verify the method signature matches what users expect + import inspect + + method = permit.api.user_invites.approve + signature = inspect.signature(method) + params = list(signature.parameters.keys()) + + assert "user_invite_id" in params, "Method should accept user_invite_id parameter" + assert "approve_data" in params, "Method should accept approve_data parameter" + + # Verify the approve_data can be created with user's data + assert approve_data.email == "newuser@company.com" + assert approve_data.key == "new-user-invite-key" + assert approve_data.attributes["department"] == "Engineering" + + +if __name__ == "__main__": + # Run with: python -m pytest tests/test_user_invite_approve_simple.py -v + pass From c173ae836ca1820325e544b64654996a071adf19 Mon Sep 17 00:00:00 2001 From: Shuvy Date: Wed, 10 Sep 2025 22:36:29 +0300 Subject: [PATCH 4/4] Fix pre commit --- permit/api/user_invites.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/permit/api/user_invites.py b/permit/api/user_invites.py index 9142982..7c11152 100644 --- a/permit/api/user_invites.py +++ b/permit/api/user_invites.py @@ -1,5 +1,3 @@ -from typing import List, Optional, Union - from ..utils.pydantic_version import PYDANTIC_VERSION if PYDANTIC_VERSION < (2, 0): @@ -10,7 +8,6 @@ from .base import ( BasePermitApi, SimpleHttpClient, - pagination_params, ) from .context import ApiContextLevel, ApiKeyAccessLevel from .models import ( @@ -48,4 +45,4 @@ async def approve(self, user_invite_id: str, approve_data: ElementsUserInviteApp f"/{user_invite_id}/approve", model=ElementsUserInviteRead, json=approve_data, - ) \ No newline at end of file + )