Skip to content

Commit 568eb1d

Browse files
authored
Merge pull request #35 from python-scim/additional-checks
Implement additional tests on the core resources
2 parents 83e1476 + 29f4bed commit 568eb1d

14 files changed

Lines changed: 342 additions & 96 deletions

scim2_tester/checker.py

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@
44

55
from scim2_tester.checkers import random_url
66
from scim2_tester.checkers import resource_type_tests
7-
from scim2_tester.checkers import resource_types_endpoint
8-
from scim2_tester.checkers import schemas_endpoint
97
from scim2_tester.checkers import service_provider_config_endpoint
8+
from scim2_tester.checkers.resource_types import _resource_types_endpoint
9+
from scim2_tester.checkers.schemas import _schemas_endpoint
1010
from scim2_tester.utils import CheckConfig
1111
from scim2_tester.utils import CheckContext
1212
from scim2_tester.utils import CheckResult
@@ -74,25 +74,22 @@ def check_server(
7474
context = CheckContext(client, conf)
7575
results = []
7676

77-
# Get the initial basic objects
7877
result_spc = service_provider_config_endpoint(context)
7978
results.append(result_spc)
8079
if result_spc.status != Status.SKIPPED and not client.service_provider_config:
8180
client.service_provider_config = result_spc.data
8281

83-
results_resource_types = resource_types_endpoint(context)
82+
results_resource_types = _resource_types_endpoint(context)
8483
results.extend(results_resource_types)
8584
if not client.resource_types:
86-
# Find first non-skipped result with data
8785
for rt_result in results_resource_types:
8886
if rt_result.status != Status.SKIPPED and rt_result.data:
8987
client.resource_types = rt_result.data
9088
break
9189

92-
results_schemas = schemas_endpoint(context)
90+
results_schemas = _schemas_endpoint(context)
9391
results.extend(results_schemas)
9492
if not client.resource_models:
95-
# Find first non-skipped result with data
9693
for schema_result in results_schemas:
9794
if schema_result.status != Status.SKIPPED and schema_result.data:
9895
client.resource_models = client.build_resource_models(
@@ -107,18 +104,14 @@ def check_server(
107104
):
108105
return results
109106

110-
# Miscelleaneous checks
111107
result_random = random_url(context)
112108
results.append(result_random)
113109

114-
# Resource checks
115110
for resource_type in client.resource_types or []:
116-
# Filter by resource type if specified
117111
if conf.resource_types and resource_type.name not in conf.resource_types:
118112
continue
119113

120114
resource_results = resource_type_tests(context, resource_type)
121-
# Add resource type to each result for better tracking
122115
for result in resource_results:
123116
result.resource_type = resource_type.name
124117
results.extend(resource_results)

scim2_tester/checkers/__init__.py

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,31 +20,34 @@
2020
from .resource_types import access_invalid_resource_type
2121
from .resource_types import query_all_resource_types
2222
from .resource_types import query_resource_type_by_id
23-
from .resource_types import resource_types_endpoint
23+
from .resource_types import resource_types_endpoint_methods
24+
from .resource_types import resource_types_schema_validation
2425
from .schemas import access_invalid_schema
26+
from .schemas import access_schema_by_id
27+
from .schemas import core_schemas_validation
2528
from .schemas import query_all_schemas
26-
from .schemas import query_schema_by_id
27-
from .schemas import schemas_endpoint
29+
from .schemas import schemas_endpoint_methods
2830
from .service_provider_config import service_provider_config_endpoint
31+
from .service_provider_config import service_provider_config_endpoint_methods
2932

3033
__all__ = [
31-
# Discovery checkers
3234
"service_provider_config_endpoint",
33-
"resource_types_endpoint",
35+
"service_provider_config_endpoint_methods",
36+
"resource_types_endpoint_methods",
37+
"resource_types_schema_validation",
3438
"query_all_resource_types",
3539
"query_resource_type_by_id",
3640
"access_invalid_resource_type",
37-
"schemas_endpoint",
41+
"schemas_endpoint_methods",
3842
"query_all_schemas",
39-
"query_schema_by_id",
43+
"access_schema_by_id",
4044
"access_invalid_schema",
41-
# CRUD checkers
45+
"core_schemas_validation",
4246
"object_creation",
4347
"object_query",
4448
"object_query_without_id",
4549
"object_replacement",
4650
"object_deletion",
4751
"resource_type_tests",
48-
# Miscellaneous checkers
4952
"random_url",
5053
]
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
"""Utility functions for discovery endpoint testing."""
2+
3+
from scim2_client import SCIMClientError
4+
5+
from ..utils import CheckContext
6+
from ..utils import CheckResult
7+
from ..utils import Status
8+
9+
10+
def _test_discovery_endpoint_methods(
11+
context: CheckContext, endpoint: str
12+
) -> list[CheckResult]:
13+
"""Test that unsupported HTTP methods return 405 Method Not Allowed.
14+
15+
Tests that POST, PUT, PATCH, and DELETE methods on the specified discovery
16+
endpoint correctly return HTTP 405 Method Not Allowed status, as only GET is supported.
17+
18+
**Status:**
19+
20+
- :attr:`~scim2_tester.Status.SUCCESS`: All unsupported methods return 405 status
21+
- :attr:`~scim2_tester.Status.ERROR`: One or more methods return unexpected status
22+
23+
.. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>`
24+
25+
Discovery endpoints only support GET method. Other HTTP methods
26+
should return appropriate error responses.
27+
"""
28+
results = []
29+
methods = ["POST", "PUT", "PATCH", "DELETE"]
30+
31+
for method in methods:
32+
try:
33+
response = context.client.client.request(
34+
method=method,
35+
url=endpoint,
36+
)
37+
if response.status_code == 405:
38+
results.append(
39+
CheckResult(
40+
status=Status.SUCCESS,
41+
reason=f"{method} {endpoint} correctly returned 405 Method Not Allowed",
42+
data=response,
43+
)
44+
)
45+
else:
46+
results.append(
47+
CheckResult(
48+
status=Status.ERROR,
49+
reason=f"{method} {endpoint} returned {response.status_code} instead of 405",
50+
data=response,
51+
)
52+
)
53+
except SCIMClientError as e:
54+
results.append(
55+
CheckResult(
56+
status=Status.ERROR,
57+
reason=f"{method} {endpoint} failed: {str(e)}",
58+
)
59+
)
60+
61+
return results

scim2_tester/checkers/resource.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,6 @@ def resource_type_tests(
4343

4444
results = []
4545

46-
# Each test is now completely independent and handles its own cleanup
47-
# These functions have @checker decorators so we call them with client, conf
48-
# The decorator will create a context and call the function appropriately
49-
# For now, call them directly - may need adjustment based on actual function signatures
5046
results.append(object_creation(context, model))
5147
results.append(object_query(context, model))
5248
results.append(object_query_without_id(context, model))

scim2_tester/checkers/resource_delete.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ def object_deletion(context: CheckContext, model: type[Resource[Any]]) -> CheckR
3030
"""
3131
test_obj = context.resource_manager.create_and_register(model)
3232

33-
# Remove from resource manager since we're testing deletion explicitly
3433
if test_obj in context.resource_manager.resources:
3534
context.resource_manager.resources.remove(test_obj)
3635

@@ -48,7 +47,6 @@ def object_deletion(context: CheckContext, model: type[Resource[Any]]) -> CheckR
4847
reason=f"{model.__name__} object with id {test_obj.id} still exists after deletion",
4948
)
5049
except Exception:
51-
# Expected - object should not exist after deletion
5250
pass
5351

5452
return CheckResult(

scim2_tester/checkers/resource_types.py

Lines changed: 81 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import uuid
22

3+
from scim2_client import SCIMClientError
34
from scim2_models import Error
45
from scim2_models import ResourceType
6+
from scim2_models import Schema
57

68
from ..utils import CheckContext
79
from ..utils import CheckResult
810
from ..utils import Status
911
from ..utils import checker
12+
from ._discovery_utils import _test_discovery_endpoint_methods
1013

1114

12-
def resource_types_endpoint(context: CheckContext) -> list[CheckResult]:
15+
def _resource_types_endpoint(context: CheckContext) -> list[CheckResult]:
1316
"""Orchestrate validation of the ResourceTypes discovery endpoint.
1417
1518
Runs comprehensive tests on the ``/ResourceTypes`` endpoint including listing
@@ -28,12 +31,6 @@ def resource_types_endpoint(context: CheckContext) -> list[CheckResult]:
2831
2932
"Service providers MUST provide this endpoint."
3033
31-
.. todo::
32-
33-
- Check POST/PUT/PATCH/DELETE on the endpoint
34-
- Check that query parameters are ignored
35-
- Check that a 403 response is returned if a filter is passed
36-
- Check that the `schema` attribute exists and is available.
3734
"""
3835
resource_types_result = query_all_resource_types(context)
3936
results = [resource_types_result]
@@ -42,11 +39,88 @@ def resource_types_endpoint(context: CheckContext) -> list[CheckResult]:
4239
for resource_type in resource_types_result.data:
4340
results.append(query_resource_type_by_id(context, resource_type))
4441

42+
results.extend(resource_types_schema_validation(context))
43+
4544
results.append(access_invalid_resource_type(context))
4645

4746
return results
4847

4948

49+
@checker("discovery", "resource-types")
50+
def resource_types_endpoint_methods(
51+
context: CheckContext,
52+
) -> list[CheckResult]:
53+
"""Validate that unsupported HTTP methods return 405 Method Not Allowed.
54+
55+
Tests that POST, PUT, PATCH, and DELETE methods on the ``/ResourceTypes``
56+
endpoint correctly return HTTP 405 Method Not Allowed status, as only GET is supported.
57+
58+
**Status:**
59+
60+
- :attr:`~scim2_tester.Status.SUCCESS`: All unsupported methods return 405 status
61+
- :attr:`~scim2_tester.Status.ERROR`: One or more methods return unexpected status
62+
63+
.. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>`
64+
65+
"An HTTP GET to this endpoint is used to discover the types of resources
66+
available on a SCIM service provider."
67+
68+
Only GET method is specified, other methods should return appropriate errors.
69+
"""
70+
return _test_discovery_endpoint_methods(context, "/ResourceTypes")
71+
72+
73+
@checker("discovery", "resource-types")
74+
def resource_types_schema_validation(
75+
context: CheckContext,
76+
) -> list[CheckResult]:
77+
"""Validate that ResourceType schemas exist and are accessible.
78+
79+
Tests that all :class:`~scim2_models.ResourceType` objects returned by the
80+
``/ResourceTypes`` endpoint reference valid schemas that can be retrieved
81+
from the ``/Schemas`` endpoint.
82+
83+
**Status:**
84+
85+
- :attr:`~scim2_tester.Status.SUCCESS`: All ResourceType schemas are accessible
86+
- :attr:`~scim2_tester.Status.ERROR`: One or more ResourceType schemas are missing or inaccessible
87+
88+
.. pull-quote:: :rfc:`RFC 7644 Section 4 - Discovery <7644#section-4>`
89+
90+
"Each resource type defines the endpoint, the core schema URI that defines
91+
the resource, and any supported schema extensions."
92+
"""
93+
response = context.client.query(
94+
ResourceType, expected_status_codes=context.conf.expected_status_codes or [200]
95+
)
96+
97+
results = []
98+
for resource_type in response.resources:
99+
schema_id = resource_type.schema_
100+
try:
101+
schema_response = context.client.query(
102+
Schema,
103+
schema_id,
104+
expected_status_codes=context.conf.expected_status_codes or [200],
105+
)
106+
results.append(
107+
CheckResult(
108+
status=Status.SUCCESS,
109+
reason=f"ResourceType '{resource_type.name}' schema '{schema_id}' is accessible",
110+
data=schema_response,
111+
)
112+
)
113+
except SCIMClientError as e:
114+
results.append(
115+
CheckResult(
116+
status=Status.ERROR,
117+
reason=f"ResourceType '{resource_type.name}' schema '{schema_id}' is not accessible: {str(e)}",
118+
)
119+
)
120+
121+
return results
122+
123+
50124
@checker("discovery", "resource-types")
51125
def query_all_resource_types(context: CheckContext) -> CheckResult:
52126
"""Validate retrieval of all available resource types.

0 commit comments

Comments
 (0)