"""Coverage tests for utils.py missing lines.""" from unittest.mock import MagicMock, patch class TestConvertSpeedToKbps: """Boundary and type tests for convert_speed_to_kbps.""" def test_none_returns_none(self): from netbox_librenms_plugin.utils import convert_speed_to_kbps assert convert_speed_to_kbps(None) is None def test_zero_returns_zero(self): from netbox_librenms_plugin.utils import convert_speed_to_kbps assert convert_speed_to_kbps(0) == 0 def test_sub_kbps_rounds_down_to_zero(self): from netbox_librenms_plugin.utils import convert_speed_to_kbps assert convert_speed_to_kbps(1) == 0 assert convert_speed_to_kbps(999) == 0 def test_exact_kbps_boundary(self): from netbox_librenms_plugin.utils import convert_speed_to_kbps assert convert_speed_to_kbps(1000) == 1 def test_1gbps(self): from netbox_librenms_plugin.utils import convert_speed_to_kbps assert convert_speed_to_kbps(1_000_000_000) == 1_000_000 def test_string_input_raises_type_error(self): import pytest from netbox_librenms_plugin.utils import convert_speed_to_kbps with pytest.raises(TypeError): convert_speed_to_kbps("1000000") class TestGetVirtualChassisMemberException: """Tests for get_virtual_chassis_member exception path (lines 76-77).""" def test_exception_returns_original_device(self): """When ObjectDoesNotExist raised, return original device.""" from django.core.exceptions import ObjectDoesNotExist from netbox_librenms_plugin.utils import get_virtual_chassis_member device = MagicMock() device.virtual_chassis = MagicMock() device.virtual_chassis.members.get.side_effect = ObjectDoesNotExist("not found") result = get_virtual_chassis_member(device, "Ethernet1") assert result is device def test_no_virtual_chassis_returns_device(self): from netbox_librenms_plugin.utils import get_virtual_chassis_member device = MagicMock() device.virtual_chassis = None result = get_virtual_chassis_member(device, "Ethernet1") assert result is device def test_port_name_no_digit_returns_device(self): from netbox_librenms_plugin.utils import get_virtual_chassis_member device = MagicMock() device.virtual_chassis = MagicMock() # Port name with no leading digit after alpha chars → no match result = get_virtual_chassis_member(device, "Management") assert result is device class TestGetLibreNMSSyncDeviceServerKey: """Tests for get_librenms_sync_device with server_key (lines 113-125).""" def test_returns_member_with_dict_cf_for_server_key(self): """Priority 1: member with dict CF matching server_key.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member1 = MagicMock() member1.cf = {"librenms_id": {"default": 42}} member2 = MagicMock() member2.cf = {"librenms_id": None} vc.members.all.return_value = [member1, member2] result = get_librenms_sync_device(device, server_key="default") assert result is member1 def test_falls_back_to_get_librenms_device_id_when_no_dict(self): """Priority 2 legacy: falls back to get_librenms_device_id.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member = MagicMock() member.cf = {"librenms_id": None} member.primary_ip = MagicMock() vc.members.all.return_value = [member] vc.master = None with patch("netbox_librenms_plugin.utils.get_librenms_device_id") as mock_get_id: mock_get_id.return_value = 99 result = get_librenms_sync_device(device, server_key="default") assert result is member def test_server_key_none_matches_any_dict_member(self): """server_key=None: matches any member with any librenms_id in dict.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member_with_id = MagicMock() member_with_id.cf = {"librenms_id": {"primary": 10}} member_without_id = MagicMock() member_without_id.cf = {"librenms_id": None} vc.members.all.return_value = [member_without_id, member_with_id] result = get_librenms_sync_device(device, server_key=None) assert result is member_with_id def test_server_key_none_matches_legacy_cf(self): """server_key=None: matches member with legacy bare int librenms_id.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member = MagicMock() member.cf = {"librenms_id": 42} # legacy bare int vc.members.all.return_value = [member] result = get_librenms_sync_device(device, server_key=None) assert result is member class TestGetLibreNMSSyncDeviceLegacyInt: """Tests for get_librenms_sync_device legacy int CF (lines 132-133).""" def test_legacy_int_cf_with_server_key_uses_get_id(self): """server_key set, raw_cf is legacy int → doesn't match dict path, falls back.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member = MagicMock() member.cf = {"librenms_id": 55} # legacy int, not dict vc.members.all.return_value = [member] vc.master = None with patch("netbox_librenms_plugin.utils.get_librenms_device_id") as mock_get_id: mock_get_id.return_value = 55 result = get_librenms_sync_device(device, server_key="default") assert result is member class TestGetLibreNMSSyncDeviceFallbacks: """Tests for get_librenms_sync_device fallback paths (lines 138-150).""" def test_falls_back_to_master_with_primary_ip(self): """When no member has librenms_id, uses master with primary IP.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member = MagicMock() member.cf = {"librenms_id": None} master = MagicMock() master.primary_ip = MagicMock() vc.master = master vc.members.all.return_value = [member] with patch("netbox_librenms_plugin.utils.get_librenms_device_id", return_value=None): result = get_librenms_sync_device(device, server_key="default") assert result is master def test_falls_back_to_any_member_with_primary_ip(self): """When no master, falls back to any member with primary IP.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc member_no_ip = MagicMock() member_no_ip.cf = {"librenms_id": None} member_no_ip.primary_ip = None member_with_ip = MagicMock() member_with_ip.cf = {"librenms_id": None} member_with_ip.primary_ip = MagicMock() vc.master = None vc.members.all.return_value = [member_no_ip, member_with_ip] with patch("netbox_librenms_plugin.utils.get_librenms_device_id", return_value=None): result = get_librenms_sync_device(device, server_key="default") assert result is member_with_ip def test_falls_back_to_lowest_vc_position(self): """Fallback to member with lowest vc_position when no IPs.""" from netbox_librenms_plugin.utils import get_librenms_sync_device device = MagicMock() vc = MagicMock() device.virtual_chassis = vc m1 = MagicMock() m1.cf = {"librenms_id": None} m1.primary_ip = None m1.vc_position = 3 m2 = MagicMock() m2.cf = {"librenms_id": None} m2.primary_ip = None m2.vc_position = 1 vc.master = None vc.members.all.return_value = [m1, m2] with patch("netbox_librenms_plugin.utils.get_librenms_device_id", return_value=None): result = get_librenms_sync_device(device, server_key="default") assert result is m2 class TestGetTablePaginateCountValueError: """Tests for get_table_paginate_count ValueError path (lines 169-170).""" def test_invalid_per_page_falls_back_to_default(self): from netbox_librenms_plugin.utils import get_table_paginate_count request = MagicMock() request.GET = {"table_per_page": "not_a_number"} with patch("netbox_librenms_plugin.utils.get_config"): with patch("netbox_librenms_plugin.utils.netbox_get_paginate_count") as mock_paginate: mock_paginate.return_value = 50 result = get_table_paginate_count(request, "table_") assert result == 50 class TestGetUserPrefNoConfig: """Tests for get_user_pref when user has no config (line 179).""" def test_returns_default_when_no_config_attr(self): from netbox_librenms_plugin.utils import get_user_pref request = MagicMock(spec=["user"]) request.user = MagicMock(spec=["has_perm"]) # No 'config' attr result = get_user_pref(request, "some.pref", default="fallback") assert result == "fallback" def test_returns_none_when_no_user(self): from netbox_librenms_plugin.utils import get_user_pref request = MagicMock(spec=[]) # No 'user' attr result = get_user_pref(request, "some.pref") assert result is None class TestSaveUserPrefExceptions: """Tests for save_user_pref TypeError/ValueError exceptions (lines 187-188).""" def test_type_error_is_swallowed(self): from netbox_librenms_plugin.utils import save_user_pref request = MagicMock() request.user = MagicMock() request.user.config.set.side_effect = TypeError("bad type") # Should not raise save_user_pref(request, "some.pref", "value") def test_value_error_is_swallowed(self): from netbox_librenms_plugin.utils import save_user_pref request = MagicMock() request.user = MagicMock() request.user.config.set.side_effect = ValueError("bad value") save_user_pref(request, "some.pref", "value") class TestMatchLibrenmsHardwareImportError: """Tests for DeviceTypeMapping ImportError guard (line 242).""" def test_no_hardware_returns_no_match(self): """Empty hardware string returns no match.""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type result = match_librenms_hardware_to_device_type("") assert result["matched"] is False def test_dash_hardware_returns_no_match(self): """'-' hardware returns no match.""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type result = match_librenms_hardware_to_device_type("-") assert result["matched"] is False class TestMatchLibrenmsHardwareDeviceTypeMappingPaths: """Tests for DeviceTypeMapping paths (lines 251-261).""" def test_device_type_mapping_found(self): """DeviceTypeMapping.objects.get returns match → return mapping result.""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type mock_device_type = MagicMock() mock_mapping = MagicMock() mock_mapping.netbox_device_type = mock_device_type DoesNotExist = type("DoesNotExist", (Exception,), {}) MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) mock_dtm_class = MagicMock() mock_dtm_class.DoesNotExist = DoesNotExist mock_dtm_class.MultipleObjectsReturned = MultipleObjectsReturned mock_dtm_class.objects.get.return_value = mock_mapping with patch("netbox_librenms_plugin.models.DeviceTypeMapping", mock_dtm_class, create=True): result = match_librenms_hardware_to_device_type("C9300-48P") assert result["matched"] is True assert result["device_type"] is mock_device_type assert result["match_type"] == "mapping" def test_device_type_mapping_multiple_returns_logs_warning(self): """DeviceTypeMapping.MultipleObjectsReturned → logs warning and skips mapping.""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type DoesNotExist = type("DoesNotExist", (Exception,), {}) MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) mock_dtm_class = MagicMock() mock_dtm_class.DoesNotExist = DoesNotExist mock_dtm_class.MultipleObjectsReturned = MultipleObjectsReturned mock_dtm_class.objects.get.side_effect = MultipleObjectsReturned("multiple") dt_DoesNotExist = type("DoesNotExist", (Exception,), {}) dt_MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) with patch("netbox_librenms_plugin.models.DeviceTypeMapping", mock_dtm_class, create=True): with patch("dcim.models.DeviceType") as MockDT: MockDT.DoesNotExist = dt_DoesNotExist MockDT.MultipleObjectsReturned = dt_MultipleObjectsReturned MockDT.objects.get.side_effect = dt_DoesNotExist("no match") result = match_librenms_hardware_to_device_type("Ambiguous Hardware") assert result is None # multiple DeviceTypeMapping matches returns None (ambiguous) class TestMatchLibrenmsHardwareDeviceTypeMultipleReturned: """Tests for DeviceType MultipleObjectsReturned — ambiguity surfaces as None.""" def test_part_number_multiple_returns_none(self): """DeviceType.MultipleObjectsReturned for part_number → return None (not silently pick first).""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type DoesNotExist = type("DoesNotExist", (Exception,), {}) MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) dtm_DoesNotExist = type("DoesNotExist", (Exception,), {}) mock_dtm = MagicMock() mock_dtm.DoesNotExist = dtm_DoesNotExist mock_dtm.MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) mock_dtm.objects.get.side_effect = dtm_DoesNotExist() with patch("netbox_librenms_plugin.models.DeviceTypeMapping", mock_dtm, create=True): with patch("dcim.models.DeviceType") as MockDT: MockDT.DoesNotExist = DoesNotExist MockDT.MultipleObjectsReturned = MultipleObjectsReturned MockDT.objects.get.side_effect = MultipleObjectsReturned("multiple") result = match_librenms_hardware_to_device_type("C9300") assert result is None def test_model_multiple_returns_none(self): """DeviceType.MultipleObjectsReturned for model → return None (not silently pick first).""" from netbox_librenms_plugin.utils import match_librenms_hardware_to_device_type DoesNotExist = type("DoesNotExist", (Exception,), {}) MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) def get_side_effect(**kwargs): if "part_number__iexact" in kwargs: raise DoesNotExist("no part number") raise MultipleObjectsReturned("multiple models") dtm_DoesNotExist = type("DoesNotExist", (Exception,), {}) mock_dtm = MagicMock() mock_dtm.DoesNotExist = dtm_DoesNotExist mock_dtm.MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) mock_dtm.objects.get.side_effect = dtm_DoesNotExist() with patch("netbox_librenms_plugin.models.DeviceTypeMapping", mock_dtm, create=True): with patch("dcim.models.DeviceType") as MockDT: MockDT.DoesNotExist = DoesNotExist MockDT.MultipleObjectsReturned = MultipleObjectsReturned MockDT.objects.get.side_effect = get_side_effect result = match_librenms_hardware_to_device_type("SomeModel") assert result is None class TestFindMatchingSiteMultipleReturned: """Tests for find_matching_site MultipleObjectsReturned (lines 325-327).""" def test_multiple_objects_returned_uses_first(self): from netbox_librenms_plugin.utils import find_matching_site mock_site = MagicMock() Site_DoesNotExist = type("DoesNotExist", (Exception,), {}) Site_MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) with patch("dcim.models.Site") as MockSite: MockSite.DoesNotExist = Site_DoesNotExist MockSite.MultipleObjectsReturned = Site_MultipleObjectsReturned MockSite.objects.get.side_effect = Site_MultipleObjectsReturned("multiple") MockSite.objects.filter.return_value.first.return_value = mock_site result = find_matching_site("NYC") assert result["found"] is True assert result["site"] is mock_site class TestFindMatchingPlatformMultipleReturned: """Tests for find_matching_platform MultipleObjectsReturned (lines 358-360).""" def test_multiple_objects_returned_uses_first(self): from netbox_librenms_plugin.utils import find_matching_platform mock_platform = MagicMock() Platform_DoesNotExist = type("DoesNotExist", (Exception,), {}) Platform_MultipleObjectsReturned = type("MultipleObjectsReturned", (Exception,), {}) with patch("dcim.models.Platform") as MockPlatform: MockPlatform.DoesNotExist = Platform_DoesNotExist MockPlatform.MultipleObjectsReturned = Platform_MultipleObjectsReturned MockPlatform.objects.get.side_effect = Platform_MultipleObjectsReturned("multiple") MockPlatform.objects.filter.return_value.first.return_value = mock_platform result = find_matching_platform("ios") assert result["found"] is True assert result["platform"] is mock_platform class TestGetMissingVlanWarning: """Tests for get_missing_vlan_warning when vid in missing_vlans (lines 462-467).""" def test_vid_in_missing_vlans_returns_warning_html(self): from netbox_librenms_plugin.utils import get_missing_vlan_warning result = get_missing_vlan_warning(100, [100, 200]) assert "mdi-alert" in result assert "text-danger" in result def test_vid_not_in_missing_vlans_returns_empty_string(self): from netbox_librenms_plugin.utils import get_missing_vlan_warning result = get_missing_vlan_warning(999, [100, 200]) assert result == "" class TestGetLibreNMSDeviceIdStringNormalization: """Tests for get_librenms_device_id string normalization (lines 557-558).""" def test_string_id_normalized_to_int_and_saved(self): """String stored as librenms_id is normalized to int and saved.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": "42"} obj.custom_field_data = {"librenms_id": "42"} result = get_librenms_device_id(obj, "default", auto_save=True) assert result == 42 # Should save to normalize obj.save.assert_called_once() def test_string_id_returned_without_save_when_auto_save_false(self): """String normalized but not saved when auto_save=False.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": "99"} obj.custom_field_data = {"librenms_id": "99"} result = get_librenms_device_id(obj, "default", auto_save=False) assert result == 99 obj.save.assert_not_called() def test_dict_with_string_value_normalized(self): """Dict entry with string value is normalized to int.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": {"default": "77"}} obj.custom_field_data = {"librenms_id": {"default": "77"}} result = get_librenms_device_id(obj, "default", auto_save=True) assert result == 77 obj.save.assert_called_once() def test_invalid_string_returns_none(self): """Non-digit string in librenms_id returns None.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": "not-a-number"} obj.custom_field_data = {"librenms_id": "not-a-number"} result = get_librenms_device_id(obj, "default") assert result is None class TestFindByLibreNMSIdNoneGuard: """Verify find_by_librenms_id returns None for None input without querying the DB.""" def test_none_id_returns_none_without_query(self): """find_by_librenms_id(None, ...) must return None without hitting the DB.""" from netbox_librenms_plugin.utils import find_by_librenms_id model = MagicMock() result = find_by_librenms_id(model, None, server_key="default") assert result is None model.objects.filter.assert_not_called()