33
44import paramiko
55from django .core .exceptions import ValidationError
6- from django .test import TestCase
6+ from django .test import TestCase , TransactionTestCase
77from swapper import load_model
88
99from openwisp_users .models import Group , Organization
1919Credentials = load_model ('connection' , 'Credentials' )
2020DeviceConnection = load_model ('connection' , 'DeviceConnection' )
2121
22+ _connect_path = 'paramiko.SSHClient.connect'
23+ _exec_command_path = 'paramiko.SSHClient.exec_command'
2224
23- class TestModels (CreateConnectionsMixin , TestCase ):
25+
26+ class BaseTestModels (CreateConnectionsMixin ):
2427 app_label = 'connection'
25- _connect_path = 'paramiko.SSHClient.connect'
2628
29+ def _exec_command_return_value (
30+ self , stdin = '' , stdout = 'mocked' , stderr = '' , exit_code = 0
31+ ):
32+ stdin_ = mock .Mock ()
33+ stdout_ = mock .Mock ()
34+ stderr_ = mock .Mock ()
35+ stdin_ .read ().decode ('utf8' ).strip .return_value = stdin
36+ stdout_ .read ().decode ('utf8' ).strip .return_value = stdout
37+ stdout_ .channel .recv_exit_status .return_value = exit_code
38+ stderr_ .read ().decode ('utf8' ).strip .return_value = stderr
39+ return (stdin_ , stdout_ , stderr_ )
40+
41+
42+ class TestModels (BaseTestModels , TestCase ):
2743 def test_connection_str (self ):
2844 c = Credentials (name = 'Dev Key' , connector = app_settings .CONNECTORS [0 ][0 ])
2945 self .assertIn (c .name , str (c ))
@@ -98,7 +114,7 @@ def test_ssh_connect_failure(self):
98114 dc = self ._create_device_connection (credentials = ckey )
99115 dc .device .last_ip = None
100116 dc .device .save ()
101- with mock .patch (self . _connect_path ) as mocked_connect :
117+ with mock .patch (_connect_path ) as mocked_connect :
102118 mocked_connect .side_effect = Exception ('Authentication failed.' )
103119 dc .connect ()
104120 mocked_connect .assert_called_once ()
@@ -243,26 +259,12 @@ def test_auto_add_device_missing_config(self):
243259 self ._create_credentials (auto_add = True , organization = None )
244260 self .assertEqual (Credentials .objects .count (), 1 )
245261
246- _exec_command_path = 'paramiko.SSHClient.exec_command'
247-
248- def _exec_command_return_value (
249- self , stdin = '' , stdout = 'mocked' , stderr = '' , exit_code = 0
250- ):
251- stdin_ = mock .Mock ()
252- stdout_ = mock .Mock ()
253- stderr_ = mock .Mock ()
254- stdin_ .read ().decode ('utf8' ).strip .return_value = stdin
255- stdout_ .read ().decode ('utf8' ).strip .return_value = stdout
256- stdout_ .channel .recv_exit_status .return_value = exit_code
257- stderr_ .read ().decode ('utf8' ).strip .return_value = stderr
258- return (stdin_ , stdout_ , stderr_ )
259-
260262 @mock .patch (_connect_path )
261263 def test_ssh_exec_exit_code (self , * args ):
262264 ckey = self ._create_credentials_with_key (port = self .ssh_server .port )
263265 dc = self ._create_device_connection (credentials = ckey )
264266 dc .connector_instance .connect ()
265- with mock .patch (self . _exec_command_path ) as mocked :
267+ with mock .patch (_exec_command_path ) as mocked :
266268 mocked .return_value = self ._exec_command_return_value (exit_code = 1 )
267269 with self .assertRaises (Exception ):
268270 dc .connector_instance .exec_command ('trigger_command_not_found' )
@@ -274,7 +276,7 @@ def test_ssh_exec_timeout(self, *args):
274276 ckey = self ._create_credentials_with_key (port = self .ssh_server .port )
275277 dc = self ._create_device_connection (credentials = ckey )
276278 dc .connector_instance .connect ()
277- with mock .patch (self . _exec_command_path ) as mocked :
279+ with mock .patch (_exec_command_path ) as mocked :
278280 mocked .side_effect = socket .timeout ()
279281 with self .assertRaises (socket .timeout ):
280282 dc .connector_instance .exec_command ('trigger_timeout' )
@@ -286,7 +288,7 @@ def test_ssh_exec_exception(self, *args):
286288 ckey = self ._create_credentials_with_key (port = self .ssh_server .port )
287289 dc = self ._create_device_connection (credentials = ckey )
288290 dc .connector_instance .connect ()
289- with mock .patch (self . _exec_command_path ) as mocked :
291+ with mock .patch (_exec_command_path ) as mocked :
290292 mocked .side_effect = RuntimeError ('test' )
291293 with self .assertRaises (RuntimeError ):
292294 dc .connector_instance .exec_command ('trigger_exception' )
@@ -351,7 +353,45 @@ def test_device_connection_set_connector(self):
351353 self .assertFalse (hasattr (dc2 .connector_instance , 'IS_MODIFIED' ))
352354
353355 @mock .patch ('logging.Logger.warning' )
354- def test_update_config_task_resilient_to_failure (self , mocked ):
356+ @mock .patch ('time.sleep' )
357+ def test_update_config_task_resilient_to_failure (
358+ self , mocked_sleep , mocked_warning
359+ ):
355360 pk = self ._create_device ().pk
356361 update_config .delay (pk )
357- mocked .assert_called_with (f'Config with device id: { pk } does not exist' )
362+ mocked_warning .assert_called_with (f'Config with device id: { pk } does not exist' )
363+ mocked_sleep .assert_called_once ()
364+
365+
366+ class TestModelsTransaction (BaseTestModels , TransactionTestCase ):
367+ @mock .patch (_connect_path )
368+ @mock .patch ('time.sleep' )
369+ def test_device_config_update (self , mocked_sleep , mocked_connect ):
370+ org1 = self ._create_org (name = 'org1' )
371+ cred = self ._create_credentials_with_key (
372+ organization = org1 , port = self .ssh_server .port
373+ )
374+ device = self ._create_device (organization = org1 )
375+ update_strategy = app_settings .UPDATE_STRATEGIES [0 ][0 ]
376+ c = self ._create_config (device = device , status = 'applied' )
377+ self ._create_device_connection (
378+ device = device , credentials = cred , update_strategy = update_strategy
379+ )
380+ c .config = {
381+ 'interfaces' : [
382+ {
383+ 'name' : 'eth10' ,
384+ 'type' : 'ethernet' ,
385+ 'addresses' : [{'family' : 'ipv4' , 'proto' : 'dhcp' }],
386+ }
387+ ]
388+ }
389+ c .full_clean ()
390+
391+ with mock .patch (_exec_command_path ) as mocked_exec_command :
392+ mocked_exec_command .return_value = self ._exec_command_return_value ()
393+ c .save ()
394+ mocked_exec_command .assert_called_once ()
395+
396+ c .refresh_from_db ()
397+ self .assertEqual (c .status , 'applied' )
0 commit comments