Skip to content

Commit 87a1296

Browse files
lol
2 parents 4ccc1d9 + 8dd434f commit 87a1296

File tree

8 files changed

+737
-9
lines changed

8 files changed

+737
-9
lines changed

src/workos/authorization.py

Lines changed: 462 additions & 3 deletions
Large diffs are not rendered by default.

src/workos/types/authorization/organization_membership.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
from typing import Any, Literal, Mapping, Optional
22

3+
from workos.types.user_management.organization_membership_status import (
4+
OrganizationMembershipStatus,
5+
)
36
from workos.types.workos_model import WorkOSModel
47
from workos.typing.literals import LiteralOrUntyped
58

6-
OrganizationMembershipStatus = Literal["active", "inactive", "pending"]
7-
89

910
class AuthorizationOrganizationMembership(WorkOSModel):
1011
"""Representation of an Organization Membership returned by Authorization endpoints.

src/workos/types/user_management/list_filters.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional, Sequence
22
from workos.types.list_resource import ListArgs
3-
from workos.types.user_management.organization_membership import (
3+
from workos.types.user_management.organization_membership_status import (
44
OrganizationMembershipStatus,
55
)
66

src/workos/types/user_management/organization_membership.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
from typing import Any, Literal, Mapping, Optional, Sequence
22
from typing_extensions import TypedDict
33

4+
from workos.types.user_management.organization_membership_status import (
5+
OrganizationMembershipStatus,
6+
)
47
from workos.types.workos_model import WorkOSModel
58
from workos.typing.literals import LiteralOrUntyped
69

7-
OrganizationMembershipStatus = Literal["active", "inactive", "pending"]
8-
910

1011
class OrganizationMembershipRole(TypedDict):
1112
slug: str
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from typing import Literal
2+
3+
OrganizationMembershipStatus = Literal["active", "inactive", "pending"]

src/workos/utils/_base_http_client.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,7 @@ def _prepare_request(
124124
headers: HeadersType = None,
125125
exclude_default_auth_headers: bool = False,
126126
force_include_body: bool = False,
127+
exclude_none: bool = True,
127128
) -> PreparedRequest:
128129
"""Executes a request against the WorkOS API.
129130
@@ -159,7 +160,7 @@ def _prepare_request(
159160
params = {k: v for k, v in params.items() if v is not None}
160161

161162
# Remove any body values that are None
162-
if json is not None and isinstance(json, Mapping):
163+
if exclude_none and json is not None and isinstance(json, Mapping):
163164
json = {k: v for k, v in json.items() if v is not None}
164165

165166
# We'll spread these return values onto the HTTP client request method

src/workos/utils/http_client.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ def request(
8888
json: JsonType = None,
8989
headers: HeadersType = None,
9090
exclude_default_auth_headers: bool = False,
91+
exclude_none: bool = True,
9192
) -> ResponseJson:
9293
"""Executes a request against the WorkOS API.
9394
@@ -98,6 +99,7 @@ def request(
9899
method (str): One of the supported methods as defined by the REQUEST_METHOD_X constants
99100
params (ParamsType): Query params to be added to the request
100101
json (JsonType): Body payload to be added to the request
102+
exclude_none (bool): If True, removes None values from the JSON body
101103
102104
Returns:
103105
ResponseJson: Response from WorkOS
@@ -109,6 +111,7 @@ def request(
109111
json=json,
110112
headers=headers,
111113
exclude_default_auth_headers=exclude_default_auth_headers,
114+
exclude_none=exclude_none,
112115
)
113116
response = self._client.request(**prepared_request_parameters)
114117
return self._handle_response(response)
@@ -206,6 +209,7 @@ async def request(
206209
json: JsonType = None,
207210
headers: HeadersType = None,
208211
exclude_default_auth_headers: bool = False,
212+
exclude_none: bool = True,
209213
) -> ResponseJson:
210214
"""Executes a request against the WorkOS API.
211215
@@ -216,6 +220,7 @@ async def request(
216220
method (str): One of the supported methods as defined by the REQUEST_METHOD_X constants
217221
params (ParamsType): Query params to be added to the request
218222
json (JsonType): Body payload to be added to the request
223+
exclude_none (bool): If True, removes None values from the JSON body
219224
220225
Returns:
221226
ResponseJson: Response from WorkOS
@@ -227,6 +232,7 @@ async def request(
227232
json=json,
228233
headers=headers,
229234
exclude_default_auth_headers=exclude_default_auth_headers,
235+
exclude_none=exclude_none,
230236
)
231237
response = await self._client.request(**prepared_request_parameters)
232238
return self._handle_response(response)
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
from typing import Union
2+
3+
import pytest
4+
from tests.utils.fixtures.mock_resource import MockResource
5+
from tests.utils.syncify import syncify
6+
from workos.authorization import AsyncAuthorization, Authorization
7+
8+
9+
@pytest.mark.sync_and_async(Authorization, AsyncAuthorization)
10+
class TestAuthorizationResourceCRUD:
11+
@pytest.fixture(autouse=True)
12+
def setup(self, module_instance: Union[Authorization, AsyncAuthorization]):
13+
self.http_client = module_instance._http_client
14+
self.authorization = module_instance
15+
16+
@pytest.fixture
17+
def mock_resource(self):
18+
return MockResource(id="res_01ABC").dict()
19+
20+
# --- get_resource ---
21+
22+
def test_get_resource(self, mock_resource, capture_and_mock_http_client_request):
23+
request_kwargs = capture_and_mock_http_client_request(
24+
self.http_client, mock_resource, 200
25+
)
26+
27+
resource = syncify(self.authorization.get_resource("res_01ABC"))
28+
29+
assert resource.id == "res_01ABC"
30+
assert resource.object == "authorization_resource"
31+
assert request_kwargs["method"] == "get"
32+
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
33+
34+
# --- create_resource ---
35+
36+
def test_create_resource_required_fields_only(
37+
self, mock_resource, capture_and_mock_http_client_request
38+
):
39+
request_kwargs = capture_and_mock_http_client_request(
40+
self.http_client, mock_resource, 201
41+
)
42+
43+
resource = syncify(
44+
self.authorization.create_resource(
45+
resource_type_slug="document",
46+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
47+
external_id="ext_123",
48+
name="Test Resource",
49+
parent={"parent_resource_id": "res_01PARENT"},
50+
)
51+
)
52+
53+
assert resource.id == "res_01ABC"
54+
assert request_kwargs["method"] == "post"
55+
assert request_kwargs["url"].endswith("/authorization/resources")
56+
assert request_kwargs["json"] == {
57+
"resource_type_slug": "document",
58+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
59+
"external_id": "ext_123",
60+
"name": "Test Resource",
61+
"parent_resource_id": "res_01PARENT",
62+
}
63+
64+
def test_create_resource_without_parent(
65+
self, mock_resource, capture_and_mock_http_client_request
66+
):
67+
request_kwargs = capture_and_mock_http_client_request(
68+
self.http_client, mock_resource, 201
69+
)
70+
71+
resource = syncify(
72+
self.authorization.create_resource(
73+
resource_type_slug="document",
74+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
75+
external_id="ext_123",
76+
name="Test Resource",
77+
)
78+
)
79+
80+
assert resource.id == "res_01ABC"
81+
assert request_kwargs["method"] == "post"
82+
assert request_kwargs["json"] == {
83+
"resource_type_slug": "document",
84+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
85+
"external_id": "ext_123",
86+
"name": "Test Resource",
87+
}
88+
89+
def test_create_resource_with_all_optional_fields(
90+
self, mock_resource, capture_and_mock_http_client_request
91+
):
92+
request_kwargs = capture_and_mock_http_client_request(
93+
self.http_client, mock_resource, 201
94+
)
95+
96+
syncify(
97+
self.authorization.create_resource(
98+
resource_type_slug="document",
99+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
100+
external_id="ext_123",
101+
name="Test Resource",
102+
parent={"parent_resource_id": "res_01PARENT"},
103+
description="A test document",
104+
)
105+
)
106+
107+
assert request_kwargs["json"] == {
108+
"resource_type_slug": "document",
109+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
110+
"external_id": "ext_123",
111+
"name": "Test Resource",
112+
"parent_resource_id": "res_01PARENT",
113+
"description": "A test document",
114+
}
115+
116+
def test_create_resource_with_parent_by_id(
117+
self, mock_resource, capture_and_mock_http_client_request
118+
):
119+
request_kwargs = capture_and_mock_http_client_request(
120+
self.http_client, mock_resource, 201
121+
)
122+
123+
syncify(
124+
self.authorization.create_resource(
125+
resource_type_slug="document",
126+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
127+
external_id="ext_123",
128+
name="Test Resource",
129+
parent={"parent_resource_id": "res_01PARENT"},
130+
)
131+
)
132+
133+
assert request_kwargs["json"]["parent_resource_id"] == "res_01PARENT"
134+
135+
def test_create_resource_with_parent_by_external_id(
136+
self, mock_resource, capture_and_mock_http_client_request
137+
):
138+
request_kwargs = capture_and_mock_http_client_request(
139+
self.http_client, mock_resource, 201
140+
)
141+
142+
syncify(
143+
self.authorization.create_resource(
144+
resource_type_slug="document",
145+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
146+
external_id="ext_123",
147+
name="Test Resource",
148+
parent={
149+
"parent_resource_external_id": "ext_parent_456",
150+
"parent_resource_type_slug": "folder",
151+
},
152+
)
153+
)
154+
155+
assert request_kwargs["json"]["parent_resource_external_id"] == "ext_parent_456"
156+
assert request_kwargs["json"]["parent_resource_type_slug"] == "folder"
157+
158+
# --- update_resource ---
159+
160+
def test_update_resource_with_meta(
161+
self, mock_resource, capture_and_mock_http_client_request
162+
):
163+
request_kwargs = capture_and_mock_http_client_request(
164+
self.http_client, mock_resource, 200
165+
)
166+
167+
resource = syncify(
168+
self.authorization.update_resource(
169+
"res_01ABC",
170+
name="Updated Name",
171+
description="Updated description",
172+
)
173+
)
174+
175+
assert resource.id == "res_01ABC"
176+
assert request_kwargs["method"] == "patch"
177+
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
178+
assert request_kwargs["json"] == {
179+
"name": "Updated Name",
180+
"description": "Updated description",
181+
}
182+
183+
def test_update_resource_clear_description(
184+
self, mock_resource, capture_and_mock_http_client_request
185+
):
186+
request_kwargs = capture_and_mock_http_client_request(
187+
self.http_client, mock_resource, 200
188+
)
189+
190+
syncify(self.authorization.update_resource("res_01ABC", description=None))
191+
192+
assert request_kwargs["method"] == "patch"
193+
assert request_kwargs["json"] == {"description": None}
194+
195+
def test_update_resource_without_meta(
196+
self, mock_resource, capture_and_mock_http_client_request
197+
):
198+
request_kwargs = capture_and_mock_http_client_request(
199+
self.http_client, mock_resource, 200
200+
)
201+
202+
syncify(self.authorization.update_resource("res_01ABC"))
203+
204+
assert request_kwargs["method"] == "patch"
205+
assert request_kwargs["json"] == {}
206+
207+
def test_update_resource_without_desc(
208+
self, mock_resource, capture_and_mock_http_client_request
209+
):
210+
request_kwargs = capture_and_mock_http_client_request(
211+
self.http_client, mock_resource, 200
212+
)
213+
214+
resource = syncify(
215+
self.authorization.update_resource(
216+
"res_01ABC",
217+
name="Updated Name",
218+
)
219+
)
220+
221+
assert resource.id == "res_01ABC"
222+
assert request_kwargs["method"] == "patch"
223+
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
224+
assert request_kwargs["json"] == {"name": "Updated Name"}
225+
226+
# --- delete_resource ---
227+
228+
def test_delete_resource_without_cascade(
229+
self, capture_and_mock_http_client_request
230+
):
231+
request_kwargs = capture_and_mock_http_client_request(
232+
self.http_client,
233+
status_code=202,
234+
headers={"content-type": "text/plain; charset=utf-8"},
235+
)
236+
237+
response = syncify(self.authorization.delete_resource("res_01ABC"))
238+
239+
assert response is None
240+
assert request_kwargs["method"] == "delete"
241+
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
242+
243+
def test_delete_resource_with_cascade(self, capture_and_mock_http_client_request):
244+
request_kwargs = capture_and_mock_http_client_request(
245+
self.http_client,
246+
status_code=202,
247+
headers={"content-type": "text/plain; charset=utf-8"},
248+
)
249+
250+
response = syncify(
251+
self.authorization.delete_resource("res_01ABC", cascade_delete=True)
252+
)
253+
254+
assert response is None
255+
assert request_kwargs["method"] == "delete"
256+
assert request_kwargs["url"].endswith("/authorization/resources/res_01ABC")
257+
assert request_kwargs["json"] == {"cascade_delete": True}

0 commit comments

Comments
 (0)