Skip to content

Commit 3431022

Browse files
committed
Add organization domains module
1 parent e445949 commit 3431022

File tree

15 files changed

+347
-8
lines changed

15 files changed

+347
-8
lines changed

tests/test_organization_domains.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import datetime
2+
from typing import Union
3+
import pytest
4+
from tests.utils.syncify import syncify
5+
from workos.organization_domains import AsyncOrganizationDomains, OrganizationDomains
6+
7+
8+
@pytest.mark.sync_and_async(OrganizationDomains, AsyncOrganizationDomains)
9+
class TestOrganizationDomains:
10+
@pytest.fixture(autouse=True)
11+
def setup(self, module_instance: Union[OrganizationDomains, AsyncOrganizationDomains]):
12+
self.http_client = module_instance._http_client
13+
self.organization_domains = module_instance
14+
15+
@pytest.fixture
16+
def mock_organization_domain(self):
17+
return {
18+
"object": "organization_domain",
19+
"id": "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8",
20+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
21+
"domain": "example.com",
22+
"state": "pending",
23+
"verification_strategy": "dns",
24+
"verification_token": "workos_example_verification_token_12345",
25+
"verification_prefix": "_workos-challenge",
26+
"created_at": datetime.datetime.now().isoformat(),
27+
"updated_at": datetime.datetime.now().isoformat(),
28+
}
29+
30+
@pytest.fixture
31+
def mock_organization_domain_verified(self):
32+
return {
33+
"object": "organization_domain",
34+
"id": "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8",
35+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
36+
"domain": "example.com",
37+
"state": "verified",
38+
"verification_strategy": "dns",
39+
"verification_token": "workos_example_verification_token_12345",
40+
"verification_prefix": "_workos-challenge",
41+
"created_at": datetime.datetime.now().isoformat(),
42+
"updated_at": datetime.datetime.now().isoformat(),
43+
}
44+
45+
def test_get_organization_domain(
46+
self, capture_and_mock_http_client_request, mock_organization_domain
47+
):
48+
request_kwargs = capture_and_mock_http_client_request(
49+
self.http_client,
50+
mock_organization_domain,
51+
200,
52+
)
53+
54+
organization_domain = syncify(
55+
self.organization_domains.get_organization_domain(
56+
organization_domain_id="org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
57+
)
58+
)
59+
60+
assert request_kwargs["url"].endswith(
61+
"/organization_domains/org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
62+
)
63+
assert request_kwargs["method"] == "get"
64+
assert organization_domain.id == "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
65+
assert organization_domain.domain == "example.com"
66+
assert organization_domain.state == "pending"
67+
assert organization_domain.verification_strategy == "dns"
68+
69+
def test_create_organization_domain(
70+
self, capture_and_mock_http_client_request, mock_organization_domain
71+
):
72+
request_kwargs = capture_and_mock_http_client_request(
73+
self.http_client,
74+
mock_organization_domain,
75+
201,
76+
)
77+
78+
organization_domain = syncify(
79+
self.organization_domains.create_organization_domain(
80+
organization_id="org_01EHT88Z8J8795GZNQ4ZP1J81T",
81+
domain="example.com",
82+
)
83+
)
84+
85+
assert request_kwargs["url"].endswith("/organization_domains")
86+
assert request_kwargs["method"] == "post"
87+
assert request_kwargs["json"] == {
88+
"organization_id": "org_01EHT88Z8J8795GZNQ4ZP1J81T",
89+
"domain": "example.com",
90+
}
91+
assert organization_domain.id == "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
92+
assert organization_domain.domain == "example.com"
93+
assert organization_domain.organization_id == "org_01EHT88Z8J8795GZNQ4ZP1J81T"
94+
95+
def test_verify_organization_domain(
96+
self, capture_and_mock_http_client_request, mock_organization_domain_verified
97+
):
98+
request_kwargs = capture_and_mock_http_client_request(
99+
self.http_client,
100+
mock_organization_domain_verified,
101+
200,
102+
)
103+
104+
organization_domain = syncify(
105+
self.organization_domains.verify_organization_domain(
106+
organization_domain_id="org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
107+
)
108+
)
109+
110+
assert request_kwargs["url"].endswith(
111+
"/organization_domains/org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8/verify"
112+
)
113+
assert request_kwargs["method"] == "post"
114+
assert organization_domain.id == "org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
115+
assert organization_domain.state == "verified"
116+
117+
def test_delete_organization_domain(self, capture_and_mock_http_client_request):
118+
request_kwargs = capture_and_mock_http_client_request(
119+
self.http_client,
120+
None,
121+
204,
122+
headers={"content-type": "text/plain; charset=utf-8"},
123+
)
124+
125+
response = syncify(
126+
self.organization_domains.delete_organization_domain(
127+
organization_domain_id="org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
128+
)
129+
)
130+
131+
assert request_kwargs["url"].endswith(
132+
"/organization_domains/org_domain_01EHT88Z8WZEFWYPM6EC9BX2R8"
133+
)
134+
assert request_kwargs["method"] == "delete"
135+
assert response is None

tests/utils/fixtures/mock_organization.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import datetime
22

33
from workos.types.organizations import Organization
4-
from workos.types.organizations.organization_domain import OrganizationDomain
4+
from workos.types.organization_domains import OrganizationDomain
55

66

77
class MockOrganization(Organization):

workos/_base_client.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from workos.events import EventsModule
1212
from workos.mfa import MFAModule
1313
from workos.organizations import OrganizationsModule
14+
from workos.organization_domains import OrganizationDomainsModule
1415
from workos.passwordless import PasswordlessModule
1516
from workos.portal import PortalModule
1617
from workos.sso import SSOModule
@@ -88,6 +89,10 @@ def mfa(self) -> MFAModule: ...
8889
@abstractmethod
8990
def organizations(self) -> OrganizationsModule: ...
9091

92+
@property
93+
@abstractmethod
94+
def organization_domains(self) -> OrganizationDomainsModule: ...
95+
9196
@property
9297
@abstractmethod
9398
def passwordless(self) -> PasswordlessModule: ...

workos/async_client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from workos.fga import FGAModule
88
from workos.mfa import MFAModule
99
from workos.organizations import AsyncOrganizations
10+
from workos.organization_domains import AsyncOrganizationDomains
1011
from workos.passwordless import PasswordlessModule
1112
from workos.portal import PortalModule
1213
from workos.sso import AsyncSSO
@@ -80,6 +81,14 @@ def organizations(self) -> AsyncOrganizations:
8081
self._organizations = AsyncOrganizations(self._http_client)
8182
return self._organizations
8283

84+
@property
85+
def organization_domains(self) -> AsyncOrganizationDomains:
86+
if not getattr(self, "_organization_domains", None):
87+
self._organization_domains = AsyncOrganizationDomains(
88+
http_client=self._http_client, client_configuration=self
89+
)
90+
return self._organization_domains
91+
8392
@property
8493
def passwordless(self) -> PasswordlessModule:
8594
raise NotImplementedError(

workos/client.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from workos.directory_sync import DirectorySync
66
from workos.fga import FGA
77
from workos.organizations import Organizations
8+
from workos.organization_domains import OrganizationDomains
89
from workos.passwordless import Passwordless
910
from workos.portal import Portal
1011
from workos.sso import SSO
@@ -80,6 +81,14 @@ def organizations(self) -> Organizations:
8081
self._organizations = Organizations(self._http_client)
8182
return self._organizations
8283

84+
@property
85+
def organization_domains(self) -> OrganizationDomains:
86+
if not getattr(self, "_organization_domains", None):
87+
self._organization_domains = OrganizationDomains(
88+
http_client=self._http_client, client_configuration=self
89+
)
90+
return self._organization_domains
91+
8392
@property
8493
def passwordless(self) -> Passwordless:
8594
if not getattr(self, "_passwordless", None):

workos/organization_domains.py

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
from typing import Optional, Protocol
2+
from workos._client_configuration import ClientConfiguration
3+
from workos.types.organization_domains import OrganizationDomain
4+
from workos.typing.sync_or_async import SyncOrAsync
5+
from workos.utils.http_client import AsyncHTTPClient, SyncHTTPClient
6+
from workos.utils.request_helper import (
7+
REQUEST_METHOD_DELETE,
8+
REQUEST_METHOD_GET,
9+
REQUEST_METHOD_POST,
10+
RequestHelper,
11+
)
12+
13+
14+
class OrganizationDomainsModule(Protocol):
15+
"""Offers methods for managing organization domains."""
16+
17+
_client_configuration: ClientConfiguration
18+
19+
def get_organization_domain(
20+
self, organization_domain_id: str
21+
) -> SyncOrAsync[OrganizationDomain]:
22+
"""Gets a single Organization Domain
23+
24+
Args:
25+
organization_domain_id (str): Organization Domain unique identifier
26+
27+
Returns:
28+
OrganizationDomain: Organization Domain response from WorkOS
29+
"""
30+
...
31+
32+
def create_organization_domain(
33+
self,
34+
organization_id: str,
35+
domain: str,
36+
) -> SyncOrAsync[OrganizationDomain]:
37+
"""Creates an Organization Domain
38+
39+
Args:
40+
organization_id (str): Organization unique identifier
41+
domain (str): Domain to be added to the organization
42+
43+
Returns:
44+
OrganizationDomain: Organization Domain response from WorkOS
45+
"""
46+
...
47+
48+
def verify_organization_domain(
49+
self, organization_domain_id: str
50+
) -> SyncOrAsync[OrganizationDomain]:
51+
"""Verifies an Organization Domain
52+
53+
Args:
54+
organization_domain_id (str): Organization Domain unique identifier
55+
56+
Returns:
57+
OrganizationDomain: Organization Domain response from WorkOS
58+
"""
59+
...
60+
61+
def delete_organization_domain(
62+
self, organization_domain_id: str
63+
) -> SyncOrAsync[None]:
64+
"""Deletes a single Organization Domain
65+
66+
Args:
67+
organization_domain_id (str): Organization Domain unique identifier
68+
69+
Returns:
70+
None
71+
"""
72+
...
73+
74+
75+
class OrganizationDomains:
76+
"""Offers methods for managing organization domains."""
77+
78+
_http_client: SyncHTTPClient
79+
_client_configuration: ClientConfiguration
80+
81+
def __init__(
82+
self,
83+
http_client: SyncHTTPClient,
84+
client_configuration: ClientConfiguration,
85+
):
86+
self._http_client = http_client
87+
self._client_configuration = client_configuration
88+
89+
def get_organization_domain(
90+
self, organization_domain_id: str
91+
) -> OrganizationDomain:
92+
response = self._http_client.request(
93+
f"organization_domains/{organization_domain_id}",
94+
method=REQUEST_METHOD_GET,
95+
)
96+
97+
return OrganizationDomain.model_validate(response)
98+
99+
def create_organization_domain(
100+
self,
101+
organization_id: str,
102+
domain: str,
103+
) -> OrganizationDomain:
104+
response = self._http_client.request(
105+
"organization_domains",
106+
method=REQUEST_METHOD_POST,
107+
json={"organization_id": organization_id, "domain": domain},
108+
)
109+
110+
return OrganizationDomain.model_validate(response)
111+
112+
def verify_organization_domain(
113+
self, organization_domain_id: str
114+
) -> OrganizationDomain:
115+
response = self._http_client.request(
116+
f"organization_domains/{organization_domain_id}/verify",
117+
method=REQUEST_METHOD_POST,
118+
)
119+
120+
return OrganizationDomain.model_validate(response)
121+
122+
def delete_organization_domain(self, organization_domain_id: str) -> None:
123+
self._http_client.request(
124+
f"organization_domains/{organization_domain_id}",
125+
method=REQUEST_METHOD_DELETE,
126+
)
127+
128+
129+
class AsyncOrganizationDomains:
130+
"""Offers async methods for managing organization domains."""
131+
132+
_http_client: AsyncHTTPClient
133+
_client_configuration: ClientConfiguration
134+
135+
def __init__(
136+
self,
137+
http_client: AsyncHTTPClient,
138+
client_configuration: ClientConfiguration,
139+
):
140+
self._http_client = http_client
141+
self._client_configuration = client_configuration
142+
143+
async def get_organization_domain(
144+
self, organization_domain_id: str
145+
) -> OrganizationDomain:
146+
response = await self._http_client.request(
147+
f"organization_domains/{organization_domain_id}",
148+
method=REQUEST_METHOD_GET,
149+
)
150+
151+
return OrganizationDomain.model_validate(response)
152+
153+
async def create_organization_domain(
154+
self,
155+
organization_id: str,
156+
domain: str,
157+
) -> OrganizationDomain:
158+
response = await self._http_client.request(
159+
"organization_domains",
160+
method=REQUEST_METHOD_POST,
161+
json={"organization_id": organization_id, "domain": domain},
162+
)
163+
164+
return OrganizationDomain.model_validate(response)
165+
166+
async def verify_organization_domain(
167+
self, organization_domain_id: str
168+
) -> OrganizationDomain:
169+
response = await self._http_client.request(
170+
f"organization_domains/{organization_domain_id}/verify",
171+
method=REQUEST_METHOD_POST,
172+
)
173+
174+
return OrganizationDomain.model_validate(response)
175+
176+
async def delete_organization_domain(self, organization_domain_id: str) -> None:
177+
await self._http_client.request(
178+
f"organization_domains/{organization_domain_id}",
179+
method=REQUEST_METHOD_DELETE,
180+
)

0 commit comments

Comments
 (0)