@@ -931,3 +931,206 @@ def test_apply_two_changes_that_create_the_same_object_return_200(self):
931931 ],
932932 }
933933 _ = self .send_request (payload2 )
934+
935+ def test_module_bay_from_template_no_duplicate (self ):
936+ """Test that module bays created from templates are reused and updated, not duplicated."""
937+ from dcim .models import Module , ModuleBay , ModuleBayTemplate , ModuleType
938+
939+ # Create a device type with a module bay template
940+ device_type = DeviceType .objects .create (
941+ manufacturer = Manufacturer .objects .first (),
942+ model = "Device with Module Bay Template" ,
943+ slug = "device-with-module-bay-template" ,
944+ )
945+
946+ # Create module bay template
947+ ModuleBayTemplate .objects .create (
948+ device_type = device_type ,
949+ name = "Tray" ,
950+ )
951+
952+ # Create module type
953+ module_type = ModuleType .objects .create (
954+ manufacturer = Manufacturer .objects .first (),
955+ model = "Test Module Type" ,
956+ )
957+
958+ # Step 1: Create a device - this will auto-create module bay "Tray" from template
959+ device_payload = {
960+ "id" : str (uuid .uuid4 ()),
961+ "changes" : [
962+ {
963+ "change_id" : str (uuid .uuid4 ()),
964+ "change_type" : "create" ,
965+ "object_version" : None ,
966+ "object_type" : "dcim.device" ,
967+ "object_id" : None ,
968+ "ref_id" : "device-1" ,
969+ "data" : {
970+ "name" : "Test Device with Module Bay" ,
971+ "device_type" : device_type .id ,
972+ "role" : self .roles [0 ].id ,
973+ "site" : self .sites [0 ].id ,
974+ },
975+ },
976+ ],
977+ }
978+ self .send_request (device_payload )
979+
980+ # Verify device was created
981+ device = Device .objects .get (name = "Test Device with Module Bay" )
982+
983+ # Verify module bay was auto-created from template
984+ module_bays_before = ModuleBay .objects .filter (device = device , name = "Tray" )
985+ self .assertEqual (module_bays_before .count (), 1 )
986+ module_bay = module_bays_before .first ()
987+ self .assertIsNone (module_bay .module ) # No module installed yet
988+ self .assertEqual (module_bay .description , "" ) # Template has no description
989+
990+ # Step 2: Create a module with the module bay - should reuse existing bay and update it
991+ module_payload = {
992+ "id" : str (uuid .uuid4 ()),
993+ "changes" : [
994+ {
995+ "change_id" : str (uuid .uuid4 ()),
996+ "change_type" : "create" ,
997+ "object_version" : None ,
998+ "object_type" : "dcim.modulebay" ,
999+ "object_id" : None ,
1000+ "ref_id" : "modulebay-1" ,
1001+ "data" : {
1002+ "name" : "Tray" ,
1003+ "device" : device .id ,
1004+ "description" : "Ingested module bay" ,
1005+ },
1006+ },
1007+ {
1008+ "change_id" : str (uuid .uuid4 ()),
1009+ "change_type" : "create" ,
1010+ "object_version" : None ,
1011+ "object_type" : "dcim.module" ,
1012+ "object_id" : None ,
1013+ "ref_id" : "module-1" ,
1014+ "new_refs" : ["module_bay" ],
1015+ "data" : {
1016+ "device" : device .id ,
1017+ "module_bay" : "modulebay-1" ,
1018+ "module_type" : module_type .id ,
1019+ "description" : "Ingested module" ,
1020+ },
1021+ },
1022+ ],
1023+ }
1024+ self .send_request (module_payload )
1025+
1026+ # Verify NO duplicate module bays were created
1027+ module_bays_after = ModuleBay .objects .filter (device = device , name = "Tray" )
1028+ self .assertEqual (
1029+ module_bays_after .count (),
1030+ 1 ,
1031+ "Module bay should be reused, not duplicated"
1032+ )
1033+
1034+ # Verify the module bay was updated with the description
1035+ module_bay .refresh_from_db ()
1036+ self .assertEqual (
1037+ module_bay .description ,
1038+ "Ingested module bay" ,
1039+ "Module bay should be updated with ingested data"
1040+ )
1041+
1042+ # Verify module was created successfully
1043+ modules = Module .objects .filter (device = device , module_bay = module_bay )
1044+ self .assertEqual (modules .count (), 1 )
1045+ module = modules .first ()
1046+ self .assertEqual (module .module_type , module_type )
1047+ self .assertEqual (module .description , "Ingested module" )
1048+
1049+ def test_interface_from_template_no_duplicate (self ):
1050+ """Test that interfaces created from templates are reused and updated, not duplicated."""
1051+ from dcim .models import InterfaceTemplate
1052+
1053+ # Create a device type with an interface template
1054+ device_type = DeviceType .objects .create (
1055+ manufacturer = Manufacturer .objects .first (),
1056+ model = "Device with Interface Template" ,
1057+ slug = "device-with-interface-template" ,
1058+ )
1059+
1060+ # Create interface template
1061+ InterfaceTemplate .objects .create (
1062+ device_type = device_type ,
1063+ name = "eth0" ,
1064+ type = "1000base-t" ,
1065+ )
1066+
1067+ # Step 1: Create a device - this will auto-create interface "eth0" from template
1068+ device_payload = {
1069+ "id" : str (uuid .uuid4 ()),
1070+ "changes" : [
1071+ {
1072+ "change_id" : str (uuid .uuid4 ()),
1073+ "change_type" : "create" ,
1074+ "object_version" : None ,
1075+ "object_type" : "dcim.device" ,
1076+ "object_id" : None ,
1077+ "ref_id" : "device-1" ,
1078+ "data" : {
1079+ "name" : "Test Device with Interface" ,
1080+ "device_type" : device_type .id ,
1081+ "role" : self .roles [0 ].id ,
1082+ "site" : self .sites [0 ].id ,
1083+ },
1084+ },
1085+ ],
1086+ }
1087+ self .send_request (device_payload )
1088+
1089+ # Verify device was created
1090+ device = Device .objects .get (name = "Test Device with Interface" )
1091+
1092+ # Verify interface was auto-created from template
1093+ interfaces_before = Interface .objects .filter (device = device , name = "eth0" )
1094+ self .assertEqual (interfaces_before .count (), 1 )
1095+ interface = interfaces_before .first ()
1096+ self .assertEqual (interface .description , "" ) # Template has no description
1097+
1098+ # Step 2: Try to create the same interface with additional data - should reuse and update
1099+ interface_payload = {
1100+ "id" : str (uuid .uuid4 ()),
1101+ "changes" : [
1102+ {
1103+ "change_id" : str (uuid .uuid4 ()),
1104+ "change_type" : "create" ,
1105+ "object_version" : None ,
1106+ "object_type" : "dcim.interface" ,
1107+ "object_id" : None ,
1108+ "ref_id" : "interface-1" ,
1109+ "data" : {
1110+ "name" : "eth0" ,
1111+ "device" : device .id ,
1112+ "type" : "1000base-t" ,
1113+ "description" : "Ingested interface" ,
1114+ "enabled" : True ,
1115+ },
1116+ },
1117+ ],
1118+ }
1119+ self .send_request (interface_payload )
1120+
1121+ # Verify NO duplicate interfaces were created
1122+ interfaces_after = Interface .objects .filter (device = device , name = "eth0" )
1123+ self .assertEqual (
1124+ interfaces_after .count (),
1125+ 1 ,
1126+ "Interface should be reused, not duplicated"
1127+ )
1128+
1129+ # Verify the interface was updated with the description
1130+ interface .refresh_from_db ()
1131+ self .assertEqual (
1132+ interface .description ,
1133+ "Ingested interface" ,
1134+ "Interface should be updated with ingested data"
1135+ )
1136+ self .assertTrue (interface .enabled )
0 commit comments