From ab35617fdaef0d0fc49bd5b65203636a6c9e4e81 Mon Sep 17 00:00:00 2001 From: swaroopakkineni Date: Thu, 19 Feb 2026 09:02:10 -1000 Subject: [PATCH 1/4] adding base types and module registraiton --- src/workos/types/authorization/__init__.py | 6 ++++ .../types/authorization/access_evaluation.py | 7 ++++ .../authorization/organization_membership.py | 23 ++++++++++++ src/workos/types/authorization/resource.py | 18 ++++++++++ .../types/authorization/role_assignment.py | 13 +++++++ src/workos/types/list_resource.py | 8 +++++ src/workos/utils/_base_http_client.py | 6 ++-- src/workos/utils/http_client.py | 36 ++++++++++++++++++- 8 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 src/workos/types/authorization/access_evaluation.py create mode 100644 src/workos/types/authorization/organization_membership.py create mode 100644 src/workos/types/authorization/resource.py create mode 100644 src/workos/types/authorization/role_assignment.py diff --git a/src/workos/types/authorization/__init__.py b/src/workos/types/authorization/__init__.py index 609a4f2d..ecdafa79 100644 --- a/src/workos/types/authorization/__init__.py +++ b/src/workos/types/authorization/__init__.py @@ -1,14 +1,20 @@ +from workos.types.authorization.access_evaluation import AccessEvaluation from workos.types.authorization.environment_role import ( EnvironmentRole, EnvironmentRoleList, ) +from workos.types.authorization.organization_membership import ( + AuthorizationOrganizationMembership, +) from workos.types.authorization.organization_role import ( OrganizationRole, OrganizationRoleEvent, OrganizationRoleList, ) from workos.types.authorization.permission import Permission +from workos.types.authorization.resource import Resource from workos.types.authorization.role import ( Role, RoleList, ) +from workos.types.authorization.role_assignment import RoleAssignment diff --git a/src/workos/types/authorization/access_evaluation.py b/src/workos/types/authorization/access_evaluation.py new file mode 100644 index 00000000..6b2a22af --- /dev/null +++ b/src/workos/types/authorization/access_evaluation.py @@ -0,0 +1,7 @@ +from workos.types.workos_model import WorkOSModel + + +class AccessEvaluation(WorkOSModel): + """Representation of a WorkOS Authorization access check result.""" + + authorized: bool diff --git a/src/workos/types/authorization/organization_membership.py b/src/workos/types/authorization/organization_membership.py new file mode 100644 index 00000000..1ddc3ef7 --- /dev/null +++ b/src/workos/types/authorization/organization_membership.py @@ -0,0 +1,23 @@ +from typing import Any, Literal, Mapping, Optional + +from workos.types.workos_model import WorkOSModel +from workos.typing.literals import LiteralOrUntyped + +OrganizationMembershipStatus = Literal["active", "inactive", "pending"] + + +class AuthorizationOrganizationMembership(WorkOSModel): + """Representation of an Organization Membership returned by Authorization endpoints. + + This is a separate type from the user_management OrganizationMembership because + authorization endpoints return memberships without `role` and `organization_name` fields. + """ + + object: Literal["organization_membership"] + id: str + user_id: str + organization_id: str + status: LiteralOrUntyped[OrganizationMembershipStatus] + custom_attributes: Optional[Mapping[str, Any]] = None + created_at: str + updated_at: str diff --git a/src/workos/types/authorization/resource.py b/src/workos/types/authorization/resource.py new file mode 100644 index 00000000..65aef33b --- /dev/null +++ b/src/workos/types/authorization/resource.py @@ -0,0 +1,18 @@ +from typing import Any, Literal, Mapping, Optional + +from workos.types.workos_model import WorkOSModel + + +class Resource(WorkOSModel): + """Representation of a WorkOS Authorization Resource.""" + + object: Literal["authorization_resource"] + id: str + resource_type: str + resource_id: str + organization_id: str + external_id: Optional[str] = None + meta: Optional[Mapping[str, Any]] = None + environment_id: str + created_at: str + updated_at: str diff --git a/src/workos/types/authorization/role_assignment.py b/src/workos/types/authorization/role_assignment.py new file mode 100644 index 00000000..e06c04c2 --- /dev/null +++ b/src/workos/types/authorization/role_assignment.py @@ -0,0 +1,13 @@ +from typing import Literal + +from workos.types.workos_model import WorkOSModel + + +class RoleAssignment(WorkOSModel): + """Representation of a WorkOS Authorization Role Assignment.""" + + object: Literal["role_assignment"] + id: str + role_slug: str + role_name: str + role_id: str diff --git a/src/workos/types/list_resource.py b/src/workos/types/list_resource.py index e8621ab9..db07dcf0 100644 --- a/src/workos/types/list_resource.py +++ b/src/workos/types/list_resource.py @@ -19,7 +19,12 @@ from typing_extensions import Required, TypedDict from workos.types.api_keys import ApiKey from workos.types.audit_logs import AuditLogAction, AuditLogSchema +from workos.types.authorization.organization_membership import ( + AuthorizationOrganizationMembership, +) from workos.types.authorization.permission import Permission +from workos.types.authorization.resource import Resource +from workos.types.authorization.role_assignment import RoleAssignment from workos.types.directory_sync import ( Directory, DirectoryGroup, @@ -59,6 +64,9 @@ Organization, OrganizationMembership, Permission, + Resource, + RoleAssignment, + AuthorizationOrganizationMembership, AuthorizationResource, AuthorizationResourceType, User, diff --git a/src/workos/utils/_base_http_client.py b/src/workos/utils/_base_http_client.py index 49dcbcf5..e2164f1a 100644 --- a/src/workos/utils/_base_http_client.py +++ b/src/workos/utils/_base_http_client.py @@ -123,6 +123,7 @@ def _prepare_request( json: JsonType = None, headers: HeadersType = None, exclude_default_auth_headers: bool = False, + force_include_body: bool = False, ) -> PreparedRequest: """Executes a request against the WorkOS API. @@ -134,6 +135,7 @@ def _prepare_request( params Optional[dict]: Query params or body payload to be added to the request headers Optional[dict]: Custom headers to be added to the request token Optional[str]: Bearer token + force_include_body (bool): If True, allows sending a body with DELETE requests Returns: dict: Response from WorkOS @@ -149,7 +151,7 @@ def _prepare_request( REQUEST_METHOD_GET, ] - if bodyless_http_method and json is not None: + if bodyless_http_method and json is not None and not force_include_body: raise ValueError(f"Cannot send a body with a {parsed_method} request") # Remove any parameters that are None @@ -161,7 +163,7 @@ def _prepare_request( json = {k: v for k, v in json.items() if v is not None} # We'll spread these return values onto the HTTP client request method - if bodyless_http_method: + if bodyless_http_method and not force_include_body: return { "method": parsed_method, "url": url, diff --git a/src/workos/utils/http_client.py b/src/workos/utils/http_client.py index 203c7df0..0af5834c 100644 --- a/src/workos/utils/http_client.py +++ b/src/workos/utils/http_client.py @@ -14,7 +14,7 @@ ParamsType, ResponseJson, ) -from workos.utils.request_helper import REQUEST_METHOD_GET +from workos.utils.request_helper import REQUEST_METHOD_DELETE, REQUEST_METHOD_GET class SyncHttpxClientWrapper(httpx.Client): @@ -113,6 +113,23 @@ def request( response = self._client.request(**prepared_request_parameters) return self._handle_response(response) + def delete_with_body( + self, + path: str, + json: JsonType = None, + headers: HeadersType = None, + ) -> ResponseJson: + """Executes a DELETE request with a JSON body against the WorkOS API.""" + prepared_request_parameters = self._prepare_request( + path=path, + method=REQUEST_METHOD_DELETE, + json=json, + headers=headers, + force_include_body=True, + ) + response = self._client.request(**prepared_request_parameters) + return self._handle_response(response) + class AsyncHttpxClientWrapper(httpx.AsyncClient): def __del__(self) -> None: @@ -210,5 +227,22 @@ async def request( response = await self._client.request(**prepared_request_parameters) return self._handle_response(response) + async def delete_with_body( + self, + path: str, + json: JsonType = None, + headers: HeadersType = None, + ) -> ResponseJson: + """Executes a DELETE request with a JSON body against the WorkOS API.""" + prepared_request_parameters = self._prepare_request( + path=path, + method=REQUEST_METHOD_DELETE, + json=json, + headers=headers, + force_include_body=True, + ) + response = await self._client.request(**prepared_request_parameters) + return self._handle_response(response) + HTTPClient = Union[AsyncHTTPClient, SyncHTTPClient] From 5f6892d3c6f0dbc017ca23cc4fd7e769489a583b Mon Sep 17 00:00:00 2001 From: swaroopakkineni Date: Thu, 19 Feb 2026 11:00:21 -1000 Subject: [PATCH 2/4] moar --- src/workos/types/authorization/__init__.py | 6 +++++- .../authorization/organization_membership.py | 3 ++- src/workos/types/authorization/resource.py | 10 +++++---- .../types/authorization/role_assignment.py | 21 ++++++++++++++++--- 4 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/workos/types/authorization/__init__.py b/src/workos/types/authorization/__init__.py index ecdafa79..9eb705a0 100644 --- a/src/workos/types/authorization/__init__.py +++ b/src/workos/types/authorization/__init__.py @@ -17,4 +17,8 @@ Role, RoleList, ) -from workos.types.authorization.role_assignment import RoleAssignment +from workos.types.authorization.role_assignment import ( + RoleAssignment, + RoleAssignmentResource, + RoleAssignmentRole, +) diff --git a/src/workos/types/authorization/organization_membership.py b/src/workos/types/authorization/organization_membership.py index 1ddc3ef7..73d1983c 100644 --- a/src/workos/types/authorization/organization_membership.py +++ b/src/workos/types/authorization/organization_membership.py @@ -10,13 +10,14 @@ class AuthorizationOrganizationMembership(WorkOSModel): """Representation of an Organization Membership returned by Authorization endpoints. This is a separate type from the user_management OrganizationMembership because - authorization endpoints return memberships without `role` and `organization_name` fields. + authorization endpoints return memberships without the `role` field. """ object: Literal["organization_membership"] id: str user_id: str organization_id: str + organization_name: str status: LiteralOrUntyped[OrganizationMembershipStatus] custom_attributes: Optional[Mapping[str, Any]] = None created_at: str diff --git a/src/workos/types/authorization/resource.py b/src/workos/types/authorization/resource.py index 65aef33b..e3525ffe 100644 --- a/src/workos/types/authorization/resource.py +++ b/src/workos/types/authorization/resource.py @@ -8,11 +8,13 @@ class Resource(WorkOSModel): object: Literal["authorization_resource"] id: str - resource_type: str - resource_id: str + external_id: str + name: str + description: Optional[str] = None + resource_type_slug: str organization_id: str - external_id: Optional[str] = None + parent_resource_id: Optional[str] = None meta: Optional[Mapping[str, Any]] = None - environment_id: str + environment_id: Optional[str] = None created_at: str updated_at: str diff --git a/src/workos/types/authorization/role_assignment.py b/src/workos/types/authorization/role_assignment.py index e06c04c2..784eb589 100644 --- a/src/workos/types/authorization/role_assignment.py +++ b/src/workos/types/authorization/role_assignment.py @@ -3,11 +3,26 @@ from workos.types.workos_model import WorkOSModel +class RoleAssignmentRole(WorkOSModel): + """The role associated with a role assignment.""" + + slug: str + + +class RoleAssignmentResource(WorkOSModel): + """The resource associated with a role assignment.""" + + id: str + external_id: str + resource_type_slug: str + + class RoleAssignment(WorkOSModel): """Representation of a WorkOS Authorization Role Assignment.""" object: Literal["role_assignment"] id: str - role_slug: str - role_name: str - role_id: str + role: RoleAssignmentRole + resource: RoleAssignmentResource + created_at: str + updated_at: str From 0bc40fb39b5d4ff4a1171dc4ae020eab3e4bd249 Mon Sep 17 00:00:00 2001 From: swaroopakkineni Date: Thu, 19 Feb 2026 14:27:27 -1000 Subject: [PATCH 3/4] lol --- .../authorization/{resource.py => authorization_resource.py} | 5 +---- src/workos/types/authorization/role_assignment.py | 3 --- src/workos/utils/_base_http_client.py | 2 +- 3 files changed, 2 insertions(+), 8 deletions(-) rename src/workos/types/authorization/{resource.py => authorization_resource.py} (68%) diff --git a/src/workos/types/authorization/resource.py b/src/workos/types/authorization/authorization_resource.py similarity index 68% rename from src/workos/types/authorization/resource.py rename to src/workos/types/authorization/authorization_resource.py index e3525ffe..769d93fd 100644 --- a/src/workos/types/authorization/resource.py +++ b/src/workos/types/authorization/authorization_resource.py @@ -3,8 +3,7 @@ from workos.types.workos_model import WorkOSModel -class Resource(WorkOSModel): - """Representation of a WorkOS Authorization Resource.""" +class AuthorizationResource(WorkOSModel): object: Literal["authorization_resource"] id: str @@ -14,7 +13,5 @@ class Resource(WorkOSModel): resource_type_slug: str organization_id: str parent_resource_id: Optional[str] = None - meta: Optional[Mapping[str, Any]] = None - environment_id: Optional[str] = None created_at: str updated_at: str diff --git a/src/workos/types/authorization/role_assignment.py b/src/workos/types/authorization/role_assignment.py index 784eb589..5d0888de 100644 --- a/src/workos/types/authorization/role_assignment.py +++ b/src/workos/types/authorization/role_assignment.py @@ -4,13 +4,11 @@ class RoleAssignmentRole(WorkOSModel): - """The role associated with a role assignment.""" slug: str class RoleAssignmentResource(WorkOSModel): - """The resource associated with a role assignment.""" id: str external_id: str @@ -18,7 +16,6 @@ class RoleAssignmentResource(WorkOSModel): class RoleAssignment(WorkOSModel): - """Representation of a WorkOS Authorization Role Assignment.""" object: Literal["role_assignment"] id: str diff --git a/src/workos/utils/_base_http_client.py b/src/workos/utils/_base_http_client.py index e2164f1a..f467b371 100644 --- a/src/workos/utils/_base_http_client.py +++ b/src/workos/utils/_base_http_client.py @@ -135,7 +135,7 @@ def _prepare_request( params Optional[dict]: Query params or body payload to be added to the request headers Optional[dict]: Custom headers to be added to the request token Optional[str]: Bearer token - force_include_body (bool): If True, allows sending a body with DELETE requests + force_include_body (bool): If True, allows sending a body in a bodyless request (used for DELETE requests) Returns: dict: Response from WorkOS From 526eb7d61fa1c83bad2a6f55ade102944bbec81f Mon Sep 17 00:00:00 2001 From: swaroopakkineni Date: Thu, 19 Feb 2026 14:35:55 -1000 Subject: [PATCH 4/4] lol --- .../authorization/organization_membership.py | 4 +- ...{authorization_resource.py => resource.py} | 5 +- .../types/authorization/role_assignment.py | 3 - src/workos/utils/_base_http_client.py | 2 +- src/workos/utils/http_client.py | 8 + tests/test_async_http_client.py | 37 +++++ tests/test_authorization_types.py | 144 ++++++++++++++++++ tests/test_sync_http_client.py | 33 ++++ tests/utils/fixtures/mock_resource.py | 25 +++ tests/utils/fixtures/mock_role_assignment.py | 31 ++++ 10 files changed, 285 insertions(+), 7 deletions(-) rename src/workos/types/authorization/{authorization_resource.py => resource.py} (72%) create mode 100644 tests/test_authorization_types.py create mode 100644 tests/utils/fixtures/mock_resource.py create mode 100644 tests/utils/fixtures/mock_role_assignment.py diff --git a/src/workos/types/authorization/organization_membership.py b/src/workos/types/authorization/organization_membership.py index 73d1983c..50d77bec 100644 --- a/src/workos/types/authorization/organization_membership.py +++ b/src/workos/types/authorization/organization_membership.py @@ -10,7 +10,9 @@ class AuthorizationOrganizationMembership(WorkOSModel): """Representation of an Organization Membership returned by Authorization endpoints. This is a separate type from the user_management OrganizationMembership because - authorization endpoints return memberships without the `role` field. + authorization endpoints return memberships without the ``role`` field and include + ``organization_name``. Additionally, ``custom_attributes`` is optional here as + authorization endpoints may omit it. """ object: Literal["organization_membership"] diff --git a/src/workos/types/authorization/authorization_resource.py b/src/workos/types/authorization/resource.py similarity index 72% rename from src/workos/types/authorization/authorization_resource.py rename to src/workos/types/authorization/resource.py index 769d93fd..917673c4 100644 --- a/src/workos/types/authorization/authorization_resource.py +++ b/src/workos/types/authorization/resource.py @@ -1,9 +1,10 @@ -from typing import Any, Literal, Mapping, Optional +from typing import Literal, Optional from workos.types.workos_model import WorkOSModel -class AuthorizationResource(WorkOSModel): +class Resource(WorkOSModel): + """Representation of an Authorization Resource.""" object: Literal["authorization_resource"] id: str diff --git a/src/workos/types/authorization/role_assignment.py b/src/workos/types/authorization/role_assignment.py index 5d0888de..9ca59936 100644 --- a/src/workos/types/authorization/role_assignment.py +++ b/src/workos/types/authorization/role_assignment.py @@ -4,19 +4,16 @@ class RoleAssignmentRole(WorkOSModel): - slug: str class RoleAssignmentResource(WorkOSModel): - id: str external_id: str resource_type_slug: str class RoleAssignment(WorkOSModel): - object: Literal["role_assignment"] id: str role: RoleAssignmentRole diff --git a/src/workos/utils/_base_http_client.py b/src/workos/utils/_base_http_client.py index f467b371..ad5ebaa5 100644 --- a/src/workos/utils/_base_http_client.py +++ b/src/workos/utils/_base_http_client.py @@ -134,7 +134,7 @@ def _prepare_request( method Optional[str]: One of the supported methods as defined by the REQUEST_METHOD_X constants params Optional[dict]: Query params or body payload to be added to the request headers Optional[dict]: Custom headers to be added to the request - token Optional[str]: Bearer token + exclude_default_auth_headers (bool): If True, excludes default auth headers from the request force_include_body (bool): If True, allows sending a body in a bodyless request (used for DELETE requests) Returns: diff --git a/src/workos/utils/http_client.py b/src/workos/utils/http_client.py index 0af5834c..9a2d7a57 100644 --- a/src/workos/utils/http_client.py +++ b/src/workos/utils/http_client.py @@ -117,14 +117,18 @@ def delete_with_body( self, path: str, json: JsonType = None, + params: ParamsType = None, headers: HeadersType = None, + exclude_default_auth_headers: bool = False, ) -> ResponseJson: """Executes a DELETE request with a JSON body against the WorkOS API.""" prepared_request_parameters = self._prepare_request( path=path, method=REQUEST_METHOD_DELETE, json=json, + params=params, headers=headers, + exclude_default_auth_headers=exclude_default_auth_headers, force_include_body=True, ) response = self._client.request(**prepared_request_parameters) @@ -231,14 +235,18 @@ async def delete_with_body( self, path: str, json: JsonType = None, + params: ParamsType = None, headers: HeadersType = None, + exclude_default_auth_headers: bool = False, ) -> ResponseJson: """Executes a DELETE request with a JSON body against the WorkOS API.""" prepared_request_parameters = self._prepare_request( path=path, method=REQUEST_METHOD_DELETE, json=json, + params=params, headers=headers, + exclude_default_auth_headers=exclude_default_auth_headers, force_include_body=True, ) response = await self._client.request(**prepared_request_parameters) diff --git a/tests/test_async_http_client.py b/tests/test_async_http_client.py index 633ed71a..c9da72ba 100644 --- a/tests/test_async_http_client.py +++ b/tests/test_async_http_client.py @@ -326,3 +326,40 @@ async def test_request_removes_none_json_values( json={"organization_id": None, "test": "value"}, ) assert request_kwargs["json"] == {"test": "value"} + + async def test_delete_with_body_sends_json( + self, capture_and_mock_http_client_request + ): + request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200) + + await self.http_client.delete_with_body( + path="/test", + json={"resource_id": "res_01ABC"}, + ) + + assert request_kwargs["method"] == "delete" + assert request_kwargs["json"] == {"resource_id": "res_01ABC"} + + async def test_delete_with_body_sends_params( + self, capture_and_mock_http_client_request + ): + request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200) + + await self.http_client.delete_with_body( + path="/test", + json={"resource_id": "res_01ABC"}, + params={"org_id": "org_01ABC"}, + ) + + assert request_kwargs["params"] == {"org_id": "org_01ABC"} + assert request_kwargs["json"] == {"resource_id": "res_01ABC"} + + async def test_delete_without_body_raises_value_error(self): + with pytest.raises( + ValueError, match="Cannot send a body with a delete request" + ): + await self.http_client.request( + path="/test", + method="delete", + json={"should": "fail"}, + ) diff --git a/tests/test_authorization_types.py b/tests/test_authorization_types.py new file mode 100644 index 00000000..a3480bb5 --- /dev/null +++ b/tests/test_authorization_types.py @@ -0,0 +1,144 @@ +"""Tests for new authorization types: Resource, RoleAssignment, AccessEvaluation, +AuthorizationOrganizationMembership.""" + +from workos.types.authorization import ( + AccessEvaluation, + AuthorizationOrganizationMembership, + Resource, + RoleAssignment, + RoleAssignmentResource, + RoleAssignmentRole, +) + + +class TestAccessEvaluation: + def test_authorized_true(self): + result = AccessEvaluation(authorized=True) + assert result.authorized is True + + def test_authorized_false(self): + result = AccessEvaluation(authorized=False) + assert result.authorized is False + + def test_from_dict(self): + result = AccessEvaluation.model_validate({"authorized": True}) + assert result.authorized is True + + +class TestResource: + def test_resource_deserialization(self): + data = { + "object": "authorization_resource", + "id": "res_01ABC", + "external_id": "ext_123", + "name": "Test Document", + "resource_type_slug": "document", + "organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + resource = Resource.model_validate(data) + + assert resource.object == "authorization_resource" + assert resource.id == "res_01ABC" + assert resource.external_id == "ext_123" + assert resource.name == "Test Document" + assert resource.resource_type_slug == "document" + assert resource.organization_id == "org_01EHT88Z8J8795GZNQ4ZP1J81T" + assert resource.description is None + assert resource.parent_resource_id is None + + def test_resource_with_optional_fields(self): + data = { + "object": "authorization_resource", + "id": "res_01ABC", + "external_id": "ext_123", + "name": "Test Document", + "description": "A test document resource", + "resource_type_slug": "document", + "organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T", + "parent_resource_id": "res_01PARENT", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + resource = Resource.model_validate(data) + + assert resource.description == "A test document resource" + assert resource.parent_resource_id == "res_01PARENT" + + +class TestRoleAssignment: + def test_role_assignment_deserialization(self): + data = { + "object": "role_assignment", + "id": "ra_01ABC", + "role": {"slug": "admin"}, + "resource": { + "id": "res_01ABC", + "external_id": "ext_123", + "resource_type_slug": "document", + }, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + assignment = RoleAssignment.model_validate(data) + + assert assignment.object == "role_assignment" + assert assignment.id == "ra_01ABC" + assert assignment.role.slug == "admin" + assert assignment.resource.id == "res_01ABC" + assert assignment.resource.external_id == "ext_123" + assert assignment.resource.resource_type_slug == "document" + + def test_role_assignment_role(self): + role = RoleAssignmentRole(slug="editor") + assert role.slug == "editor" + + def test_role_assignment_resource(self): + resource = RoleAssignmentResource( + id="res_01ABC", + external_id="ext_123", + resource_type_slug="document", + ) + assert resource.id == "res_01ABC" + assert resource.external_id == "ext_123" + assert resource.resource_type_slug == "document" + + +class TestAuthorizationOrganizationMembership: + def test_membership_deserialization(self): + data = { + "object": "organization_membership", + "id": "om_01ABC", + "user_id": "user_01ABC", + "organization_id": "org_01ABC", + "organization_name": "Test Org", + "status": "active", + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + membership = AuthorizationOrganizationMembership.model_validate(data) + + assert membership.object == "organization_membership" + assert membership.id == "om_01ABC" + assert membership.user_id == "user_01ABC" + assert membership.organization_id == "org_01ABC" + assert membership.organization_name == "Test Org" + assert membership.status == "active" + assert membership.custom_attributes is None + + def test_membership_with_custom_attributes(self): + data = { + "object": "organization_membership", + "id": "om_01ABC", + "user_id": "user_01ABC", + "organization_id": "org_01ABC", + "organization_name": "Test Org", + "status": "active", + "custom_attributes": {"department": "Engineering"}, + "created_at": "2024-01-01T00:00:00Z", + "updated_at": "2024-01-01T00:00:00Z", + } + membership = AuthorizationOrganizationMembership.model_validate(data) + + assert membership.custom_attributes == {"department": "Engineering"} diff --git a/tests/test_sync_http_client.py b/tests/test_sync_http_client.py index edbba0b4..c75ae504 100644 --- a/tests/test_sync_http_client.py +++ b/tests/test_sync_http_client.py @@ -372,3 +372,36 @@ def test_request_removes_none_json_values( json={"organization_id": None, "test": "value"}, ) assert request_kwargs["json"] == {"test": "value"} + + def test_delete_with_body_sends_json(self, capture_and_mock_http_client_request): + request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200) + + self.http_client.delete_with_body( + path="/test", + json={"resource_id": "res_01ABC"}, + ) + + assert request_kwargs["method"] == "delete" + assert request_kwargs["json"] == {"resource_id": "res_01ABC"} + + def test_delete_with_body_sends_params(self, capture_and_mock_http_client_request): + request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200) + + self.http_client.delete_with_body( + path="/test", + json={"resource_id": "res_01ABC"}, + params={"org_id": "org_01ABC"}, + ) + + assert request_kwargs["params"] == {"org_id": "org_01ABC"} + assert request_kwargs["json"] == {"resource_id": "res_01ABC"} + + def test_delete_without_body_raises_value_error(self): + with pytest.raises( + ValueError, match="Cannot send a body with a delete request" + ): + self.http_client.request( + path="/test", + method="delete", + json={"should": "fail"}, + ) diff --git a/tests/utils/fixtures/mock_resource.py b/tests/utils/fixtures/mock_resource.py new file mode 100644 index 00000000..825bf5fb --- /dev/null +++ b/tests/utils/fixtures/mock_resource.py @@ -0,0 +1,25 @@ +import datetime + +from workos.types.authorization.resource import Resource + + +class MockResource(Resource): + def __init__( + self, + id: str = "res_01ABC", + external_id: str = "ext_123", + name: str = "Test Resource", + resource_type_slug: str = "document", + organization_id: str = "org_01EHT88Z8J8795GZNQ4ZP1J81T", + ): + now = datetime.datetime.now().isoformat() + super().__init__( + object="authorization_resource", + id=id, + external_id=external_id, + name=name, + resource_type_slug=resource_type_slug, + organization_id=organization_id, + created_at=now, + updated_at=now, + ) diff --git a/tests/utils/fixtures/mock_role_assignment.py b/tests/utils/fixtures/mock_role_assignment.py new file mode 100644 index 00000000..23b2dcde --- /dev/null +++ b/tests/utils/fixtures/mock_role_assignment.py @@ -0,0 +1,31 @@ +import datetime + +from workos.types.authorization.role_assignment import ( + RoleAssignment, + RoleAssignmentResource, + RoleAssignmentRole, +) + + +class MockRoleAssignment(RoleAssignment): + def __init__( + self, + id: str = "ra_01ABC", + role_slug: str = "admin", + resource_id: str = "res_01ABC", + resource_external_id: str = "ext_123", + resource_type_slug: str = "document", + ): + now = datetime.datetime.now().isoformat() + super().__init__( + object="role_assignment", + id=id, + role=RoleAssignmentRole(slug=role_slug), + resource=RoleAssignmentResource( + id=resource_id, + external_id=resource_external_id, + resource_type_slug=resource_type_slug, + ), + created_at=now, + updated_at=now, + )