Skip to content

Commit c7f1cbd

Browse files
authored
[fix] One credential can be added to a device only once #795
Added unique_together constraint in DeviceConnection, device and credentials should be unique. Fixes #795
1 parent c540f93 commit c7f1cbd

File tree

5 files changed

+42
-8
lines changed

5 files changed

+42
-8
lines changed

openwisp_controller/connection/base/models.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,7 @@ class AbstractDeviceConnection(ConnectorMixin, TimeStampedEditableModel):
254254
class Meta:
255255
verbose_name = _('Device connection')
256256
verbose_name_plural = _('Device connections')
257+
unique_together = (('device', 'credentials'),)
257258
abstract = True
258259

259260
def __init__(self, *args, **kwargs):
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Generated by Django 3.2.20 on 2023-08-24 12:35
2+
3+
from django.conf import settings
4+
from django.db import migrations
5+
6+
7+
class Migration(migrations.Migration):
8+
9+
dependencies = [
10+
migrations.swappable_dependency(settings.CONFIG_DEVICE_MODEL),
11+
('connection', '0007_command'),
12+
]
13+
14+
operations = [
15+
migrations.AlterUniqueTogether(
16+
name='deviceconnection',
17+
unique_together={('device', 'credentials')},
18+
),
19+
]

openwisp_controller/connection/tests/test_api.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from .utils import CreateCommandMixin, CreateConnectionsMixin
1818

1919
Command = load_model('connection', 'Command')
20+
DeviceConnection = load_model('connection', 'DeviceConnection')
2021
command_qs = Command.objects.order_by('-created')
2122
OrganizationUser = load_model('openwisp_users', 'OrganizationUser')
2223
Group = load_model('openwisp_users', 'Group')
@@ -423,7 +424,7 @@ def test_post_deviceconnection_list(self):
423424
'enabled': True,
424425
'failure_reason': '',
425426
}
426-
with self.assertNumQueries(11):
427+
with self.assertNumQueries(12):
427428
response = self.client.post(path, data, content_type='application/json')
428429
self.assertEqual(response.status_code, 201)
429430

@@ -436,7 +437,7 @@ def test_post_deviceconenction_with_no_config_device(self):
436437
'enabled': True,
437438
'failure_reason': '',
438439
}
439-
with self.assertNumQueries(11):
440+
with self.assertNumQueries(12):
440441
response = self.client.post(path, data, content_type='application/json')
441442
error_msg = '''
442443
the update strategy can be determined automatically only if
@@ -468,7 +469,7 @@ def test_put_devceconnection_detail(self):
468469
'enabled': False,
469470
'failure_reason': '',
470471
}
471-
with self.assertNumQueries(13):
472+
with self.assertNumQueries(14):
472473
response = self.client.put(path, data, content_type='application/json')
473474
self.assertEqual(response.status_code, 200)
474475
self.assertEqual(
@@ -482,7 +483,7 @@ def test_patch_deviceconnectoin_detail(self):
482483
path = reverse('connection_api:deviceconnection_detail', args=(d1, dc.pk))
483484
self.assertEqual(dc.update_strategy, app_settings.UPDATE_STRATEGIES[0][0])
484485
data = {'update_strategy': app_settings.UPDATE_STRATEGIES[1][0]}
485-
with self.assertNumQueries(12):
486+
with self.assertNumQueries(13):
486487
response = self.client.patch(path, data, content_type='application/json')
487488
self.assertEqual(response.status_code, 200)
488489
self.assertEqual(
@@ -501,8 +502,8 @@ def test_bearer_authentication(self):
501502
self.client.logout()
502503
token = self._obtain_auth_token(username='admin', password='tester')
503504
credentials = self._create_credentials(auto_add=True)
504-
device_conn = self._create_device_connection(credentials=credentials)
505-
device = device_conn.device
505+
device = self._create_config(organization=credentials.organization).device
506+
device_conn = device.deviceconnection_set.first()
506507

507508
with self.subTest('Test CredentialListCreateView'):
508509
response = self.client.get(

openwisp_controller/connection/tests/test_models.py

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,18 @@ def test_device_connection_credential_org_validation(self):
328328
else:
329329
self.fail('ValidationError not raised')
330330

331+
def test_device_connection_same_credential_twice(self):
332+
device_conn = self._create_device_connection()
333+
with self.assertRaises(ValidationError) as context_manager:
334+
device_conn = DeviceConnection(
335+
device=device_conn.device, credentials=device_conn.credentials
336+
)
337+
device_conn.full_clean()
338+
self.assertEqual(
339+
context_manager.exception.message_dict['__all__'][0],
340+
'Device connection with this Device and Credentials already exists.',
341+
)
342+
331343
def test_auto_add_to_new_device(self):
332344
c = self._create_credentials(auto_add=True, organization=None)
333345
self._create_credentials(name='cred2', auto_add=False, organization=None)
@@ -1049,12 +1061,12 @@ def test_chunk_size(self):
10491061
organization=org, name='device3', mac_address='33:33:33:33:33:33'
10501062
)
10511063
)
1052-
with self.assertNumQueries(28):
1064+
with self.assertNumQueries(31):
10531065
credential = self._create_credentials(auto_add=True, organization=org)
10541066
self.assertEqual(credential.deviceconnection_set.count(), 3)
10551067

10561068
with mock.patch.object(Credentials, 'chunk_size', 2):
1057-
with self.assertNumQueries(30):
1069+
with self.assertNumQueries(33):
10581070
credential = self._create_credentials(
10591071
name='Mocked Credential', auto_add=True, organization=org
10601072
)

tests/openwisp2/sample_connection/migrations/0001_initial.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ class Migration(migrations.Migration):
195195
options={
196196
'verbose_name': 'Device connection',
197197
'verbose_name_plural': 'Device connections',
198+
'unique_together': {('device', 'credentials')},
198199
'abstract': False,
199200
},
200201
bases=(

0 commit comments

Comments
 (0)