""" Regression tests for reviewer-requested fixes. Covers: _load_vc_member_name_pattern validation, _generate_vc_member_name pattern handling, _normalize_librenms_mapping guards, all_server_mappings did validation, render_device_selection XSS escape, SingleCableVerifyView server_key from POST, import_single_device lazy validation api passthrough, CreateAndAssignPlatformView full_clean before save. """ from unittest.mock import MagicMock, patch # --------------------------------------------------------------------------- # _load_vc_member_name_pattern # --------------------------------------------------------------------------- class TestLoadVcMemberNamePattern: """_load_vc_member_name_pattern must return valid string or default.""" DEFAULT = "-M{position}" def _call(self): from netbox_librenms_plugin.import_utils.virtual_chassis import _load_vc_member_name_pattern return _load_vc_member_name_pattern() def _patch_settings(self, settings_obj): """Patch the deferred import of LibreNMSSettings inside the function.""" return patch( "netbox_librenms_plugin.models.LibreNMSSettings.objects", **{"order_by.return_value.first.return_value": settings_obj}, ) def test_returns_valid_pattern(self): settings = MagicMock() settings.vc_member_name_pattern = "-SW{position}" with self._patch_settings(settings): assert self._call() == "-SW{position}" def test_returns_default_for_none_pattern(self): settings = MagicMock() settings.vc_member_name_pattern = None with self._patch_settings(settings): assert self._call() == self.DEFAULT def test_returns_default_for_empty_string(self): settings = MagicMock() settings.vc_member_name_pattern = "" with self._patch_settings(settings): assert self._call() == self.DEFAULT def test_returns_default_for_whitespace_only(self): settings = MagicMock() settings.vc_member_name_pattern = " " with self._patch_settings(settings): assert self._call() == self.DEFAULT def test_returns_default_for_boolean(self): settings = MagicMock() settings.vc_member_name_pattern = True with self._patch_settings(settings): assert self._call() == self.DEFAULT def test_returns_default_when_no_settings(self): with self._patch_settings(None): assert self._call() == self.DEFAULT def test_returns_default_on_exception(self): with patch( "netbox_librenms_plugin.models.LibreNMSSettings.objects", ) as mock_objs: mock_objs.order_by.side_effect = RuntimeError("db error") assert self._call() == self.DEFAULT # --------------------------------------------------------------------------- # _normalize_librenms_mapping # --------------------------------------------------------------------------- class TestNormalizeLibreNMSMapping: """_normalize_librenms_mapping must reject booleans and non-digit strings.""" def _call(self, value): # Instantiate the view class minimally to access the method from netbox_librenms_plugin.views.sync.device_fields import RemoveServerMappingView view = object.__new__(RemoveServerMappingView) return view._normalize_librenms_mapping(value) def test_int_becomes_default_dict(self): assert self._call(42) == {"default": 42} def test_bool_true_returns_empty(self): assert self._call(True) == {} def test_bool_false_returns_empty(self): assert self._call(False) == {} def test_digit_string_coerced(self): assert self._call("42") == {"default": 42} def test_non_digit_string_returns_empty(self): assert self._call("not-a-number") == {} def test_plus_prefix_rejected(self): """'+1' is not strictly digit-only.""" assert self._call("+1") == {} def test_space_padded_rejected(self): """' 42 ' is not strictly digit-only.""" assert self._call(" 42 ") == {} def test_dict_passed_through(self): d = {"production": 7} assert self._call(d) is d def test_none_returns_empty(self): assert self._call(None) == {} def test_list_returns_empty(self): assert self._call([1, 2]) == {} # --------------------------------------------------------------------------- # all_server_mappings — did validation # --------------------------------------------------------------------------- class TestAllServerMappingsDidValidation: """all_server_mappings must skip invalid device IDs in the cf_value dict.""" def _call(self, obj, active_server_key="default"): from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView return BaseLibreNMSSyncView._build_all_server_mappings(obj, active_server_key) @patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") def test_skips_boolean_did(self, mock_settings): mock_settings.PLUGINS_CONFIG = {"netbox_librenms_plugin": {"servers": {}}} obj = MagicMock() obj.custom_field_data = {"librenms_id": {"default": True, "prod": 42}} result = self._call(obj) # Only prod=42 should survive assert len(result) == 1 assert result[0]["device_id"] == 42 @patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") def test_skips_none_did(self, mock_settings): mock_settings.PLUGINS_CONFIG = {"netbox_librenms_plugin": {"servers": {}}} obj = MagicMock() obj.custom_field_data = {"librenms_id": {"default": None}} result = self._call(obj) assert result is None # empty list → returns None @patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") def test_coerces_digit_string_did(self, mock_settings): mock_settings.PLUGINS_CONFIG = {"netbox_librenms_plugin": {"servers": {}}} obj = MagicMock() obj.custom_field_data = {"librenms_id": {"prod": "99"}} result = self._call(obj) assert len(result) == 1 assert result[0]["device_id"] == 99 @patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") def test_skips_non_digit_string_did(self, mock_settings): mock_settings.PLUGINS_CONFIG = {"netbox_librenms_plugin": {"servers": {}}} obj = MagicMock() obj.custom_field_data = {"librenms_id": {"default": "bogus"}} result = self._call(obj) assert result is None @patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") def test_valid_int_passes_through(self, mock_settings): mock_settings.PLUGINS_CONFIG = {"netbox_librenms_plugin": {"servers": {}}} obj = MagicMock() obj.custom_field_data = {"librenms_id": {"default": 5, "secondary": 10}} result = self._call(obj) assert len(result) == 2 ids = {e["device_id"] for e in result} assert ids == {5, 10} # --------------------------------------------------------------------------- # render_device_selection — XSS escape # --------------------------------------------------------------------------- class TestRenderDeviceSelectionEscape: """render_device_selection must HTML-escape member.name.""" def test_member_name_is_escaped(self): from netbox_librenms_plugin.tables.cables import VCCableTable device = MagicMock() device.id = 1 vc = MagicMock() member = MagicMock() member.id = 1 member.name = '' vc.members.all.return_value = [member] device.virtual_chassis = vc table = VCCableTable([], device=device) record = {"local_port": "eth0", "local_port_id": "42"} with patch( "netbox_librenms_plugin.tables.cables.get_virtual_chassis_member", return_value=member, ): html = str(table.render_device_selection(None, record)) # The raw