|
1 | 1 | # SPDX-License-Identifier: Apache-2.0 |
2 | 2 | # Copyright (C) 2025 Marcin Zieba <marcinpsk@gmail.com> |
3 | | -"""Tests for utils, jobs, model properties, and API serializer edge-cases.""" |
| 3 | +"""Tests for jobs, model properties, and API serializer edge-cases.""" |
4 | 4 |
|
5 | 5 | from unittest.mock import MagicMock, patch |
6 | 6 |
|
|
9 | 9 | from django.test import TestCase |
10 | 10 |
|
11 | 11 | from netbox_interface_name_rules.models import InterfaceNameRule |
12 | | -from netbox_interface_name_rules.utils import supports_module_path |
13 | | - |
14 | | -# --------------------------------------------------------------------------- |
15 | | -# utils.py |
16 | | -# --------------------------------------------------------------------------- |
17 | | - |
18 | | - |
19 | | -class SupportModulePathTest(TestCase): |
20 | | - """Test the supports_module_path feature-detection helper.""" |
21 | | - |
22 | | - def test_returns_bool(self): |
23 | | - """supports_module_path() returns a boolean (True or False).""" |
24 | | - result = supports_module_path() |
25 | | - self.assertIsInstance(result, bool) |
26 | | - |
27 | 12 |
|
28 | 13 | # --------------------------------------------------------------------------- |
29 | 14 | # jobs.py |
@@ -742,31 +727,77 @@ def test_job_run_exception_reraises_and_logs(self): |
742 | 727 |
|
743 | 728 |
|
744 | 729 | # --------------------------------------------------------------------------- |
745 | | -# utils.py — supports_module_path ImportError path (lines 16-17) |
| 730 | +# Model csv_headers / to_csv() — regression: KeyError 'Ch' on CSV import |
746 | 731 | # --------------------------------------------------------------------------- |
747 | 732 |
|
748 | 733 |
|
749 | | -class UtilsModulePathFalseTest(TestCase): |
750 | | - """Test supports_module_path() returns False when MODULE_PATH_TOKEN is missing.""" |
| 734 | +class ModelCSVExportTest(TestCase): |
| 735 | + """Test that InterfaceNameRule exposes csv_headers and to_csv() for round-trip CSV.""" |
751 | 736 |
|
752 | | - def test_returns_false_when_token_missing(self): |
753 | | - """supports_module_path() returns False when MODULE_PATH_TOKEN is absent. |
754 | | -
|
755 | | - Temporarily removes the attribute from dcim.constants (if present) to |
756 | | - trigger the ImportError branch in supports_module_path, then restores it. |
757 | | - Asserts False regardless of whether the attribute existed beforehand. |
758 | | - """ |
759 | | - import dcim.constants as dc |
760 | | - |
761 | | - from netbox_interface_name_rules.utils import supports_module_path |
762 | | - |
763 | | - had_attr = hasattr(dc, "MODULE_PATH_TOKEN") |
764 | | - original = getattr(dc, "MODULE_PATH_TOKEN", None) |
765 | | - try: |
766 | | - if had_attr: |
767 | | - delattr(dc, "MODULE_PATH_TOKEN") |
768 | | - result = supports_module_path() |
769 | | - self.assertFalse(result) |
770 | | - finally: |
771 | | - if had_attr: |
772 | | - dc.MODULE_PATH_TOKEN = original |
| 737 | + @classmethod |
| 738 | + def setUpTestData(cls): |
| 739 | + manufacturer = Manufacturer.objects.create(name="CSVMfg", slug="csvmfg") |
| 740 | + cls.module_type = ModuleType.objects.create(manufacturer=manufacturer, model="CSV-SFP", part_number="CSV-SFP") |
| 741 | + cls.rule = InterfaceNameRule.objects.create( |
| 742 | + module_type=cls.module_type, |
| 743 | + name_template="et-0/0/{bay_position}", |
| 744 | + channel_count=4, |
| 745 | + channel_start=0, |
| 746 | + description="CSV test rule", |
| 747 | + ) |
| 748 | + cls.regex_rule = InterfaceNameRule.objects.create( |
| 749 | + module_type_is_regex=True, |
| 750 | + module_type_pattern="QSFP-.*", |
| 751 | + name_template="{base}/{channel}", |
| 752 | + channel_count=4, |
| 753 | + channel_start=1, |
| 754 | + description="Regex CSV rule", |
| 755 | + ) |
| 756 | + |
| 757 | + def test_csv_headers_attribute_exists(self): |
| 758 | + """InterfaceNameRule.csv_headers class attribute must exist.""" |
| 759 | + self.assertTrue(hasattr(InterfaceNameRule, "csv_headers"), "InterfaceNameRule.csv_headers missing") |
| 760 | + |
| 761 | + def test_csv_headers_is_list_or_tuple(self): |
| 762 | + """csv_headers must be a list or tuple of strings.""" |
| 763 | + self.assertIsInstance(InterfaceNameRule.csv_headers, (list, tuple)) |
| 764 | + |
| 765 | + def test_csv_headers_matches_import_form_fields(self): |
| 766 | + """csv_headers must exactly match InterfaceNameRuleImportForm.Meta.fields.""" |
| 767 | + from netbox_interface_name_rules.forms import InterfaceNameRuleImportForm |
| 768 | + |
| 769 | + form_fields = list(InterfaceNameRuleImportForm.Meta.fields) |
| 770 | + self.assertEqual(list(InterfaceNameRule.csv_headers), form_fields) |
| 771 | + |
| 772 | + def test_to_csv_method_exists(self): |
| 773 | + """InterfaceNameRule instances must have a to_csv() method.""" |
| 774 | + self.assertTrue(callable(getattr(self.rule, "to_csv", None)), "InterfaceNameRule.to_csv() missing") |
| 775 | + |
| 776 | + def test_to_csv_returns_sequence(self): |
| 777 | + """to_csv() must return a tuple or list.""" |
| 778 | + result = self.rule.to_csv() |
| 779 | + self.assertIsInstance(result, (tuple, list)) |
| 780 | + |
| 781 | + def test_to_csv_length_matches_csv_headers(self): |
| 782 | + """to_csv() must return exactly len(csv_headers) values.""" |
| 783 | + result = self.rule.to_csv() |
| 784 | + self.assertEqual(len(result), len(InterfaceNameRule.csv_headers)) |
| 785 | + |
| 786 | + def test_to_csv_exact_rule_module_type(self): |
| 787 | + """to_csv() for exact rule must include the module_type model string.""" |
| 788 | + result = self.rule.to_csv() |
| 789 | + values = list(result) |
| 790 | + idx = list(InterfaceNameRule.csv_headers).index("module_type") |
| 791 | + self.assertEqual(values[idx], "CSV-SFP") |
| 792 | + |
| 793 | + def test_to_csv_regex_rule_no_module_type(self): |
| 794 | + """to_csv() for regex rule must have empty string for module_type.""" |
| 795 | + result = self.regex_rule.to_csv() |
| 796 | + values = list(result) |
| 797 | + idx = list(InterfaceNameRule.csv_headers).index("module_type") |
| 798 | + self.assertEqual(values[idx], "") |
| 799 | + |
| 800 | + def test_to_csv_no_dots_in_headers(self): |
| 801 | + """csv_headers must not contain dots (regression guard for KeyError 'Ch' bug).""" |
| 802 | + for header in InterfaceNameRule.csv_headers: |
| 803 | + self.assertNotIn(".", header, f"csv_headers entry '{header}' contains a dot — would break import") |
0 commit comments