Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ django.jQuery(function ($) {
resetTemplateOptions();
var enabledTemplates = [],
sortedm2mUl = $("ul.sortedm2m-items:first"),
sortedm2mPrefixUl = $("ul.sortedm2m-items:last");
sortedm2mPrefixUl = $("#config-empty ul.sortedm2m-items");

// Adds "li" elements for templates
Object.keys(data).forEach(function (templateId, index) {
Expand Down
100 changes: 98 additions & 2 deletions openwisp_controller/config/tests/test_selenium.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import time

from django.contrib.auth.models import Group, Permission
from django.contrib.staticfiles.testing import StaticLiveServerTestCase
from django.test import tag
from django.urls.base import reverse
Expand Down Expand Up @@ -40,9 +41,17 @@ def _verify_templates_visibility(self, hidden=None, visible=None):
hidden = hidden or []
visible = visible or []
for template in hidden:
self.wait_for_invisibility(By.XPATH, f'//*[@value="{template.id}"]')
self.wait_for_invisibility(
By.XPATH,
f'//ul[contains(@class,"sortedm2m-items")]'
f'//input[@value="{template.id}"]',
)
for template in visible:
self.wait_for_visibility(By.XPATH, f'//*[@value="{template.id}"]')
self.wait_for_visibility(
By.XPATH,
f'//ul[contains(@class,"sortedm2m-items")]'
f'//input[@value="{template.id}"]',
)


@tag("selenium_tests")
Expand Down Expand Up @@ -373,6 +382,93 @@ def test_add_remove_templates(self):
self.assertEqual(config.templates.count(), 0)
self.assertEqual(config.status, "modified")

def test_relevant_templates_duplicates(self):
"""
Test that a user with specific permissions can see shared templates
properly. Verifies that:
1. User with custom group permissions can access the admin
2. Multiple shared templates are displayed correctly
3. Each template appears only once in the sortedm2m list
"""
# Define permission codenames for the custom group
permission_codenames = [
"view_group",
"change_config",
"view_config",
"add_device",
"change_device",
"delete_device",
"view_device",
"view_devicegroup",
"view_template",
]
# Create a custom group with the specified permissions
permissions = Permission.objects.filter(codename__in=permission_codenames)
custom_group, _ = Group.objects.get_or_create(name="Custom Operator")
custom_group.permissions.set(permissions)
# Create a user and assign the custom group
user = self._create_user(
username="limited_user",
password="testpass123",
email="limited@test.com",
is_staff=True,
)
user.groups.add(custom_group)
org = self._get_org()
self._create_org_user(user=user, organization=org, is_admin=True)
# Create multiple shared templates (organization=None)
template1 = self._create_template(
name="Shared Template 1", organization=None, default=True
)
template2 = self._create_template(name="Shared Template 2", organization=None)
device = self._create_config(organization=org).device
# Login as the limited user
self.login(username="limited_user", password="testpass123")
# Navigate using Selenium
self.open(
reverse("admin:config_device_change", args=[device.id]) + "#config-group"
)
self.hide_loading_overlay()
with self.subTest(
"Regression precondition: empty Config inline is not rendered"
):
self.assertFalse(self.web_driver.find_elements(By.ID, "config-empty"))

with self.subTest("All shared templates should be visible"):
self._verify_templates_visibility(visible=[template1, template2])

with self.subTest("Verify sortedm2m list has exactly 2 template items"):
# Check that ul.sortedm2m-items.sortedm2m.ui-sortable has exactly 2 children
# with .sortedm2m-item class
sortedm2m_items = self.find_elements(
by=By.CSS_SELECTOR,
value="ul.sortedm2m-items.sortedm2m.ui-sortable > li.sortedm2m-item",
)
self.assertEqual(
len(sortedm2m_items),
2,
(
"Expected exactly 2 template items in sortedm2m list,"
f" found {len(sortedm2m_items)}"
),
)

with self.subTest(
"Verify checkbox inputs are rendered with expected attributes"
):
for idx, template_id in enumerate([template1.id, template2.id]):
checkbox = self.find_element(
by=By.ID, value=f"id_config-templates_{idx}"
)
self.assertEqual(checkbox.get_attribute("value"), str(template_id))
self.assertEqual(checkbox.get_attribute("data-required"), "false")

with self.subTest("Save operation completes successfully"):
# Scroll to the top of the page to ensure the save button is visible
self.web_driver.execute_script("window.scrollTo(0, 0);")
self.find_element(by=By.NAME, value="_save").click()
self.wait_for_presence(By.CSS_SELECTOR, ".messagelist .success", timeout=5)


@tag("selenium_tests")
class TestDeviceGroupAdmin(
Expand Down
6 changes: 6 additions & 0 deletions openwisp_controller/geo/tests/test_admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ def setUp(self):
"""override TestAdminMixin.setUp"""
pass

def _get_location_add_params(self, **kwargs):
params = super()._get_location_add_params(**kwargs)
if "organization" not in kwargs:
params["organization"] = self._get_org().id
return params

def _create_multitenancy_test_env(self, vpn=False):
org1 = self._create_organization(name="test1org")
org2 = self._create_organization(name="test2org")
Expand Down
6 changes: 5 additions & 1 deletion openwisp_controller/geo/tests/test_selenium.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,11 @@ def setUp(self):
def test_unsaved_changes_readonly(self):
self.login()
ol = self._create_object_location()
path = reverse("admin:config_device_change", args=[ol.device.id])
path = reverse(
f"admin:{self.object_model._meta.app_label}_"
f"{self.object_model._meta.model_name}_change",
args=[ol.device.id],
)

with self.subTest("Alert should not be displayed without any change"):
self.open(path)
Expand Down
Loading