Skip to content

Commit 526eb7d

Browse files
lol
1 parent 0bc40fb commit 526eb7d

File tree

10 files changed

+285
-7
lines changed

10 files changed

+285
-7
lines changed

src/workos/types/authorization/organization_membership.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ class AuthorizationOrganizationMembership(WorkOSModel):
1010
"""Representation of an Organization Membership returned by Authorization endpoints.
1111
1212
This is a separate type from the user_management OrganizationMembership because
13-
authorization endpoints return memberships without the `role` field.
13+
authorization endpoints return memberships without the ``role`` field and include
14+
``organization_name``. Additionally, ``custom_attributes`` is optional here as
15+
authorization endpoints may omit it.
1416
"""
1517

1618
object: Literal["organization_membership"]

src/workos/types/authorization/authorization_resource.py renamed to src/workos/types/authorization/resource.py

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

33
from workos.types.workos_model import WorkOSModel
44

55

6-
class AuthorizationResource(WorkOSModel):
6+
class Resource(WorkOSModel):
7+
"""Representation of an Authorization Resource."""
78

89
object: Literal["authorization_resource"]
910
id: str

src/workos/types/authorization/role_assignment.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,16 @@
44

55

66
class RoleAssignmentRole(WorkOSModel):
7-
87
slug: str
98

109

1110
class RoleAssignmentResource(WorkOSModel):
12-
1311
id: str
1412
external_id: str
1513
resource_type_slug: str
1614

1715

1816
class RoleAssignment(WorkOSModel):
19-
2017
object: Literal["role_assignment"]
2118
id: str
2219
role: RoleAssignmentRole

src/workos/utils/_base_http_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def _prepare_request(
134134
method Optional[str]: One of the supported methods as defined by the REQUEST_METHOD_X constants
135135
params Optional[dict]: Query params or body payload to be added to the request
136136
headers Optional[dict]: Custom headers to be added to the request
137-
token Optional[str]: Bearer token
137+
exclude_default_auth_headers (bool): If True, excludes default auth headers from the request
138138
force_include_body (bool): If True, allows sending a body in a bodyless request (used for DELETE requests)
139139
140140
Returns:

src/workos/utils/http_client.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,18 @@ def delete_with_body(
117117
self,
118118
path: str,
119119
json: JsonType = None,
120+
params: ParamsType = None,
120121
headers: HeadersType = None,
122+
exclude_default_auth_headers: bool = False,
121123
) -> ResponseJson:
122124
"""Executes a DELETE request with a JSON body against the WorkOS API."""
123125
prepared_request_parameters = self._prepare_request(
124126
path=path,
125127
method=REQUEST_METHOD_DELETE,
126128
json=json,
129+
params=params,
127130
headers=headers,
131+
exclude_default_auth_headers=exclude_default_auth_headers,
128132
force_include_body=True,
129133
)
130134
response = self._client.request(**prepared_request_parameters)
@@ -231,14 +235,18 @@ async def delete_with_body(
231235
self,
232236
path: str,
233237
json: JsonType = None,
238+
params: ParamsType = None,
234239
headers: HeadersType = None,
240+
exclude_default_auth_headers: bool = False,
235241
) -> ResponseJson:
236242
"""Executes a DELETE request with a JSON body against the WorkOS API."""
237243
prepared_request_parameters = self._prepare_request(
238244
path=path,
239245
method=REQUEST_METHOD_DELETE,
240246
json=json,
247+
params=params,
241248
headers=headers,
249+
exclude_default_auth_headers=exclude_default_auth_headers,
242250
force_include_body=True,
243251
)
244252
response = await self._client.request(**prepared_request_parameters)

tests/test_async_http_client.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,3 +326,40 @@ async def test_request_removes_none_json_values(
326326
json={"organization_id": None, "test": "value"},
327327
)
328328
assert request_kwargs["json"] == {"test": "value"}
329+
330+
async def test_delete_with_body_sends_json(
331+
self, capture_and_mock_http_client_request
332+
):
333+
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)
334+
335+
await self.http_client.delete_with_body(
336+
path="/test",
337+
json={"resource_id": "res_01ABC"},
338+
)
339+
340+
assert request_kwargs["method"] == "delete"
341+
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}
342+
343+
async def test_delete_with_body_sends_params(
344+
self, capture_and_mock_http_client_request
345+
):
346+
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)
347+
348+
await self.http_client.delete_with_body(
349+
path="/test",
350+
json={"resource_id": "res_01ABC"},
351+
params={"org_id": "org_01ABC"},
352+
)
353+
354+
assert request_kwargs["params"] == {"org_id": "org_01ABC"}
355+
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}
356+
357+
async def test_delete_without_body_raises_value_error(self):
358+
with pytest.raises(
359+
ValueError, match="Cannot send a body with a delete request"
360+
):
361+
await self.http_client.request(
362+
path="/test",
363+
method="delete",
364+
json={"should": "fail"},
365+
)

tests/test_authorization_types.py

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
"""Tests for new authorization types: Resource, RoleAssignment, AccessEvaluation,
2+
AuthorizationOrganizationMembership."""
3+
4+
from workos.types.authorization import (
5+
AccessEvaluation,
6+
AuthorizationOrganizationMembership,
7+
Resource,
8+
RoleAssignment,
9+
RoleAssignmentResource,
10+
RoleAssignmentRole,
11+
)
12+
13+
14+
class TestAccessEvaluation:
15+
def test_authorized_true(self):
16+
result = AccessEvaluation(authorized=True)
17+
assert result.authorized is True
18+
19+
def test_authorized_false(self):
20+
result = AccessEvaluation(authorized=False)
21+
assert result.authorized is False
22+
23+
def test_from_dict(self):
24+
result = AccessEvaluation.model_validate({"authorized": True})
25+
assert result.authorized is True
26+
27+
28+
class TestResource:
29+
def test_resource_deserialization(self):
30+
data = {
31+
"object": "authorization_resource",
32+
"id": "res_01ABC",
33+
"external_id": "ext_123",
34+
"name": "Test Document",
35+
"resource_type_slug": "document",
36+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
37+
"created_at": "2024-01-01T00:00:00Z",
38+
"updated_at": "2024-01-01T00:00:00Z",
39+
}
40+
resource = Resource.model_validate(data)
41+
42+
assert resource.object == "authorization_resource"
43+
assert resource.id == "res_01ABC"
44+
assert resource.external_id == "ext_123"
45+
assert resource.name == "Test Document"
46+
assert resource.resource_type_slug == "document"
47+
assert resource.organization_id == "org_01EHT88Z8J8795GZNQ4ZP1J81T"
48+
assert resource.description is None
49+
assert resource.parent_resource_id is None
50+
51+
def test_resource_with_optional_fields(self):
52+
data = {
53+
"object": "authorization_resource",
54+
"id": "res_01ABC",
55+
"external_id": "ext_123",
56+
"name": "Test Document",
57+
"description": "A test document resource",
58+
"resource_type_slug": "document",
59+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
60+
"parent_resource_id": "res_01PARENT",
61+
"created_at": "2024-01-01T00:00:00Z",
62+
"updated_at": "2024-01-01T00:00:00Z",
63+
}
64+
resource = Resource.model_validate(data)
65+
66+
assert resource.description == "A test document resource"
67+
assert resource.parent_resource_id == "res_01PARENT"
68+
69+
70+
class TestRoleAssignment:
71+
def test_role_assignment_deserialization(self):
72+
data = {
73+
"object": "role_assignment",
74+
"id": "ra_01ABC",
75+
"role": {"slug": "admin"},
76+
"resource": {
77+
"id": "res_01ABC",
78+
"external_id": "ext_123",
79+
"resource_type_slug": "document",
80+
},
81+
"created_at": "2024-01-01T00:00:00Z",
82+
"updated_at": "2024-01-01T00:00:00Z",
83+
}
84+
assignment = RoleAssignment.model_validate(data)
85+
86+
assert assignment.object == "role_assignment"
87+
assert assignment.id == "ra_01ABC"
88+
assert assignment.role.slug == "admin"
89+
assert assignment.resource.id == "res_01ABC"
90+
assert assignment.resource.external_id == "ext_123"
91+
assert assignment.resource.resource_type_slug == "document"
92+
93+
def test_role_assignment_role(self):
94+
role = RoleAssignmentRole(slug="editor")
95+
assert role.slug == "editor"
96+
97+
def test_role_assignment_resource(self):
98+
resource = RoleAssignmentResource(
99+
id="res_01ABC",
100+
external_id="ext_123",
101+
resource_type_slug="document",
102+
)
103+
assert resource.id == "res_01ABC"
104+
assert resource.external_id == "ext_123"
105+
assert resource.resource_type_slug == "document"
106+
107+
108+
class TestAuthorizationOrganizationMembership:
109+
def test_membership_deserialization(self):
110+
data = {
111+
"object": "organization_membership",
112+
"id": "om_01ABC",
113+
"user_id": "user_01ABC",
114+
"organization_id": "org_01ABC",
115+
"organization_name": "Test Org",
116+
"status": "active",
117+
"created_at": "2024-01-01T00:00:00Z",
118+
"updated_at": "2024-01-01T00:00:00Z",
119+
}
120+
membership = AuthorizationOrganizationMembership.model_validate(data)
121+
122+
assert membership.object == "organization_membership"
123+
assert membership.id == "om_01ABC"
124+
assert membership.user_id == "user_01ABC"
125+
assert membership.organization_id == "org_01ABC"
126+
assert membership.organization_name == "Test Org"
127+
assert membership.status == "active"
128+
assert membership.custom_attributes is None
129+
130+
def test_membership_with_custom_attributes(self):
131+
data = {
132+
"object": "organization_membership",
133+
"id": "om_01ABC",
134+
"user_id": "user_01ABC",
135+
"organization_id": "org_01ABC",
136+
"organization_name": "Test Org",
137+
"status": "active",
138+
"custom_attributes": {"department": "Engineering"},
139+
"created_at": "2024-01-01T00:00:00Z",
140+
"updated_at": "2024-01-01T00:00:00Z",
141+
}
142+
membership = AuthorizationOrganizationMembership.model_validate(data)
143+
144+
assert membership.custom_attributes == {"department": "Engineering"}

tests/test_sync_http_client.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -372,3 +372,36 @@ def test_request_removes_none_json_values(
372372
json={"organization_id": None, "test": "value"},
373373
)
374374
assert request_kwargs["json"] == {"test": "value"}
375+
376+
def test_delete_with_body_sends_json(self, capture_and_mock_http_client_request):
377+
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)
378+
379+
self.http_client.delete_with_body(
380+
path="/test",
381+
json={"resource_id": "res_01ABC"},
382+
)
383+
384+
assert request_kwargs["method"] == "delete"
385+
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}
386+
387+
def test_delete_with_body_sends_params(self, capture_and_mock_http_client_request):
388+
request_kwargs = capture_and_mock_http_client_request(self.http_client, {}, 200)
389+
390+
self.http_client.delete_with_body(
391+
path="/test",
392+
json={"resource_id": "res_01ABC"},
393+
params={"org_id": "org_01ABC"},
394+
)
395+
396+
assert request_kwargs["params"] == {"org_id": "org_01ABC"}
397+
assert request_kwargs["json"] == {"resource_id": "res_01ABC"}
398+
399+
def test_delete_without_body_raises_value_error(self):
400+
with pytest.raises(
401+
ValueError, match="Cannot send a body with a delete request"
402+
):
403+
self.http_client.request(
404+
path="/test",
405+
method="delete",
406+
json={"should": "fail"},
407+
)
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import datetime
2+
3+
from workos.types.authorization.resource import Resource
4+
5+
6+
class MockResource(Resource):
7+
def __init__(
8+
self,
9+
id: str = "res_01ABC",
10+
external_id: str = "ext_123",
11+
name: str = "Test Resource",
12+
resource_type_slug: str = "document",
13+
organization_id: str = "org_01EHT88Z8J8795GZNQ4ZP1J81T",
14+
):
15+
now = datetime.datetime.now().isoformat()
16+
super().__init__(
17+
object="authorization_resource",
18+
id=id,
19+
external_id=external_id,
20+
name=name,
21+
resource_type_slug=resource_type_slug,
22+
organization_id=organization_id,
23+
created_at=now,
24+
updated_at=now,
25+
)
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import datetime
2+
3+
from workos.types.authorization.role_assignment import (
4+
RoleAssignment,
5+
RoleAssignmentResource,
6+
RoleAssignmentRole,
7+
)
8+
9+
10+
class MockRoleAssignment(RoleAssignment):
11+
def __init__(
12+
self,
13+
id: str = "ra_01ABC",
14+
role_slug: str = "admin",
15+
resource_id: str = "res_01ABC",
16+
resource_external_id: str = "ext_123",
17+
resource_type_slug: str = "document",
18+
):
19+
now = datetime.datetime.now().isoformat()
20+
super().__init__(
21+
object="role_assignment",
22+
id=id,
23+
role=RoleAssignmentRole(slug=role_slug),
24+
resource=RoleAssignmentResource(
25+
id=resource_id,
26+
external_id=resource_external_id,
27+
resource_type_slug=resource_type_slug,
28+
),
29+
created_at=now,
30+
updated_at=now,
31+
)

0 commit comments

Comments
 (0)