@@ -306,16 +306,16 @@ class TestIdentifyMissingResources:
306306 @patch ("class_generator.core.schema.run_command" )
307307 def test_successful_missing_resources_identification (self , mock_run_command ):
308308 """Test successful identification of missing resources."""
309- api_resources_output = """pods po v1 Pod
310- services svc v1 Service
311- deployments deploy apps/v1 Deployment
312- configmaps cm v1 ConfigMap"""
309+ api_resources_output = """pods po v1 true Pod
310+ services svc v1 true Service
311+ deployments deploy apps/v1 true Deployment
312+ configmaps cm v1 true ConfigMap"""
313313
314314 mock_run_command .return_value = (True , api_resources_output , "" )
315315
316316 existing_mapping = {
317- "pod" : [{"x-kubernetes-group-version-kind" : [{"kind" : "Pod" }]}],
318- "service" : [{"x-kubernetes-group-version-kind" : [{"kind" : "Service" }]}],
317+ "pod" : [{"x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , " kind" : "Pod" }]}],
318+ "service" : [{"x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , " kind" : "Service" }]}],
319319 }
320320
321321 result = identify_missing_resources ("kubectl" , existing_mapping )
@@ -333,14 +333,14 @@ def test_api_resources_command_failure(self, mock_run_command):
333333 @patch ("class_generator.core.schema.run_command" )
334334 def test_no_missing_resources (self , mock_run_command ):
335335 """Test when no resources are missing."""
336- api_resources_output = """pods po v1 Pod
337- services svc v1 Service"""
336+ api_resources_output = """pods po v1 true Pod
337+ services svc v1 true Service"""
338338
339339 mock_run_command .return_value = (True , api_resources_output , "" )
340340
341341 existing_mapping = {
342- "pod" : [{"x-kubernetes-group-version-kind" : [{"kind" : "Pod" }]}],
343- "service" : [{"x-kubernetes-group-version-kind" : [{"kind" : "Service" }]}],
342+ "pod" : [{"x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , " kind" : "Pod" }]}],
343+ "service" : [{"x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , " kind" : "Service" }]}],
344344 }
345345
346346 result = identify_missing_resources ("kubectl" , existing_mapping )
@@ -349,7 +349,9 @@ def test_no_missing_resources(self, mock_run_command):
349349 @patch ("class_generator.core.schema.run_command" )
350350 def test_malformed_existing_mapping_handled (self , mock_run_command ):
351351 """Test that malformed existing mapping is handled gracefully."""
352- api_resources_output = "pods po v1 Pod"
352+ api_resources_output = (
353+ "pods po v1 true Pod"
354+ )
353355 mock_run_command .return_value = (True , api_resources_output , "" )
354356
355357 # Malformed mapping - missing required structure
@@ -359,6 +361,166 @@ def test_malformed_existing_mapping_handled(self, mock_run_command):
359361 assert "Pod" in result
360362
361363
364+ class TestIdentifyMissingResourcesDuplicateKinds :
365+ """Test identify_missing_resources with duplicate kinds across API groups."""
366+
367+ CLUSTER_OUTPUT_DUPLICATE_BACKUP = (
368+ "configmaps cm v1 true ConfigMap\n "
369+ "backups velero.io/v1 true Backup\n "
370+ "backups postgresql.cnpg.noobaa.io/v1 true Backup\n "
371+ "widgets example.com/v1 true Widget"
372+ )
373+
374+ @pytest .mark .parametrize (
375+ "test_id, existing_mapping, expected_missing" ,
376+ [
377+ pytest .param (
378+ "duplicate_kind_one_group_covered" ,
379+ {
380+ "backup" : [
381+ {
382+ "x-kubernetes-group-version-kind" : [
383+ {"group" : "postgresql.cnpg.noobaa.io" , "version" : "v1" , "kind" : "Backup" }
384+ ],
385+ "namespaced" : True ,
386+ }
387+ ],
388+ "configmap" : [
389+ {
390+ "x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , "kind" : "ConfigMap" }],
391+ "namespaced" : True ,
392+ }
393+ ],
394+ },
395+ {"Backup" , "Widget" },
396+ id = "duplicate_kind_one_group_covered" ,
397+ ),
398+ pytest .param (
399+ "duplicate_kind_all_groups_covered" ,
400+ {
401+ "backup_velero" : [
402+ {
403+ "x-kubernetes-group-version-kind" : [
404+ {"group" : "velero.io" , "version" : "v1" , "kind" : "Backup" }
405+ ],
406+ "namespaced" : True ,
407+ }
408+ ],
409+ "backup_cnpg" : [
410+ {
411+ "x-kubernetes-group-version-kind" : [
412+ {"group" : "postgresql.cnpg.noobaa.io" , "version" : "v1" , "kind" : "Backup" }
413+ ],
414+ "namespaced" : True ,
415+ }
416+ ],
417+ "configmap" : [
418+ {
419+ "x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , "kind" : "ConfigMap" }],
420+ "namespaced" : True ,
421+ }
422+ ],
423+ "widget" : [
424+ {
425+ "x-kubernetes-group-version-kind" : [
426+ {"group" : "example.com" , "version" : "v1" , "kind" : "Widget" }
427+ ],
428+ "namespaced" : True ,
429+ }
430+ ],
431+ },
432+ set (),
433+ id = "duplicate_kind_all_groups_covered" ,
434+ ),
435+ pytest .param (
436+ "completely_new_kind" ,
437+ {
438+ "configmap" : [
439+ {
440+ "x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , "kind" : "ConfigMap" }],
441+ "namespaced" : True ,
442+ }
443+ ],
444+ },
445+ {"Backup" , "Widget" },
446+ id = "completely_new_kind" ,
447+ ),
448+ pytest .param (
449+ "core_api_resource_covered" ,
450+ {
451+ "backup_velero" : [
452+ {
453+ "x-kubernetes-group-version-kind" : [
454+ {"group" : "velero.io" , "version" : "v1" , "kind" : "Backup" }
455+ ],
456+ "namespaced" : True ,
457+ }
458+ ],
459+ "backup_cnpg" : [
460+ {
461+ "x-kubernetes-group-version-kind" : [
462+ {"group" : "postgresql.cnpg.noobaa.io" , "version" : "v1" , "kind" : "Backup" }
463+ ],
464+ "namespaced" : True ,
465+ }
466+ ],
467+ "configmap" : [
468+ {
469+ "x-kubernetes-group-version-kind" : [{"group" : "" , "version" : "v1" , "kind" : "ConfigMap" }],
470+ "namespaced" : True ,
471+ }
472+ ],
473+ },
474+ {"Widget" },
475+ id = "core_api_resource_covered" ,
476+ ),
477+ ],
478+ )
479+ @patch ("class_generator.core.schema.run_command" )
480+ def test_identify_missing_resources_duplicate_kinds (
481+ self ,
482+ mock_run_command ,
483+ test_id ,
484+ existing_mapping ,
485+ expected_missing ,
486+ ):
487+ """Test identify_missing_resources correctly handles duplicate kinds across API groups."""
488+ mock_run_command .return_value = (True , self .CLUSTER_OUTPUT_DUPLICATE_BACKUP , "" )
489+
490+ result = identify_missing_resources ("oc" , existing_mapping )
491+ assert result == expected_missing , f"[{ test_id } ] Expected { expected_missing } , got { result } "
492+
493+ def test_allow_updates_false_appends_missing_group_variant (self ):
494+ """Test that allow_updates=False still appends missing group variants for existing kinds."""
495+ schemas = {
496+ "apis/postgresql.cnpg.noobaa.io/v1" : {
497+ "components" : {
498+ "schemas" : {
499+ "io.example.Backup" : {
500+ "type" : "object" ,
501+ "x-kubernetes-group-version-kind" : [
502+ {"group" : "postgresql.cnpg.noobaa.io" , "version" : "v1" , "kind" : "Backup" }
503+ ],
504+ }
505+ }
506+ }
507+ }
508+ }
509+ existing_mapping = {
510+ "backup" : [{"x-kubernetes-group-version-kind" : [{"group" : "velero.io" , "version" : "v1" , "kind" : "Backup" }]}]
511+ }
512+
513+ resources_mapping , _ = process_schema_definitions (
514+ schemas = schemas ,
515+ namespacing_dict = {"Backup" : True },
516+ existing_resources_mapping = existing_mapping ,
517+ allow_updates = False ,
518+ )
519+
520+ groups = {item ["x-kubernetes-group-version-kind" ][0 ].get ("group" , "" ) for item in resources_mapping ["backup" ]}
521+ assert groups == {"velero.io" , "postgresql.cnpg.noobaa.io" }
522+
523+
362524class TestBuildDynamicResourceToApiMapping :
363525 """Test build_dynamic_resource_to_api_mapping function."""
364526
0 commit comments