|
1 | 1 | import os |
2 | 2 | import time |
3 | 3 |
|
| 4 | +from django.contrib.auth.models import Group, Permission |
4 | 5 | from django.contrib.staticfiles.testing import StaticLiveServerTestCase |
5 | 6 | from django.test import tag |
6 | 7 | from django.urls.base import reverse |
@@ -45,9 +46,17 @@ def _verify_templates_visibility(self, hidden=None, visible=None): |
45 | 46 | hidden = hidden or [] |
46 | 47 | visible = visible or [] |
47 | 48 | for template in hidden: |
48 | | - self.wait_for_invisibility(By.XPATH, f'//*[@value="{template.id}"]') |
| 49 | + self.wait_for_invisibility( |
| 50 | + By.XPATH, |
| 51 | + f'//ul[contains(@class,"sortedm2m-items")]' |
| 52 | + f'//input[@value="{template.id}"]', |
| 53 | + ) |
49 | 54 | for template in visible: |
50 | | - self.wait_for_visibility(By.XPATH, f'//*[@value="{template.id}"]') |
| 55 | + self.wait_for_visibility( |
| 56 | + By.XPATH, |
| 57 | + f'//ul[contains(@class,"sortedm2m-items")]' |
| 58 | + f'//input[@value="{template.id}"]', |
| 59 | + ) |
51 | 60 |
|
52 | 61 |
|
53 | 62 | @tag("selenium_tests") |
@@ -389,6 +398,96 @@ def test_add_remove_templates(self): |
389 | 398 | self.assertEqual(config.templates.count(), 0) |
390 | 399 | self.assertEqual(config.status, "modified") |
391 | 400 |
|
| 401 | + def test_relevant_templates_duplicates(self): |
| 402 | + """ |
| 403 | + Test that a user with specific permissions can see shared templates |
| 404 | + properly. Verifies that: |
| 405 | + 1. User with custom group permissions can access the admin |
| 406 | + 2. Multiple shared templates are displayed correctly |
| 407 | + 3. Each template appears only once in the sortedm2m list |
| 408 | + 4. Default template is automatically selected |
| 409 | + """ |
| 410 | + # Define permission codenames for the custom group |
| 411 | + permission_codenames = [ |
| 412 | + "view_group", |
| 413 | + "change_config", |
| 414 | + "view_config", |
| 415 | + "add_device", |
| 416 | + "change_device", |
| 417 | + "delete_device", |
| 418 | + "view_device", |
| 419 | + "view_devicegroup", |
| 420 | + "view_template", |
| 421 | + ] |
| 422 | + # Create a custom group with the specified permissions |
| 423 | + permissions = Permission.objects.filter(codename__in=permission_codenames) |
| 424 | + custom_group, _ = Group.objects.get_or_create(name="Custom Operator") |
| 425 | + custom_group.permissions.set(permissions) |
| 426 | + # Create a user and assign the custom group |
| 427 | + user = self._create_user( |
| 428 | + username="limited_user", |
| 429 | + password="testpass123", |
| 430 | + email="limited@test.com", |
| 431 | + is_staff=True, |
| 432 | + ) |
| 433 | + user.groups.add(custom_group) |
| 434 | + org = self._get_org() |
| 435 | + self._create_org_user(user=user, organization=org, is_admin=True) |
| 436 | + # Create multiple shared templates (organization=None) |
| 437 | + template1 = self._create_template( |
| 438 | + name="Shared Template 1", organization=None, default=True |
| 439 | + ) |
| 440 | + template2 = self._create_template(name="Shared Template 2", organization=None) |
| 441 | + device = self._create_config(organization=org).device |
| 442 | + # Login as the limited user |
| 443 | + self.login(username="limited_user", password="testpass123") |
| 444 | + # Navigate using Selenium |
| 445 | + self.open( |
| 446 | + reverse("admin:config_device_change", args=[device.id]) + "#config-group" |
| 447 | + ) |
| 448 | + self.hide_loading_overlay() |
| 449 | + with self.subTest("All shared templates should be visible"): |
| 450 | + self._verify_templates_visibility(visible=[template1, template2]) |
| 451 | + |
| 452 | + with self.subTest("Verify sortedm2m list has exactly 2 template items"): |
| 453 | + # Check that ul.sortedm2m-items.sortedm2m.ui-sortable has exactly 2 children |
| 454 | + # with .sortedm2m-item class |
| 455 | + sortedm2m_items = self.find_elements( |
| 456 | + by=By.CSS_SELECTOR, |
| 457 | + value="ul.sortedm2m-items.sortedm2m.ui-sortable > li.sortedm2m-item", |
| 458 | + ) |
| 459 | + self.assertEqual( |
| 460 | + len(sortedm2m_items), |
| 461 | + 2, |
| 462 | + ( |
| 463 | + f"Expected exactly 2 template items in sortedm2m list, found {len(sortedm2m_items)}" |
| 464 | + ), |
| 465 | + ) |
| 466 | + |
| 467 | + with self.subTest( |
| 468 | + "Verify checkbox HTML elements are present in page source with correct attributes" |
| 469 | + ): |
| 470 | + page_source = self.web_driver.page_source |
| 471 | + # Verify HTML element strings for each template |
| 472 | + for idx, template_id in enumerate([template1.id, template2.id], start=0): |
| 473 | + html_element = ( |
| 474 | + f'<input type="checkbox" value="{template_id}" ' |
| 475 | + f'id="id_config-templates_{idx}" class="sortedm2m" data-required="false"' |
| 476 | + ) |
| 477 | + self.assertIn( |
| 478 | + html_element, |
| 479 | + page_source, |
| 480 | + ( |
| 481 | + f"Expected checkbox HTML for template {template_id} not found in page source" |
| 482 | + ), |
| 483 | + ) |
| 484 | + |
| 485 | + with self.subTest("Save operation completes successfully"): |
| 486 | + # Scroll to the top of the page to ensure the save button is visible |
| 487 | + self.web_driver.execute_script("window.scrollTo(0, 0);") |
| 488 | + self.find_element(by=By.NAME, value="_save").click() |
| 489 | + self.wait_for_presence(By.CSS_SELECTOR, ".messagelist .success", timeout=5) |
| 490 | + |
392 | 491 |
|
393 | 492 | @tag("selenium_tests") |
394 | 493 | class TestDeviceGroupAdmin( |
|
0 commit comments