Skip to content

Commit cf8cdda

Browse files
committed
test: fix non-deterministic assertions and add docstrings to complex tests
1 parent e88169e commit cf8cdda

1 file changed

Lines changed: 58 additions & 18 deletions

File tree

netbox_interface_name_rules/tests/test_coverage_extras.py

Lines changed: 58 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,12 @@ class UtilsModulePathFalseTest(TestCase):
311311
"""Test supports_module_path() returns False when MODULE_PATH_TOKEN is missing."""
312312

313313
def test_returns_false_when_token_missing(self):
314-
"""supports_module_path() catches ImportError and returns False (lines 16-17)."""
314+
"""supports_module_path() returns False when MODULE_PATH_TOKEN is absent.
315+
316+
Temporarily removes the attribute from dcim.constants (if present) to
317+
trigger the ImportError branch in supports_module_path, then restores it.
318+
Asserts False regardless of whether the attribute existed beforehand.
319+
"""
315320
import dcim.constants as dc
316321

317322
from netbox_interface_name_rules.utils import supports_module_path
@@ -322,11 +327,7 @@ def test_returns_false_when_token_missing(self):
322327
if had_attr:
323328
delattr(dc, "MODULE_PATH_TOKEN")
324329
result = supports_module_path()
325-
if had_attr:
326-
self.assertFalse(result)
327-
else:
328-
# Already False before this test; just verify the type
329-
self.assertIsInstance(result, bool)
330+
self.assertFalse(result)
330331
finally:
331332
if had_attr:
332333
dc.MODULE_PATH_TOKEN = original
@@ -441,22 +442,32 @@ def _toggle_url(self, pk):
441442
)
442443

443444
def test_toggle_non_ajax_no_permission_raises_403(self):
444-
"""Non-AJAX POST from user without change permission returns 403 (lines 430-432)."""
445+
"""Non-AJAX POST from user without change permission returns 403.
446+
447+
NetBox's ObjectView dispatch checks permissions via LoginRequiredMixin and
448+
raises PermissionDenied when the user lacks change_interfacenamerule, which
449+
Django converts to a 403 response.
450+
"""
445451
User.objects.create_user(username="noperm_tog_user2", password="testpass123")
446452
self.client.login(username="noperm_tog_user2", password="testpass123")
447453
url = self._toggle_url(self.rule.pk)
448454
response = self.client.post(url)
449-
self.assertIn(response.status_code, [403, 302])
455+
self.assertEqual(response.status_code, 403)
450456

451457
def test_post_apply_apply_error_shows_error_message(self):
452-
"""POST apply when apply_rule_to_existing raises shows error message (lines 413-415)."""
458+
"""POST apply when apply_rule_to_existing raises shows error message and redirects.
459+
460+
Patches engine.apply_rule_to_existing to raise ValueError, verifying the
461+
view catches the exception (lines 413-415), logs it, adds an error message,
462+
and still issues the 302 redirect back to the apply detail page.
463+
"""
453464
url = reverse(
454465
"plugins:netbox_interface_name_rules:interfacenamerule_apply_detail",
455466
kwargs={"pk": self.rule.pk},
456467
)
457468
with patch("netbox_interface_name_rules.engine.apply_rule_to_existing", side_effect=ValueError("bad template")):
458469
response = self.client.post(url, {"action": "apply", "interface_ids": [str(self.rule.pk)]})
459-
self.assertIn(response.status_code, [302])
470+
self.assertEqual(response.status_code, 302)
460471

461472
def test_apply_detail_get_value_error_shows_error(self):
462473
"""GET apply detail when find_interfaces_for_rule raises ValueError shows error (lines 357-360)."""
@@ -638,7 +649,13 @@ def setUpTestData(cls):
638649
cls.bad_regex_rule.save()
639650

640651
def test_invalid_regex_pattern_is_skipped_not_raised(self):
641-
"""_find_regex_match silently skips rules with bad regex (lines 355-356)."""
652+
"""_find_regex_match silently skips rules whose module_type_pattern is not valid regex.
653+
654+
The bad-pattern rule is inserted directly via save() to bypass clean()
655+
validation. _find_regex_match wraps re.fullmatch in a try/except re.error
656+
so an invalid pattern just moves on to the next candidate instead of
657+
propagating the exception to the caller (lines 355-356).
658+
"""
642659
from netbox_interface_name_rules.engine import _find_regex_match
643660

644661
candidates = [(None, None, None)]
@@ -897,7 +914,11 @@ def _url(self):
897914
return reverse("plugins:netbox_interface_name_rules:interfacenamerule_test")
898915

899916
def test_save_rule_with_device_type_filters_scope(self):
900-
"""POST save_rule with device_type set applies scope filter (line 203)."""
917+
"""POST save_rule with device_type set applies scope filter and redirects.
918+
919+
Verifies that when both module_type and device_type are provided, the view
920+
looks up an existing matching rule (line 203) and redirects to it (302).
921+
"""
901922
data = {
902923
"name_template": "et-0/0/{bay_position}",
903924
"channel_count": "0",
@@ -907,8 +928,7 @@ def test_save_rule_with_device_type_filters_scope(self):
907928
"action": "save_rule",
908929
}
909930
response = self.client.post(self._url(), data)
910-
# Should redirect (either to existing rule or to add page)
911-
self.assertIn(response.status_code, [302])
931+
self.assertEqual(response.status_code, 302)
912932

913933
def test_save_rule_no_match_with_device_type_redirects_to_add_with_pk(self):
914934
"""POST save_rule with unmatched scope redirects to add page with device_type param (line 227)."""
@@ -1002,7 +1022,7 @@ def test_post_background_success_shows_success_message(self):
10021022
mock_job.pk = 42
10031023
with patch("netbox_interface_name_rules.jobs.ApplyRuleJob.enqueue", return_value=mock_job):
10041024
response = self.client.post(url, {"action": "background"})
1005-
self.assertIn(response.status_code, [302])
1025+
self.assertEqual(response.status_code, 302)
10061026

10071027

10081028
# ---------------------------------------------------------------------------
@@ -1035,7 +1055,15 @@ def setUpTestData(cls):
10351055
cls.bay = ModuleBay.objects.get(device=cls.device, name="NBBay 0")
10361056

10371057
def test_module_with_null_bay_is_skipped(self):
1038-
"""_apply_rules_for_device_deferred skips module when module_bay is None (line 214)."""
1058+
"""_apply_rules_for_device_deferred continues past modules with module_bay=None.
1059+
1060+
The signal function iterates over all modules on a device and calls
1061+
apply_interface_name_rules for each one. When module_bay is None (e.g. due
1062+
to a data inconsistency), the loop must skip that entry via ``continue``
1063+
rather than passing None to the engine. A FakeModule with module_bay=None
1064+
is injected via a patch on Module.objects.filter so the DB doesn't need
1065+
to hold inconsistent data.
1066+
"""
10391067
from netbox_interface_name_rules.signals import _apply_rules_for_device_deferred
10401068

10411069
module = Module.objects.create(device=self.device, module_bay=self.bay, module_type=self.module_type)
@@ -1077,7 +1105,13 @@ def setUpTestData(cls):
10771105
cls.bay = ModuleBay.objects.get(device=cls.device, name="CRBay 0")
10781106

10791107
def test_valueerror_in_template_sets_error_name(self):
1080-
"""_channel_rule_entry uses error name when template raises ValueError (lines 618-619)."""
1108+
"""_channel_rule_entry stores an ``<error: …>`` placeholder when evaluate_name_template raises.
1109+
1110+
evaluate_name_template is patched to raise ValueError for every call so
1111+
the loop that builds expected_names catches it and collapses to a single
1112+
error-placeholder entry (lines 618-619). The result dict is then returned
1113+
because the placeholder is not in existing_names.
1114+
"""
10811115
from netbox_interface_name_rules.engine import _channel_rule_entry
10821116

10831117
rule = InterfaceNameRule(
@@ -1147,7 +1181,13 @@ def setUpTestData(cls):
11471181
cls.bay = ModuleBay.objects.get(device=cls.device, name="CLBay 0")
11481182

11491183
def test_limit_reached_returns_true(self):
1150-
"""_process_channel_module returns should_stop=True when limit is reached (line 645)."""
1184+
"""_process_channel_module returns should_stop=True when the result limit is hit.
1185+
1186+
``results`` is pre-seeded with one entry so that after _channel_rule_entry
1187+
adds a new entry the total reaches ``limit=1``, triggering the early-stop
1188+
path (line 644-645). The module_qs mock with count=0 ensures no extra
1189+
work is counted from the remaining queryset.
1190+
"""
11511191
from netbox_interface_name_rules.engine import _process_channel_module
11521192

11531193
rule = InterfaceNameRule.objects.create(

0 commit comments

Comments
 (0)