Skip to content

Commit dff6c6c

Browse files
committed
fix: correctly handle value generation in sub-attributes
1 parent c84c68d commit dff6c6c

File tree

4 files changed

+54
-10
lines changed

4 files changed

+54
-10
lines changed

doc/changelog.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Fixed
99
- Error and critical check results directly raise an exception when ``raise_exceptions=True``.
1010
- Use ``ComplexAttribute`` instead of ``MultiValuedComplexAttribute`` to generate data.
1111
- Respect the ``primary`` attribute unique truthy value when generating random values.
12+
- Correctly handle value generation in sub-attributes. :issue:`41`
1213

1314
[0.2.2] - 2025-08-13
1415
--------------------

scim2_tester/filling.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,21 @@
2525
from scim2_tester.urns import iter_all_urns
2626
from scim2_tester.urns import set_value_by_urn
2727

28+
29+
def filter_sub_urns(parent_urn: str, allowed_urns: list[str]) -> list[str]:
30+
"""Extract and normalize sub-URNs for a parent complex attribute.
31+
32+
Converts "parent.child" URNs to "child" URNs for use in the complex attribute context.
33+
"""
34+
prefix = f"{parent_urn}."
35+
sub_urns = []
36+
for urn in allowed_urns:
37+
if urn.startswith(prefix):
38+
sub_urn = urn.removeprefix(prefix)
39+
sub_urns.append(sub_urn)
40+
return sub_urns
41+
42+
2843
if TYPE_CHECKING:
2944
from scim2_tester.utils import CheckContext
3045

@@ -69,6 +84,7 @@ def generate_random_value(
6984
context: "CheckContext",
7085
urn: str,
7186
model: type[Resource],
87+
allowed_urns: list[str] | None = None,
7288
) -> Any:
7389
field_name = _find_field_name(model, urn)
7490
field_type = get_attribute_type_by_urn(model, urn)
@@ -124,10 +140,14 @@ def generate_random_value(
124140
value = random.choice(list(field_type))
125141

126142
elif isclass(field_type) and issubclass(field_type, ComplexAttribute):
127-
value = fill_complex_attribute_with_random_values(context, field_type()) # type: ignore[arg-type]
143+
sub_urns = filter_sub_urns(urn, allowed_urns) if allowed_urns else None
144+
value = fill_complex_attribute_with_random_values(
145+
context, field_type(), sub_urns
146+
) # type: ignore[arg-type]
128147

129148
elif isclass(field_type) and issubclass(field_type, Extension):
130-
value = fill_with_random_values(context, field_type()) # type: ignore[arg-type]
149+
sub_urns = filter_sub_urns(urn, allowed_urns) if allowed_urns else None
150+
value = fill_with_random_values(context, field_type(), sub_urns) # type: ignore[arg-type]
131151

132152
elif field_type is Base64Bytes:
133153
value = base64.b64encode(uuid.uuid4().bytes).decode("ascii")
@@ -168,7 +188,9 @@ def fill_with_random_values(
168188
]
169189

170190
for urn in urns:
171-
value = generate_random_value(context, urn=urn, model=type(obj))
191+
value = generate_random_value(
192+
context, urn=urn, model=type(obj), allowed_urns=urns
193+
)
172194
set_value_by_urn(obj, urn, value)
173195

174196
fix_primary_attributes(obj)
@@ -179,13 +201,14 @@ def fill_with_random_values(
179201
def fill_complex_attribute_with_random_values(
180202
context: "CheckContext",
181203
obj: ComplexAttribute,
204+
urns: list[str] | None = None,
182205
) -> Resource[Any] | None:
183206
"""Fill a ComplexAttribute with random values.
184207
185208
For SCIM reference fields, correctly sets the value field to match
186209
the ID extracted from the reference URL.
187210
"""
188-
fill_with_random_values(context, obj)
211+
fill_with_random_values(context, obj, urns)
189212
if "ref" in type(obj).model_fields and "value" in type(obj).model_fields:
190213
ref_type = type(obj).get_field_root_type("ref")
191214
if (

tests/test_filling.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
"""Test automatic field filling functionality."""
22

3+
from typing import Annotated
34
from typing import Literal
45
from unittest.mock import patch
56

67
from scim2_models import Email
78
from scim2_models import Group
9+
from scim2_models import Mutability
810
from scim2_models import PhoneNumber
911
from scim2_models import User
12+
from scim2_models.attributes import ComplexAttribute
13+
from scim2_models.resources.resource import Resource
1014
from scim2_models.resources.user import X509Certificate
1115

1216
from scim2_tester.filling import fill_with_random_values
@@ -240,3 +244,24 @@ def test_fill_with_random_values_phone_numbers_primary_constraint(testing_contex
240244

241245
primary_count = sum(1 for phone in filled_user.phone_numbers if phone.primary)
242246
assert primary_count == 1
247+
248+
249+
def test_fill_with_random_values_ignores_mutability_filter(testing_context):
250+
"""Demonstrates that complex attribute generation fills read-only sub-attributes incorrectly."""
251+
252+
class TestComplexAttr(ComplexAttribute):
253+
writable_field: str | None = None
254+
readonly_field: Annotated[str | None, Mutability.read_only] = None
255+
256+
class TestResource(Resource):
257+
test_attr: TestComplexAttr | None = None
258+
259+
resource = TestResource(schemas=["urn:test:schema"])
260+
261+
filled_resource = fill_with_random_values(
262+
testing_context, resource, ["test_attr.writable_field"]
263+
)
264+
265+
assert filled_resource.test_attr.readonly_field is None, (
266+
f"readonly_field should be None but got {filled_resource.test_attr.readonly_field}"
267+
)

tests/test_patch_remove.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -239,12 +239,7 @@ def create_handler(request):
239239
"userType",
240240
"preferredLanguage",
241241
]:
242-
if key in request_data:
243-
response_data[key] = request_data[key]
244-
245-
extension_key = "urn:ietf:params:scim:schemas:extension:enterprise:2.0:User"
246-
if extension_key in request_data:
247-
response_data[extension_key] = request_data[extension_key]
242+
response_data[key] = request_data[key]
248243

249244
return Response(
250245
json.dumps(response_data),

0 commit comments

Comments
 (0)