@@ -44,6 +44,30 @@ def first(self):
4444 return self ._obj
4545
4646
47+ class _Attr :
48+ def __init__ (self , id , name , required ):
49+ self .Id = id
50+ self .Name = name
51+ self .Required = required
52+ self .DataType = "string"
53+ self .Array = "No"
54+ self .ValueSetId = None
55+
56+ def dict (self ):
57+ return {
58+ "Description" : None ,
59+ "UseConsiderations" : None ,
60+ "Example" : None ,
61+ "Format" : None ,
62+ "Id" : self .Id ,
63+ "Name" : self .Name ,
64+ "Required" : self .Required ,
65+ "DataType" : self .DataType ,
66+ "Array" : self .Array ,
67+ "ValueSetId" : self .ValueSetId ,
68+ }
69+
70+
4771@pytest .fixture
4872def fake_session ():
4973 s = MagicMock ()
@@ -170,7 +194,9 @@ async def test_add_ref_nested_parent_path():
170194 key = "ChildRef" ,
171195 )
172196
173- parent_props = openapi_spec ["components" ]["schemas" ]["Root" ]["properties" ]["Intermediate" ]["properties" ]["Parent" ]["properties" ]
197+ parent_props = openapi_spec ["components" ]["schemas" ]["Root" ]["properties" ]["Intermediate" ]["properties" ]["Parent" ][
198+ "properties"
199+ ]
174200 child_ref_inline = parent_props ["ChildRef" ]
175201 assert child_ref_inline ["type" ] == "object"
176202 assert set (child_ref_inline ["properties" ].keys ()) == {"a" , "b" }
@@ -344,24 +370,6 @@ def get_entity_by_id_side_effect(session=None, id=None, **_):
344370
345371 monkeypatch .setattr (svc , "get_entity_by_id" , AsyncMock (side_effect = get_entity_by_id_side_effect ))
346372
347- class _Attr :
348- def __init__ (self , id , name , required ):
349- self .Id = id
350- self .Name = name
351- self .Required = required
352- self .DataType = "string"
353- self .Array = "No"
354- self .ValueSetId = None
355-
356- def dict (self ):
357- return {
358- "Description" : None ,
359- "UseConsiderations" : None ,
360- "Example" : None ,
361- "Format" : None ,
362- "DataType" : self .DataType ,
363- }
364-
365373 def get_attributes_with_association_metadata_for_entity_side_effect (entity_id , ** _ ):
366374 if entity_id == 101 : # CompetencyFramework
367375 return [_Attr (1 , "uri" , "Yes" ), _Attr (2 , "name" , "Yes" ), _Attr (3 , "description" , "No" )]
@@ -405,6 +413,79 @@ def get_attributes_with_association_metadata_for_entity_side_effect(entity_id, *
405413 assert assoc_schema ["required" ] == ["competencyFrameworkId" , "competencyFrameworkType" ]
406414
407415
416+ async def test_generate_openapi_schema_has_sub_entity_required_entity (fake_session , monkeypatch ):
417+ """
418+ Given:
419+ - BaseLIF CompetencyFramework entity with child entity Association in EntityAssociations
420+ - The Association Entity has Required="Yes" in EntityAssociations
421+ Result:
422+ - The generated schema for CompetencyFramework includes Association with its required fields listed
423+ """
424+ dm = types .SimpleNamespace (
425+ Id = 1 ,
426+ Name = "BaseLIF" ,
427+ DataModelVersion = "3.0" ,
428+ Type = "BaseLIF" ,
429+ BaseDataModelId = None ,
430+ ContributorOrganization = "LIF" ,
431+ )
432+ monkeypatch .setattr (svc , "get_datamodel_by_id" , AsyncMock (return_value = dm ))
433+
434+ def get_entity_by_id_side_effect (session = None , id = None , ** _ ):
435+ if id == 101 :
436+ return types .SimpleNamespace (
437+ Id = 101 , Name = "CompetencyFramework" , Array = "No" , UseConsiderations = None , Deleted = False
438+ )
439+ elif id == 202 :
440+ return types .SimpleNamespace (
441+ Id = 202 , Name = "Association" , Array = "Yes" , UseConsiderations = None , Deleted = False , Required = "Yes"
442+ )
443+ else :
444+ raise ValueError (f"Unexpected entity_id { id } " )
445+
446+ monkeypatch .setattr (svc , "get_entity_by_id" , AsyncMock (side_effect = get_entity_by_id_side_effect ))
447+
448+ def get_attributes_with_association_metadata_for_entity_side_effect (entity_id , ** _ ):
449+ if entity_id == 101 : # CompetencyFramework
450+ return [_Attr (1 , "uri" , "Yes" ), _Attr (2 , "name" , "Yes" ), _Attr (3 , "description" , "No" )]
451+ elif entity_id == 202 : # Association
452+ return [_Attr (1 , "uri" , "Yes" ), _Attr (2 , "name" , "Yes" ), _Attr (3 , "description" , "No" )]
453+ else :
454+ raise ValueError (f"Unexpected entity_id { entity_id } " )
455+
456+ monkeypatch .setattr (
457+ svc ,
458+ "get_attributes_with_association_metadata_for_entity" ,
459+ AsyncMock (side_effect = get_attributes_with_association_metadata_for_entity_side_effect ),
460+ )
461+
462+ RowIN = namedtuple ("RowIN" , ["Id" , "Name" ])
463+ fake_session .execute .side_effect = [
464+ # get embedded entity associations
465+ _FetchallResult ([(101 , 202 )]),
466+ # get entities in DM not in union of associations
467+ _FetchallResult ([]),
468+ # build df_entity (include both entities)
469+ _FetchallResult ([RowIN (101 , "CompetencyFramework" ), RowIN (202 , "Association" )]),
470+ # find_children: get detailed association rows via .scalars().all()
471+ _ScalarListResult ([types .SimpleNamespace (Relationship = None )]),
472+ # enums for attributes (none)
473+ _FetchallResult ([]),
474+ _FetchallResult ([]),
475+ # inter-entity "Reference" links
476+ _FetchallResult ([]),
477+ ]
478+
479+ out = await svc .generate_openapi_schema (
480+ fake_session , data_model_id = 1 , include_attr_md = False , include_entity_md = False
481+ )
482+ cf_schema = out ["components" ]["schemas" ]["CompetencyFramework" ]
483+ assert cf_schema ["type" ] == "object"
484+ assert "Association" in cf_schema ["required" ]
485+ assert "uri" in cf_schema ["required" ]
486+ assert "name" in cf_schema ["required" ]
487+
488+
408489async def test_generate_openapi_schema_full_export (fake_session , monkeypatch ):
409490 """
410491 Given:
0 commit comments