2020from scim2_models .utils import Base64Bytes
2121from scim2_models .utils import _find_field_name
2222
23+ from scim2_tester .urns import get_annotation_by_urn
2324from scim2_tester .urns import get_attribute_type_by_urn
2425from scim2_tester .urns import get_multiplicity_by_urn
2526from scim2_tester .urns import get_target_model_by_urn
2627from scim2_tester .urns import iter_all_urns
2728from 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-
4430if 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
8474def 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
234228def 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
0 commit comments