Skip to content

Commit 1d07b1a

Browse files
committed
refactor: use explicit filters instead of allowed_urns in filling
methods
1 parent e2c88b5 commit 1d07b1a

File tree

5 files changed

+104
-139
lines changed

5 files changed

+104
-139
lines changed

scim2_tester/checkers/resource_put.py

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -35,17 +35,9 @@ def object_replacement(
3535
"""
3636
test_obj = context.resource_manager.create_and_register(model)
3737

38-
mutable_urns = []
39-
for field_name in model.model_fields.keys():
40-
if field_name in ("meta", "id", "schemas"):
41-
continue
42-
if model.get_field_annotation(field_name, Mutability) in (
43-
Mutability.read_write,
44-
Mutability.write_only,
45-
):
46-
mutable_urns.append(test_obj.get_attribute_urn(field_name))
47-
48-
modified_obj = fill_with_random_values(context, test_obj, mutable_urns)
38+
modified_obj = fill_with_random_values(
39+
context, test_obj, mutability=[Mutability.read_write, Mutability.write_only]
40+
)
4941

5042
if modified_obj is None:
5143
raise ValueError(

scim2_tester/filling.py

Lines changed: 54 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,13 @@
2020
from scim2_models.utils import Base64Bytes
2121
from scim2_models.utils import _find_field_name
2222

23+
from scim2_tester.urns import get_annotation_by_urn
2324
from scim2_tester.urns import get_attribute_type_by_urn
2425
from scim2_tester.urns import get_multiplicity_by_urn
2526
from scim2_tester.urns import get_target_model_by_urn
2627
from scim2_tester.urns import iter_all_urns
2728
from scim2_tester.urns import set_value_by_urn
2829

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

@@ -78,15 +64,30 @@ def get_model_from_ref_type_(ref_type: type) -> Any:
7864
models = get_model_from_ref_type_(ref_type)
7965
models = models if isinstance(models, list) else [models]
8066
acceptable_models = [model for model in models if model != different_than]
67+
68+
if not acceptable_models:
69+
return models[0]
70+
8171
return acceptable_models[0]
8272

8373

8474
def generate_random_value(
8575
context: "CheckContext",
8676
urn: str,
8777
model: type[Resource],
88-
allowed_urns: list[str] | None = None,
78+
mutability: list[Mutability] | None = None,
79+
required: list[Required] | None = None,
8980
) -> Any:
81+
if mutability is not None:
82+
field_mutability = get_annotation_by_urn(Mutability, urn, model)
83+
if field_mutability not in mutability:
84+
return None
85+
86+
if required is not None:
87+
field_required = get_annotation_by_urn(Required, urn, model)
88+
if field_required not in required:
89+
return None
90+
9091
field_name = _find_field_name(model, urn)
9192
field_type = get_attribute_type_by_urn(model, urn)
9293
is_multiple = get_multiplicity_by_urn(model, urn)
@@ -141,14 +142,14 @@ def generate_random_value(
141142
value = random.choice(list(field_type))
142143

143144
elif isclass(field_type) and issubclass(field_type, ComplexAttribute):
144-
sub_urns = filter_sub_urns(urn, allowed_urns) if allowed_urns else None
145-
value = fill_complex_attribute_with_random_values(
146-
context, field_type(), sub_urns
145+
value = fill_with_random_values(
146+
context, field_type(), mutability=mutability, required=required
147147
) # type: ignore[arg-type]
148148

149149
elif isclass(field_type) and issubclass(field_type, Extension):
150-
sub_urns = filter_sub_urns(urn, allowed_urns) if allowed_urns else None
151-
value = fill_with_random_values(context, field_type(), sub_urns) # type: ignore[arg-type]
150+
value = fill_with_random_values(
151+
context, field_type(), mutability=mutability, required=required
152+
) # type: ignore[arg-type]
152153

153154
elif field_type is Base64Bytes:
154155
value = base64.b64encode(uuid.uuid4().bytes).decode("ascii")
@@ -166,69 +167,62 @@ def fill_with_random_values(
166167
context: "CheckContext",
167168
obj: Resource[Any],
168169
urns: list[str] | None = None,
170+
mutability: list[Mutability] | None = None,
171+
required: list[Required] | None = None,
169172
) -> Resource[Any] | None:
170173
"""Fill an object with random values generated according the attribute types.
171174
172175
:param context: The check context containing the SCIM client and configuration
173176
:param obj: The Resource object to fill with random values
174-
:param urns: Optional list of URNs to fill (defaults to all fields)
177+
:param urns: Optional list of URNs to fill (for backward compatibility)
178+
:param mutability: Optional list of mutability constraints to filter fields
179+
:param required: Optional list of required constraints to filter fields
175180
:returns: The filled object or None if the object ends up empty
176181
"""
177-
# If no URNs provided, generate URNs for all fields
182+
mutability = mutability or [
183+
Mutability.read_write,
184+
Mutability.write_only,
185+
Mutability.immutable,
186+
]
178187
if urns is None:
179188
urns = [
180189
urn
181190
for urn, _ in iter_all_urns(
182191
type(obj),
183-
mutability=[
184-
Mutability.read_write,
185-
Mutability.write_only,
186-
Mutability.immutable,
187-
],
192+
mutability=mutability,
193+
required=required,
188194
)
189195
]
190196

191197
for urn in urns:
192198
value = generate_random_value(
193-
context, urn=urn, model=type(obj), allowed_urns=urns
199+
context, urn=urn, model=type(obj), mutability=mutability, required=required
194200
)
195-
set_value_by_urn(obj, urn, value)
201+
if value is not None:
202+
set_value_by_urn(obj, urn, value)
196203

197204
fix_primary_attributes(obj)
205+
fix_reference_values(obj)
198206

199207
return obj
200208

201209

202-
def fill_complex_attribute_with_random_values(
203-
context: "CheckContext",
204-
obj: ComplexAttribute,
205-
urns: list[str] | None = None,
206-
) -> Resource[Any] | None:
207-
"""Fill a ComplexAttribute with random values.
210+
def fix_reference_values(obj: Resource[Any]) -> None:
211+
"""Fix reference values to extract IDs from reference URLs.
208212
209213
For SCIM reference fields, correctly sets the value field to match
210214
the ID extracted from the reference URL.
211215
"""
212-
if not urns:
213-
urns = []
214-
for field_name in type(obj).model_fields:
215-
if type(obj).get_field_annotation(field_name, Required) == Required.true:
216-
alias = (
217-
type(obj).model_fields[field_name].serialization_alias or field_name
218-
)
219-
urns.append(alias)
220-
221-
fill_with_random_values(context, obj, urns)
222-
if "ref" in type(obj).model_fields and "value" in type(obj).model_fields:
223-
ref_type = type(obj).get_field_root_type("ref")
224-
if (
225-
get_origin(ref_type) is Reference
226-
and get_args(ref_type)
227-
and get_args(ref_type)[0] not in (URIReference, ExternalReference, Any)
228-
and (ref := getattr(obj, "ref", None))
229-
):
230-
obj.value = ref.rsplit("/", 1)[-1]
231-
return obj
216+
for field_name, _field_info in type(obj).model_fields.items():
217+
attr_value = getattr(obj, field_name, None)
218+
if not attr_value or not isinstance(attr_value, list):
219+
continue
220+
221+
for item in attr_value:
222+
if not (ref := getattr(item, "ref", None)) or not hasattr(item, "value"):
223+
continue
224+
225+
item.value = ref.rsplit("/", 1)[-1]
232226

233227

234228
def fix_primary_attributes(obj: Resource[Any]) -> None:
@@ -241,7 +235,7 @@ def fix_primary_attributes(obj: Resource[Any]) -> None:
241235
242236
According to RFC 7643 §2.4: The primary attribute value "true" MUST appear no more than once.
243237
"""
244-
for field_name, _field_info in type(obj).model_fields.items():
238+
for field_name in type(obj).model_fields:
245239
attr_value = getattr(obj, field_name, None)
246240
if not attr_value or not isinstance(attr_value, list) or len(attr_value) == 0:
247241
continue
@@ -250,9 +244,6 @@ def fix_primary_attributes(obj: Resource[Any]) -> None:
250244
if not hasattr(first_item, "primary"):
251245
continue
252246

253-
if len(attr_value) == 1:
254-
attr_value[0].primary = True
255-
else:
256-
primary_index = random.randint(0, len(attr_value) - 1)
257-
for i, item in enumerate(attr_value):
258-
item.primary = i == primary_index
247+
primary_index = random.randint(0, len(attr_value) - 1)
248+
for i, item in enumerate(attr_value):
249+
item.primary = i == primary_index

scim2_tester/utils.py

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -237,26 +237,17 @@ def create_and_register(
237237
obj = model()
238238

239239
if fill_all:
240-
# Fill all fields except read-only ones (including extensions)
241-
urns = []
242-
for field_name in model.model_fields:
243-
if field_name in ("meta", "id", "schemas"):
244-
continue
245-
if (
246-
model.get_field_annotation(field_name, Mutability)
247-
!= Mutability.read_only
248-
):
249-
urns.append(obj.get_attribute_urn(field_name))
240+
obj = fill_with_random_values(
241+
self.context,
242+
obj,
243+
mutability=[
244+
Mutability.read_write,
245+
Mutability.write_only,
246+
Mutability.immutable,
247+
],
248+
)
250249
else:
251-
# Default behavior: fill only required fields
252-
urns = []
253-
for field_name in model.model_fields:
254-
if field_name in ("meta", "id", "schemas"):
255-
continue
256-
if model.get_field_annotation(field_name, Required) == Required.true:
257-
urns.append(obj.get_attribute_urn(field_name))
258-
259-
obj = fill_with_random_values(self.context, obj, urns)
250+
obj = fill_with_random_values(self.context, obj, required=[Required.true])
260251
created = self.context.client.create(obj)
261252

262253
# Handle the case where create might return Error or dict

0 commit comments

Comments
 (0)