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