Skip to content

Commit 360055d

Browse files
committed
feat: implement patch nominal case checkers
1 parent 640e859 commit 360055d

File tree

13 files changed

+1516
-63
lines changed

13 files changed

+1516
-63
lines changed

scim2_tester/checkers/patch_add.py

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
"""PATCH add operation checkers for SCIM compliance testing."""
2+
3+
from typing import Any
4+
5+
from scim2_client import SCIMClientError
6+
from scim2_models import Mutability
7+
from scim2_models import PatchOp
8+
from scim2_models import PatchOperation
9+
from scim2_models import Required
10+
from scim2_models import Resource
11+
12+
from ..filling import generate_random_value
13+
from ..utils import CheckContext
14+
from ..utils import CheckResult
15+
from ..utils import Status
16+
from ..utils import checker
17+
from .utils import compare_field
18+
from .utils import get_attribute_type_by_urn
19+
from .utils import get_value_by_urn
20+
from .utils import iter_all_urns
21+
22+
23+
@checker("patch:add")
24+
def check_add_attribute(
25+
context: CheckContext, model: type[Resource[Any]]
26+
) -> list[CheckResult]:
27+
"""Test PATCH add operation on all attributes (simple, complex, and extensions).
28+
29+
Creates a minimal resource, then iterates over ALL possible URNs (base model,
30+
extensions, and sub-attributes) to test PATCH add operations systematically.
31+
Uses a unified approach that handles all attribute types consistently.
32+
33+
**Tested Behavior:**
34+
- Adding new attribute values (simple, complex, and extension attributes)
35+
- Server accepts the PATCH request with correct URN paths for extensions
36+
- Response contains the added attribute with correct values
37+
38+
**Status:**
39+
- :attr:`~scim2_tester.Status.SUCCESS`: Attribute successfully added
40+
- :attr:`~scim2_tester.Status.ERROR`: Failed to add attribute
41+
- :attr:`~scim2_tester.Status.SKIPPED`: No addable attributes found
42+
43+
.. pull-quote:: :rfc:`RFC 7644 Section 3.5.2.1 - Add Operation <7644#section-3.5.2.1>`
44+
45+
"The 'add' operation is used to add a new attribute and/or values to
46+
an existing resource."
47+
"""
48+
results = []
49+
all_urns = list(
50+
iter_all_urns(
51+
context,
52+
model,
53+
context.resource_manager,
54+
required=[Required.false],
55+
mutability=[Mutability.read_write, Mutability.write_only],
56+
)
57+
)
58+
59+
if not all_urns:
60+
return [
61+
CheckResult(
62+
status=Status.SKIPPED,
63+
reason=f"No addable attributes found for {model.__name__}",
64+
resource_type=model.__name__,
65+
)
66+
]
67+
68+
base_resource = context.resource_manager.create_and_register(model)
69+
70+
for urn, source_model in all_urns:
71+
attr_type = get_attribute_type_by_urn(source_model, urn)
72+
if attr_type is None:
73+
continue
74+
75+
patch_value = generate_random_value(context, urn=urn, field_type=attr_type)
76+
77+
patch_op = PatchOp[type(base_resource)](
78+
operations=[
79+
PatchOperation(
80+
op=PatchOperation.Op.add,
81+
path=urn,
82+
value=patch_value,
83+
)
84+
]
85+
)
86+
87+
try:
88+
updated_resource = context.client.modify(
89+
resource_model=type(base_resource),
90+
id=base_resource.id,
91+
patch_op=patch_op,
92+
)
93+
except SCIMClientError as exc:
94+
results.append(
95+
CheckResult(
96+
status=Status.ERROR,
97+
reason=f"Failed to add attribute '{urn}': {exc}",
98+
resource_type=model.__name__,
99+
data={
100+
"urn": urn,
101+
"error": exc,
102+
"patch_value": patch_value,
103+
},
104+
)
105+
)
106+
continue
107+
108+
actual_value = get_value_by_urn(updated_resource, urn)
109+
success = compare_field(patch_value, actual_value)
110+
111+
if success:
112+
results.append(
113+
CheckResult(
114+
status=Status.SUCCESS,
115+
reason=f"Successfully added attribute '{urn}'",
116+
resource_type=model.__name__,
117+
data={
118+
"urn": urn,
119+
"value": patch_value,
120+
},
121+
)
122+
)
123+
else:
124+
results.append(
125+
CheckResult(
126+
status=Status.ERROR,
127+
reason=f"Attribute '{urn}' was not added or has incorrect value",
128+
resource_type=model.__name__,
129+
data={
130+
"urn": urn,
131+
"expected": patch_value,
132+
"actual": actual_value,
133+
},
134+
)
135+
)
136+
137+
return results
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
"""PATCH remove operation checkers for SCIM compliance testing."""
2+
3+
from typing import Any
4+
5+
from scim2_client import SCIMClientError
6+
from scim2_models import Mutability
7+
from scim2_models import PatchOp
8+
from scim2_models import PatchOperation
9+
from scim2_models import Required
10+
from scim2_models import Resource
11+
12+
from ..utils import CheckContext
13+
from ..utils import CheckResult
14+
from ..utils import Status
15+
from ..utils import checker
16+
from .utils import get_value_by_urn
17+
from .utils import iter_all_urns
18+
19+
20+
@checker("patch:remove")
21+
def check_remove_attribute(
22+
context: CheckContext, model: type[Resource[Any]]
23+
) -> list[CheckResult]:
24+
"""Test PATCH remove operation on all attributes (simple, complex, and extensions).
25+
26+
Creates a resource with initial values, then iterates over ALL possible URNs
27+
(base model, extensions, and sub-attributes) to test PATCH remove operations
28+
systematically. Uses a unified approach that handles all attribute types consistently.
29+
30+
**Tested Behavior:**
31+
- Removing attribute values (simple, complex, and extension attributes)
32+
- Server accepts the PATCH request with correct URN paths for extensions
33+
- Response contains the resource with removed attributes (null/missing)
34+
35+
**Status:**
36+
- :attr:`~scim2_tester.Status.SUCCESS`: Attribute successfully removed
37+
- :attr:`~scim2_tester.Status.ERROR`: Failed to remove attribute or attribute still exists
38+
- :attr:`~scim2_tester.Status.SKIPPED`: No removable attributes found
39+
40+
.. pull-quote:: :rfc:`RFC 7644 Section 3.5.2.2 - Remove Operation <7644#section-3.5.2.2>`
41+
42+
"The 'remove' operation removes the value at the target location specified
43+
by the required attribute 'path'. The operation performs the following
44+
functions, depending on the target location specified by 'path'."
45+
"""
46+
results = []
47+
all_urns = list(
48+
iter_all_urns(
49+
context,
50+
model,
51+
context.resource_manager,
52+
required=[Required.false],
53+
mutability=[Mutability.read_write, Mutability.write_only],
54+
)
55+
)
56+
57+
if not all_urns:
58+
return [
59+
CheckResult(
60+
status=Status.SKIPPED,
61+
reason=f"No removable attributes found for {model.__name__}",
62+
resource_type=model.__name__,
63+
)
64+
]
65+
66+
full_resource = context.resource_manager.create_and_register(model, fill_all=True)
67+
68+
for urn, _source_model in all_urns:
69+
initial_value = get_value_by_urn(full_resource, urn)
70+
if initial_value is None:
71+
continue
72+
73+
remove_op = PatchOp[type(full_resource)](
74+
operations=[
75+
PatchOperation(
76+
op=PatchOperation.Op.remove,
77+
path=urn,
78+
)
79+
]
80+
)
81+
82+
try:
83+
updated_resource = context.client.modify(
84+
resource_model=type(full_resource),
85+
id=full_resource.id,
86+
patch_op=remove_op,
87+
)
88+
except SCIMClientError as exc:
89+
results.append(
90+
CheckResult(
91+
status=Status.ERROR,
92+
reason=f"Failed to remove attribute '{urn}': {exc}",
93+
resource_type=model.__name__,
94+
data={
95+
"urn": urn,
96+
"error": exc,
97+
"initial_value": initial_value,
98+
},
99+
)
100+
)
101+
continue
102+
103+
actual_value = get_value_by_urn(updated_resource, urn)
104+
105+
if actual_value is None:
106+
results.append(
107+
CheckResult(
108+
status=Status.SUCCESS,
109+
reason=f"Successfully removed attribute '{urn}'",
110+
resource_type=model.__name__,
111+
data={
112+
"urn": urn,
113+
"initial_value": initial_value,
114+
},
115+
)
116+
)
117+
else:
118+
results.append(
119+
CheckResult(
120+
status=Status.ERROR,
121+
reason=f"Attribute '{urn}' was not removed",
122+
resource_type=model.__name__,
123+
data={
124+
"urn": urn,
125+
"initial_value": initial_value,
126+
"actual_value": actual_value,
127+
},
128+
)
129+
)
130+
131+
return results

0 commit comments

Comments
 (0)