5454Group = load_model ("openwisp_users" , "Group" )
5555
5656
57+ class TestDeviceAdminMixin :
58+ _device_params = {
59+ "name" : "test-device" ,
60+ "hardware_id" : "1234" ,
61+ "mac_address" : CreateConfigTemplateMixin .TEST_MAC_ADDRESS ,
62+ "key" : CreateConfigTemplateMixin .TEST_KEY ,
63+ "model" : "" ,
64+ "os" : "" ,
65+ "notes" : "" ,
66+ "config-0-id" : "" ,
67+ "config-0-device" : "" ,
68+ "config-0-backend" : "netjsonconfig.OpenWrt" ,
69+ "config-0-templates" : "" ,
70+ "config-0-config" : json .dumps ({}),
71+ "config-0-context" : "" ,
72+ "config-TOTAL_FORMS" : 1 ,
73+ "config-INITIAL_FORMS" : 0 ,
74+ "config-MIN_NUM_FORMS" : 0 ,
75+ "config-MAX_NUM_FORMS" : 1 ,
76+ # openwisp_controller.connection
77+ "deviceconnection_set-TOTAL_FORMS" : 0 ,
78+ "deviceconnection_set-INITIAL_FORMS" : 0 ,
79+ "deviceconnection_set-MIN_NUM_FORMS" : 0 ,
80+ "deviceconnection_set-MAX_NUM_FORMS" : 1000 ,
81+ "command_set-TOTAL_FORMS" : 0 ,
82+ "command_set-INITIAL_FORMS" : 0 ,
83+ "command_set-MIN_NUM_FORMS" : 0 ,
84+ "command_set-MAX_NUM_FORMS" : 1000 ,
85+ }
86+ # WARNING - WATCHOUT
87+ # this class attribute is changed dynamically
88+ # by other apps which add inlines to DeviceAdmin
89+ _additional_params = {}
90+
91+ def _get_device_params (self , org ):
92+ p = self ._device_params .copy ()
93+ p .update (self ._additional_params )
94+ p ["organization" ] = org .pk
95+ return p
96+
97+
5798class TestImportExportMixin :
5899 """
59100 Reused in OpenWISP Monitoring
@@ -237,6 +278,7 @@ class TestAdmin(
237278 CreateDeviceGroupMixin ,
238279 CreateConfigTemplateMixin ,
239280 TestVpnX509Mixin ,
281+ TestDeviceAdminMixin ,
240282 TestAdminMixin ,
241283 TestCase ,
242284):
@@ -278,10 +320,6 @@ class TestAdmin(
278320 "command_set-MIN_NUM_FORMS" : 0 ,
279321 "command_set-MAX_NUM_FORMS" : 1000 ,
280322 }
281- # WARNING - WATCHOUT
282- # this class attribute is changed dynamically
283- # by other apps which add inlines to DeviceAdmin
284- _additional_params = {}
285323
286324 def setUp (self ):
287325 self .client .force_login (self ._get_admin ())
@@ -291,12 +329,6 @@ def tearDownClass(cls):
291329 super ().tearDownClass ()
292330 devnull .close ()
293331
294- def _get_device_params (self , org ):
295- p = self ._device_params .copy ()
296- p .update (self ._additional_params )
297- p ["organization" ] = org .pk
298- return p
299-
300332 def test_device_and_template_different_organization (self ):
301333 org1 = self ._get_org ()
302334 template = self ._create_template (organization = org1 )
@@ -2251,6 +2283,7 @@ def test_templates_fetch_queries_10(self):
22512283class TestTransactionAdmin (
22522284 CreateConfigTemplateMixin ,
22532285 TestAdminMixin ,
2286+ TestDeviceAdminMixin ,
22542287 TransactionTestCase ,
22552288):
22562289 app_label = "config"
@@ -2569,6 +2602,152 @@ def test_restoring_template_sends_config_modified(self):
25692602 self .assertEqual (config .status , "modified" )
25702603 self .assertNotEqual (config .checksum , config_checksum )
25712604
2605+ @patch .object (Config , "_CONFIG_MODIFIED_TIMEOUT" , 3 )
2606+ def test_config_modified_signal (self ):
2607+ """
2608+ Verifies multiple config_modified signal are not sent for
2609+ a single change
2610+ """
2611+ required = self ._create_template (
2612+ name = "SSH Keys" ,
2613+ required = True ,
2614+ config = {
2615+ "files" : [
2616+ {
2617+ "path" : "/etc/dropbear/authorized_keys" ,
2618+ "mode" : "0600" ,
2619+ "contents" : "ssh-rsa" ,
2620+ }
2621+ ]
2622+ },
2623+ )
2624+ template1 = self ._create_template (
2625+ default_values = {"ssid" : "OpenWISP" }, default = True
2626+ )
2627+ template2 = self ._create_template (
2628+ name = "template2" ,
2629+ config = {"interfaces" : [{"name" : "{{ ifname }}" , "type" : "ethernet" }]},
2630+ default_values = {"ifname" : "eth1" },
2631+ default = True ,
2632+ )
2633+ path = reverse (f"admin:{ self .app_label } _device_add" )
2634+ params = self ._get_device_params (org = self ._get_org ())
2635+ params .update (
2636+ {"config-0-templates" : f"{ required .pk } ,{ template1 .pk } ,{ template2 .pk } " }
2637+ )
2638+ response = self .client .post (path , data = params , follow = True )
2639+ self .assertEqual (response .status_code , 200 )
2640+
2641+ config = Device .objects .get (name = params ["name" ]).config
2642+ self .assertEqual (config .templates .count (), 3 )
2643+ path = reverse (f"admin:{ self .app_label } _device_change" , args = [config .device_id ])
2644+ params .update (
2645+ {
2646+ "config-0-id" : str (config .pk ),
2647+ "config-0-device" : str (config .device_id ),
2648+ "config-INITIAL_FORMS" : 1 ,
2649+ "_continue" : True ,
2650+ }
2651+ )
2652+
2653+ config ._delete_config_modified_timeout_cache ()
2654+ with self .subTest (
2655+ "Updating the unused context variable does not send config_modified signal"
2656+ ):
2657+ with patch (
2658+ "openwisp_controller.config.signals.config_modified.send"
2659+ ) as mocked_signal :
2660+ params .update (
2661+ {
2662+ "config-0-context" : json .dumps (
2663+ {"ssid" : "Updated" , "ifname" : "eth1" }
2664+ )
2665+ }
2666+ )
2667+ response = self .client .post (path , data = params , follow = True )
2668+ self .assertEqual (response .status_code , 200 )
2669+ config .refresh_from_db ()
2670+ self .assertEqual (config .context ["ssid" ], "Updated" )
2671+ mocked_signal .assert_not_called ()
2672+
2673+ config ._delete_config_modified_timeout_cache ()
2674+ with self .subTest (
2675+ "Changing used context variable sends config_modified signal"
2676+ ):
2677+ with patch (
2678+ "openwisp_controller.config.signals.config_modified.send"
2679+ ) as mocked_signal :
2680+ params .update (
2681+ {
2682+ "config-0-context" : json .dumps (
2683+ {"ssid" : "Updated" , "ifname" : "eth2" }
2684+ )
2685+ }
2686+ )
2687+ response = self .client .post (path , data = params , follow = True )
2688+ self .assertEqual (response .status_code , 200 )
2689+
2690+ config .refresh_from_db ()
2691+ self .assertEqual (
2692+ config .context ["ssid" ],
2693+ "Updated" ,
2694+ )
2695+ self .assertEqual (config .status , "modified" )
2696+ mocked_signal .assert_called_once ()
2697+
2698+ config ._delete_config_modified_timeout_cache ()
2699+ with self .subTest ("Changing device configuration sends config_modified signal" ):
2700+ with patch (
2701+ "openwisp_controller.config.signals.config_modified.send"
2702+ ) as mocked_signal :
2703+ params .update (
2704+ {
2705+ "config-0-config" : json .dumps (
2706+ {"interfaces" : [{"name" : "eth5" , "type" : "ethernet" }]}
2707+ )
2708+ }
2709+ )
2710+ response = self .client .post (path , data = params , follow = True )
2711+ self .assertEqual (response .status_code , 200 )
2712+ config .refresh_from_db ()
2713+ self .assertEqual (
2714+ config .config ["interfaces" ][0 ]["name" ],
2715+ "eth5" ,
2716+ )
2717+ self .assertEqual (config .status , "modified" )
2718+ mocked_signal .assert_called_once ()
2719+
2720+ config ._delete_config_modified_timeout_cache ()
2721+ with self .subTest ("Changing applied template sends config_modified signal" ):
2722+ with patch (
2723+ "openwisp_controller.config.signals.config_modified.send"
2724+ ) as mocked_signal :
2725+ response = self .client .post (
2726+ reverse (
2727+ f"admin:{ self .app_label } _template_change" , args = [template2 .pk ]
2728+ ),
2729+ data = {
2730+ "name" : template2 .name ,
2731+ "organization" : "" ,
2732+ "type" : template2 .type ,
2733+ "backend" : template2 .backend ,
2734+ "config" : json .dumps (template2 .config ),
2735+ "default_values" : json .dumps (
2736+ {
2737+ "ifname" : "eth3" ,
2738+ }
2739+ ),
2740+ "required" : False ,
2741+ "default" : True ,
2742+ "_continue" : True ,
2743+ },
2744+ follow = True ,
2745+ )
2746+ self .assertEqual (response .status_code , 200 )
2747+ template2 .refresh_from_db ()
2748+ self .assertEqual (template2 .default_values ["ifname" ], "eth3" )
2749+ mocked_signal .assert_called_once ()
2750+
25722751
25732752class TestDeviceGroupAdmin (
25742753 CreateDeviceGroupMixin ,
0 commit comments