@@ -576,3 +576,166 @@ async def test_applier_preserves_user_value_over_pool_reference(
576576 )
577577
578578 _validate_template_fields (fields = fields , user_fields = user_fields )
579+
580+
581+ class TestNodeTemplateApplierGroupForInstances :
582+ @pytest .fixture
583+ async def standard_group (self , db : InfrahubDatabase , default_branch : Branch , device_schema : None ) -> Node :
584+ group_schema = registry .schema .get_node_schema (name = InfrahubKind .STANDARDGROUP , branch = default_branch )
585+ group = await Node .init (db = db , schema = group_schema )
586+ await group .new (db = db , name = "staged-devices" )
587+ await group .save (db = db )
588+ return group
589+
590+ @pytest .fixture
591+ async def second_standard_group (self , db : InfrahubDatabase , default_branch : Branch , device_schema : None ) -> Node :
592+ group_schema = registry .schema .get_node_schema (name = InfrahubKind .STANDARDGROUP , branch = default_branch )
593+ group = await Node .init (db = db , schema = group_schema )
594+ await group .new (db = db , name = "prod-devices" )
595+ await group .save (db = db )
596+ return group
597+
598+ async def test_member_of_groups_for_instances_propagates_to_instance (
599+ self ,
600+ db : InfrahubDatabase ,
601+ default_branch : Branch ,
602+ device_schema : None ,
603+ standard_group : Node ,
604+ second_standard_group : Node ,
605+ ) -> None :
606+ template_schema = registry .schema .get_template_schema (name = f"Template{ TestKind .DEVICE } " , branch = default_branch )
607+ template = await Node .init (schema = template_schema , db = db , branch = default_branch )
608+ await template .new (
609+ db = db ,
610+ template_name = "grouped-template" ,
611+ manufacturer = "Acme" ,
612+ member_of_groups_for_instances = [{"id" : standard_group .id }, {"id" : second_standard_group .id }],
613+ )
614+ await template .save (db = db )
615+
616+ applier = NodeTemplateApplier (db = db , branch = default_branch , pool_allocator = NoOpPoolAllocator ())
617+ target_schema = registry .schema .get_node_schema (name = TestKind .DEVICE , branch = default_branch )
618+ user_fields = {"name" : "my-device" , "weight" : 100 , "airflow" : "Front to rear" }
619+
620+ fields = await applier .apply (
621+ template = template , target_schema = target_schema , target_id = "new-device-id" , user_fields = user_fields
622+ )
623+
624+ _validate_template_fields (
625+ fields = fields ,
626+ expected_relationships = [
627+ ExpectedTemplateRelationship (
628+ name = "member_of_groups" , peer_ids = [standard_group .id , second_standard_group .id ]
629+ ),
630+ ],
631+ user_fields = user_fields ,
632+ excluded_fields = ["member_of_groups_for_instances" ],
633+ )
634+
635+ async def test_subscriber_of_groups_for_instances_propagates_to_instance (
636+ self , db : InfrahubDatabase , default_branch : Branch , device_schema : None , standard_group : Node
637+ ) -> None :
638+ template_schema = registry .schema .get_template_schema (name = f"Template{ TestKind .DEVICE } " , branch = default_branch )
639+ template = await Node .init (schema = template_schema , db = db , branch = default_branch )
640+ await template .new (
641+ db = db ,
642+ template_name = "subscribed-template" ,
643+ manufacturer = "Acme" ,
644+ subscriber_of_groups_for_instances = [{"id" : standard_group .id }],
645+ )
646+ await template .save (db = db )
647+
648+ applier = NodeTemplateApplier (db = db , branch = default_branch , pool_allocator = NoOpPoolAllocator ())
649+ target_schema = registry .schema .get_node_schema (name = TestKind .DEVICE , branch = default_branch )
650+ user_fields = {"name" : "my-device" , "weight" : 100 , "airflow" : "Front to rear" }
651+
652+ fields = await applier .apply (
653+ template = template , target_schema = target_schema , target_id = "new-device-id" , user_fields = user_fields
654+ )
655+
656+ _validate_template_fields (
657+ fields = fields ,
658+ expected_relationships = [
659+ ExpectedTemplateRelationship (name = "subscriber_of_groups" , peer_ids = [standard_group .id ]),
660+ ],
661+ user_fields = user_fields ,
662+ excluded_fields = ["subscriber_of_groups_for_instances" ],
663+ )
664+
665+ async def test_template_member_of_groups_does_not_propagate (
666+ self , db : InfrahubDatabase , default_branch : Branch , device_schema : None , standard_group : Node
667+ ) -> None :
668+ template_schema = registry .schema .get_template_schema (name = f"Template{ TestKind .DEVICE } " , branch = default_branch )
669+ template = await Node .init (schema = template_schema , db = db , branch = default_branch )
670+ await template .new (
671+ db = db ,
672+ template_name = "self-member-template" ,
673+ manufacturer = "Acme" ,
674+ member_of_groups = [{"id" : standard_group .id }],
675+ )
676+ await template .save (db = db )
677+
678+ applier = NodeTemplateApplier (db = db , branch = default_branch , pool_allocator = NoOpPoolAllocator ())
679+ target_schema = registry .schema .get_node_schema (name = TestKind .DEVICE , branch = default_branch )
680+ user_fields = {"name" : "my-device" , "weight" : 100 , "airflow" : "Front to rear" }
681+
682+ fields = await applier .apply (
683+ template = template , target_schema = target_schema , target_id = "new-device-id" , user_fields = user_fields
684+ )
685+
686+ _validate_template_fields (
687+ fields = fields , user_fields = user_fields , excluded_fields = ["member_of_groups" , "subscriber_of_groups" ]
688+ )
689+
690+ async def test_user_member_of_groups_takes_precedence (
691+ self ,
692+ db : InfrahubDatabase ,
693+ default_branch : Branch ,
694+ device_schema : None ,
695+ standard_group : Node ,
696+ second_standard_group : Node ,
697+ ) -> None :
698+ template_schema = registry .schema .get_template_schema (name = f"Template{ TestKind .DEVICE } " , branch = default_branch )
699+ template = await Node .init (schema = template_schema , db = db , branch = default_branch )
700+ await template .new (
701+ db = db ,
702+ template_name = "grouped-template" ,
703+ manufacturer = "Acme" ,
704+ member_of_groups_for_instances = [{"id" : standard_group .id }],
705+ )
706+ await template .save (db = db )
707+
708+ applier = NodeTemplateApplier (db = db , branch = default_branch , pool_allocator = NoOpPoolAllocator ())
709+ target_schema = registry .schema .get_node_schema (name = TestKind .DEVICE , branch = default_branch )
710+ user_fields = {
711+ "name" : "my-device" ,
712+ "weight" : 100 ,
713+ "airflow" : "Front to rear" ,
714+ "member_of_groups" : [{"id" : second_standard_group .id }],
715+ }
716+
717+ fields = await applier .apply (
718+ template = template , target_schema = target_schema , target_id = "new-device-id" , user_fields = user_fields
719+ )
720+
721+ _validate_template_fields (fields = fields , user_fields = user_fields )
722+
723+ async def test_empty_for_instances_does_not_set_field (
724+ self , db : InfrahubDatabase , default_branch : Branch , device_schema : None
725+ ) -> None :
726+ template_schema = registry .schema .get_template_schema (name = f"Template{ TestKind .DEVICE } " , branch = default_branch )
727+ template = await Node .init (schema = template_schema , db = db , branch = default_branch )
728+ await template .new (db = db , template_name = "empty-groups-template" , manufacturer = "Acme" )
729+ await template .save (db = db )
730+
731+ applier = NodeTemplateApplier (db = db , branch = default_branch , pool_allocator = NoOpPoolAllocator ())
732+ target_schema = registry .schema .get_node_schema (name = TestKind .DEVICE , branch = default_branch )
733+ user_fields = {"name" : "my-device" , "weight" : 100 , "airflow" : "Front to rear" }
734+
735+ fields = await applier .apply (
736+ template = template , target_schema = target_schema , target_id = "new-device-id" , user_fields = user_fields
737+ )
738+
739+ _validate_template_fields (
740+ fields = fields , user_fields = user_fields , excluded_fields = ["member_of_groups" , "subscriber_of_groups" ]
741+ )
0 commit comments