Skip to content

Commit 679d8bc

Browse files
committed
[tests] Added tests
1 parent e598209 commit 679d8bc

5 files changed

Lines changed: 142 additions & 24 deletions

File tree

openwisp_users/multitenancy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def _has_org_permission(self, request, obj, perm_func):
6666
associated with specific organizations.
6767
"""
6868
perm = perm_func(request, obj)
69-
if not request.user.is_superuser and obj and hasattr(obj, 'organization_id'):
69+
if not request.user.is_superuser and obj and hasattr(obj, "organization_id"):
7070
perm = perm and (
7171
obj.organization_id
7272
and str(obj.organization_id) in request.user.organizations_managed

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
)

openwisp_users/views.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,5 +43,5 @@ def get_empty_label(self):
4343

4444
def get_allow_null(self):
4545
if self.object_list.model == Organization:
46-
return self.request.user.is_superuser or self.request.GET.get('is_filter')
46+
return self.request.user.is_superuser or self.request.GET.get("is_filter")
4747
return super().get_allow_null()

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)