Skip to content

Commit fb68e26

Browse files
committed
[tests] Added tests
1 parent 2116989 commit fb68e26

File tree

3 files changed

+140
-22
lines changed

3 files changed

+140
-22
lines changed

openwisp_users/tests/utils.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
from datetime import date
22

3+
import django
34
from django.contrib.auth import get_user_model
45
from django.contrib.auth.models import Permission
56
from django.urls import reverse
67
from swapper import load_model
78

9+
from openwisp_users.multitenancy import SHARED_SYSTEMWIDE_LABEL
10+
811
Organization = load_model('openwisp_users', 'Organization')
912
OrganizationOwner = load_model('openwisp_users', 'OrganizationOwner')
1013
OrganizationUser = load_model('openwisp_users', 'OrganizationUser')
@@ -236,9 +239,82 @@ def _test_recoverlist_operator_403(self, app_label, model_label):
236239
)
237240
self.assertEqual(response.status_code, 403)
238241

239-
def _get_autocomplete_view_path(self, app_label, model_name, field_name):
242+
def _test_org_admin_create_shareable_object(
243+
self, path, payload, model, expected_count=0, error_message=None, user=None
244+
):
245+
"""
246+
Verifies a non-superuser cannot create a shareable object
247+
"""
248+
if not user:
249+
user = self._create_administrator(organizations=[self._get_org()])
250+
self.client.force_login(user)
251+
response = self.client.post(
252+
path,
253+
data=payload,
254+
follow=True,
255+
)
256+
error_message = error_message or (
257+
'<div class="form-row errors field-organization">\n'
258+
' <ul class="errorlist"{}>'
259+
'<li>This field is required.</li></ul>'
260+
).format(' id="id_organization_error"' if django.VERSION >= (5, 2) else '')
261+
self.assertContains(response, error_message)
262+
self.assertEqual(model.objects.count(), expected_count)
263+
264+
def _test_org_admin_view_shareable_object(
265+
self, path, user=None, expected_element=None
266+
):
267+
"""
268+
Verifies a non-superuser can view a shareable object
269+
"""
270+
if not user:
271+
user = self._create_administrator(organizations=[self._get_org()])
272+
self.client.force_login(user)
273+
response = self.client.get(path, follow=True)
274+
self.assertEqual(response.status_code, 200)
275+
if not expected_element:
276+
expected_element = (
277+
'<div class="form-row field-organization">\n\n\n<div>\n\n'
278+
'<div class="flex-container">\n\n'
279+
'<label>Organization:</label>\n\n'
280+
'<div class="readonly">-</div>\n\n\n'
281+
'</div>\n\n</div>\n\n\n</div>'
282+
)
283+
self.assertContains(response, expected_element, html=True)
284+
285+
def _test_object_organization_fk_autocomplete_view(
286+
self,
287+
model,
288+
):
289+
app_label = model._meta.app_label
290+
model_name = model._meta.model_name
291+
path = self._get_autocomplete_view_path(app_label, model_name, 'organization')
292+
org = self._get_org()
293+
admin = User.objects.filter(is_superuser=True).first()
294+
if not admin:
295+
admin = self._create_admin()
296+
org_admin = self._create_administrator(organizations=[org])
297+
298+
with self.subTest('Org admin should only see their own org'):
299+
self.client.force_login(org_admin)
300+
response = self.client.get(path)
301+
self.assertEqual(response.status_code, 200)
302+
self.assertContains(response, org.name)
303+
self.assertNotContains(response, SHARED_SYSTEMWIDE_LABEL)
304+
305+
with self.subTest('Superuser should see all orgs and shared label'):
306+
self.client.force_login(admin)
307+
response = self.client.get(path)
308+
self.assertEqual(response.status_code, 200)
309+
self.assertContains(response, org.name)
310+
self.assertContains(response, SHARED_SYSTEMWIDE_LABEL)
311+
312+
def _get_autocomplete_view_path(
313+
self, app_label, model_name, field_name, is_filter=False
314+
):
240315
path = reverse('admin:ow-auto-filter')
241316
return (
242317
f'{path}?app_label={app_label}'
243318
f'&model_name={model_name}&field_name={field_name}'
319+
'{}'.format('&is_filter=true' if is_filter else '')
244320
)

tests/testapp/tests/test_admin.py

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import os
22

3-
import django
43
from django.contrib.auth import get_user_model
54
from django.test import TestCase
65
from django.urls import reverse
76
from swapper import load_model
87

9-
from openwisp_users.tests.utils import TestOrganizationMixin
8+
from openwisp_users.tests.utils import TestMultitenantAdminMixin, TestOrganizationMixin
109

1110
from ..models import Template
1211

@@ -45,30 +44,42 @@ def test_accounts_login(self):
4544
)
4645

4746

48-
class TestTemplateAdmin(TestOrganizationMixin, TestCase):
47+
class TestTemplateAdmin(TestMultitenantAdminMixin, TestCase):
48+
def _create_template(self, **kwargs):
49+
if 'organization' not in kwargs:
50+
kwargs['organization'] = self._get_org()
51+
options = {
52+
'name': 'test-template',
53+
}
54+
options.update(kwargs)
55+
template = Template(**options)
56+
template.full_clean()
57+
template.save()
58+
return template
59+
4960
def test_org_admin_create_shareable_template(self):
50-
administrator = self._create_administrator()
51-
self.client.force_login(administrator)
52-
response = self.client.post(
61+
self._test_org_admin_create_shareable_object(
5362
reverse('admin:testapp_template_add'),
54-
data={
63+
payload={
5564
'name': 'test-template',
5665
'organization': '',
5766
},
58-
follow=True,
67+
model=Template,
5968
)
60-
self.assertContains(
61-
response,
62-
(
63-
'<div class="form-row errors field-organization">\n'
64-
' <ul class="errorlist"{}>'
65-
'<li>This field is required.</li></ul>'
66-
).format(' id="id_organization_error"' if django.VERSION >= (5, 2) else ''),
69+
70+
def test_template_organization_autocomplete_view(self):
71+
self._test_object_organization_fk_autocomplete_view(
72+
Template,
73+
)
74+
75+
def test_org_admin_view_shared_template(self):
76+
template = self._create_template(organization=None)
77+
self._test_org_admin_view_shareable_object(
78+
reverse('admin:testapp_template_change', args=(template.id,)),
6779
)
68-
self.assertEqual(Template.objects.count(), 0)
6980

7081
def test_superuser_create_shareable_template(self):
71-
admin = self._create_admin()
82+
admin = self._get_admin()
7283
self.client.force_login(admin)
7384
response = self.client.post(
7485
reverse('admin:testapp_template_add'),

tests/testapp/tests/test_selenium.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,17 @@ def setUp(self):
2424
)
2525

2626
def _test_multitenant_autocomplete_org_field(
27-
self, username, password, path, visible, hidden
27+
self,
28+
username,
29+
password,
30+
path,
31+
visible,
32+
hidden,
33+
select2_selector='#select2-id_organization-container',
2834
):
2935
self.login(username=username, password=password)
3036
self.open(path)
31-
self.web_driver.find_element(
32-
By.CSS_SELECTOR, '#select2-id_organization-container'
33-
).click()
37+
self.web_driver.find_element(By.CSS_SELECTOR, select2_selector).click()
3438
WebDriverWait(self.web_driver, 2).until(
3539
EC.invisibility_of_element_located(
3640
(By.CSS_SELECTOR, '.select2-results__option.loading-results')
@@ -143,3 +147,30 @@ def test_shelf_add_form_organization_field(self):
143147
self.assertEqual(len(org_select.all_selected_options), 1)
144148
self.assertEqual(org_select.first_selected_option.text, org1.name)
145149
self.logout()
150+
151+
def test_organization_autocomplete_filter(self):
152+
"""
153+
The autocomplete_filter should show option to filter
154+
shared objects to non-superuser.
155+
"""
156+
path = reverse('admin:testapp_shelf_changelist')
157+
org1 = self._create_org(name='org1')
158+
administrator = self._create_administrator(
159+
organizations=[org1], username='tester', password='tester'
160+
)
161+
administrator.user_permissions.add(
162+
*Permission.objects.filter(
163+
Q(codename__contains='shelf') | Q(codename='view_organization')
164+
).values_list('id', flat=True),
165+
)
166+
self._test_multitenant_autocomplete_org_field(
167+
path=path,
168+
username='tester',
169+
password='tester',
170+
visible=[org1.name, 'Shared systemwide (no organization)'],
171+
hidden=list(
172+
Organization.objects.exclude(id=org1.id).values_list('name', flat=True)
173+
),
174+
select2_selector='#select2-id-organization-dal-filter-container',
175+
)
176+
self.logout()

0 commit comments

Comments
 (0)