Skip to content

Commit 0b911a6

Browse files
committed
[tests] Improved connection tests
- mock sleep to avoid slowing down test execution - removed repetition - added TestModelsTransaction to saple_connection app
1 parent 077face commit 0b911a6

File tree

4 files changed

+73
-79
lines changed

4 files changed

+73
-79
lines changed

openwisp_controller/connection/tasks.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import logging
2-
from time import sleep
2+
import time
33

44
from celery import shared_task
55
from django.core.exceptions import ObjectDoesNotExist
@@ -17,7 +17,7 @@ def update_config(device_id):
1717
"""
1818
# wait for the saving operations of this device to complete
1919
# (there may be multiple ones happening at the same time)
20-
sleep(2)
20+
time.sleep(2)
2121
# avoid repeating the operation multiple times
2222
device = Device.objects.select_related('config').get(pk=device_id)
2323
try:

openwisp_controller/connection/tests/test_models.py

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import paramiko
55
from django.core.exceptions import ValidationError
6-
from django.test import TestCase
6+
from django.test import TestCase, TransactionTestCase
77
from swapper import load_model
88

99
from openwisp_users.models import Group, Organization
@@ -19,11 +19,27 @@
1919
Credentials = load_model('connection', 'Credentials')
2020
DeviceConnection = 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')

openwisp_controller/connection/tests/test_transaction_blocks.py

Lines changed: 0 additions & 54 deletions
This file was deleted.

tests/openwisp2/sample_connection/tests.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
from openwisp_controller.connection.tests.test_models import (
33
TestModels as BaseTestModels,
44
)
5+
from openwisp_controller.connection.tests.test_models import (
6+
TestModelsTransaction as BaseTestModelsTransaction,
7+
)
58
from openwisp_controller.connection.tests.test_ssh import TestSsh as BaseTestSsh
69

710

@@ -14,10 +17,15 @@ class TestModels(BaseTestModels):
1417
app_label = 'sample_connection'
1518

1619

20+
class TestModelsTransaction(BaseTestModelsTransaction):
21+
app_label = 'sample_connection'
22+
23+
1724
class TestSsh(BaseTestSsh):
1825
pass
1926

2027

2128
del BaseTestAdmin
2229
del BaseTestModels
30+
del BaseTestModelsTransaction
2331
del BaseTestSsh

0 commit comments

Comments
 (0)