Skip to content

Commit 91b123b

Browse files
azmeukJ-Bu
andauthored
Allow testing resources with multiple extensions (#32)
* Use Union instead of tuple for creating a Resource with multiple extension Using a tuple results in a pydantic error: File "/usr/local/lib/python3.11/site-packages/pydantic/_internal/_generics.py", line 373, in map_generic_model_arguments raise TypeError(f'Too many arguments for {cls}; actual {len(args)}, expected {expected_len}') TypeError: Too many arguments for <class 'scim2_models.rfc7643.schema.User'>; actual 3, expected 1 * Add unit test for discovering resource types with multiple extensions * Fix check_resource_model if resource_models was created from schemas * fix: pre-commit * fix: unit tests --------- Co-authored-by: Jan Burgmeier <[email protected]>
1 parent ff5e6b7 commit 91b123b

File tree

3 files changed

+112
-9
lines changed

3 files changed

+112
-9
lines changed

scim2_client/client.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,13 @@ def get_resource_model(self, name: str) -> Optional[type[Resource]]:
205205
def _check_resource_model(
206206
self, resource_model: type[Resource], payload=None
207207
) -> None:
208-
if (
209-
resource_model not in self.resource_models
210-
and resource_model not in CONFIG_RESOURCES
211-
):
208+
schema_to_check = resource_model.model_fields["schemas"].default[0]
209+
for element in self.resource_models:
210+
schema = element.model_fields["schemas"].default[0]
211+
if schema_to_check == schema:
212+
return
213+
214+
if resource_model not in CONFIG_RESOURCES:
212215
raise SCIMRequestError(
213216
f"Unknown resource type: '{resource_model}'", source=payload
214217
)
@@ -640,13 +643,13 @@ def build_resource_models(
640643
for schema, resource_type in resource_types_by_schema.items():
641644
schema_obj = schema_objs_by_schema[schema]
642645
model = Resource.from_schema(schema_obj)
643-
extensions = []
646+
extensions: tuple[type[Extension], ...] = ()
644647
for ext_schema in resource_type.schema_extensions or []:
645648
schema_obj = schema_objs_by_schema[ext_schema.schema_]
646649
extension = Extension.from_schema(schema_obj)
647-
extensions.append(extension)
650+
extensions = extensions + (extension,)
648651
if extensions:
649-
model = model[Union[tuple(extensions)]]
652+
model = model[Union[extensions]]
650653
resource_models.append(model)
651654

652655
return tuple(resource_models)

tests/test_delete.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import pytest
22
from scim2_models import Error
3-
from scim2_models import Group
3+
from scim2_models import Resource
44
from scim2_models import User
55

66
from scim2_client import RequestNetworkError
77
from scim2_client import SCIMRequestError
88

99

10+
class UnregisteredResource(Resource):
11+
schemas: list[str] = ["urn:test:schemas:UnregisteredResource"]
12+
13+
1014
def test_delete_user(httpserver, sync_client):
1115
"""Nominal case for a User deletion."""
1216
httpserver.expect_request(
@@ -45,7 +49,7 @@ def test_errors(httpserver, code, sync_client):
4549
def test_invalid_resource_model(httpserver, sync_client):
4650
"""Test that resource_models passed to the method must be part of SCIMClient.resource_models."""
4751
with pytest.raises(SCIMRequestError, match=r"Unknown resource type"):
48-
sync_client.delete(Group(display_name="foobar"), id="foobar")
52+
sync_client.delete(UnregisteredResource, id="foobar")
4953

5054

5155
def test_dont_check_response_payload(httpserver, sync_client):

tests/test_discovery.py

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import threading
2+
import wsgiref.simple_server
3+
from typing import Annotated
4+
from typing import Union
5+
6+
import portpicker
7+
import pytest
8+
from httpx import Client
9+
from scim2_models import EnterpriseUser
10+
from scim2_models import Extension
11+
from scim2_models import Group
12+
from scim2_models import Meta
13+
from scim2_models import Required
14+
from scim2_models import ResourceType
15+
from scim2_models import User
16+
17+
from scim2_client.engines.httpx import SyncSCIMClient
18+
19+
scim2_server = pytest.importorskip("scim2_server")
20+
from scim2_server.backend import InMemoryBackend # noqa: E402
21+
from scim2_server.provider import SCIMProvider # noqa: E402
22+
23+
24+
class OtherExtension(Extension):
25+
schemas: Annotated[list[str], Required.true] = [
26+
"urn:ietf:params:scim:schemas:extension:Other:1.0:User"
27+
]
28+
29+
test: str | None = None
30+
test2: list[str] | None = None
31+
32+
33+
def get_schemas():
34+
schemas = [
35+
User.to_schema(),
36+
Group.to_schema(),
37+
OtherExtension.to_schema(),
38+
EnterpriseUser.to_schema(),
39+
]
40+
41+
# SCIMProvider register_schema requires meta object to be set
42+
for schema in schemas:
43+
schema.meta = Meta(resource_type="Schema")
44+
45+
return schemas
46+
47+
48+
def get_resource_types():
49+
resource_types = [
50+
ResourceType.from_resource(User[Union[EnterpriseUser, OtherExtension]]),
51+
ResourceType.from_resource(Group),
52+
]
53+
54+
# SCIMProvider register_resource_type requires meta object to be set
55+
for resource_type in resource_types:
56+
resource_type.meta = Meta(resource_type="ResourceType")
57+
58+
return resource_types
59+
60+
61+
@pytest.fixture(scope="session")
62+
def server():
63+
backend = InMemoryBackend()
64+
provider = SCIMProvider(backend)
65+
for schema in get_schemas():
66+
provider.register_schema(schema)
67+
for resource_type in get_resource_types():
68+
provider.register_resource_type(resource_type)
69+
70+
host = "localhost"
71+
port = portpicker.pick_unused_port()
72+
httpd = wsgiref.simple_server.make_server(host, port, provider)
73+
74+
server_thread = threading.Thread(target=httpd.serve_forever)
75+
server_thread.start()
76+
try:
77+
yield host, port
78+
finally:
79+
httpd.shutdown()
80+
server_thread.join()
81+
82+
83+
def test_discovery_resource_types_multiple_extensions(server):
84+
host, port = server
85+
client = Client(base_url=f"http://{host}:{port}")
86+
scim_client = SyncSCIMClient(client)
87+
88+
scim_client.discover()
89+
assert scim_client.get_resource_model("User")
90+
assert scim_client.get_resource_model("Group")
91+
92+
# Try to create a user to see if discover filled everything correctly
93+
user_request = User[Union[EnterpriseUser, OtherExtension]](
94+
user_name="[email protected]"
95+
)
96+
scim_client.create(user_request)

0 commit comments

Comments
 (0)