""" Tests for multi-server librenms_id helpers. Covers get_librenms_device_id, set_librenms_device_id, find_by_librenms_id, and migrate_legacy_librenms_id. """ from unittest.mock import MagicMock class TestGetLibreNMSDeviceId: """Tests for get_librenms_device_id().""" def test_returns_none_when_cf_missing(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {} result = get_librenms_device_id(obj, "default") assert result is None def test_returns_int_for_legacy_bare_integer(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": 42} result = get_librenms_device_id(obj, "default") assert result == 42 def test_legacy_bare_int_returned_for_any_server_key(self): """ Legacy bare integers are returned as a universal fallback for any server_key. Devices imported before multi-server support store a bare integer in librenms_id. These must remain discoverable regardless of which server is active, so the bare-int is returned as-is for any server_key. """ from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": 99} assert get_librenms_device_id(obj, "default") == 99 assert get_librenms_device_id(obj, "production") == 99 assert get_librenms_device_id(obj, "secondary") == 99 def test_returns_value_for_matching_server_key(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": {"production": 7, "secondary": 12}} assert get_librenms_device_id(obj, "production") == 7 def test_returns_none_for_missing_server_key_in_dict(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": {"production": 7}} result = get_librenms_device_id(obj, "secondary") assert result is None def test_returns_none_for_unexpected_type(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": "not-an-int-or-dict"} result = get_librenms_device_id(obj, "default") assert result is None def test_legacy_string_int_returned_for_any_server_key(self): """A bare string integer ('42') is coerced and returned for any server_key.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": "42"} assert get_librenms_device_id(obj, "default") == 42 assert get_librenms_device_id(obj, "production") == 42 def test_returns_none_for_bare_boolean(self): """bool is a subclass of int; bare True/False must not be treated as a valid ID.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": True} assert get_librenms_device_id(obj, "default") is None obj.cf = {"librenms_id": False} assert get_librenms_device_id(obj, "default") is None def test_returns_none_for_boolean_inside_dict(self): """Boolean values inside the JSON dict must be rejected.""" from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": {"default": True}} assert get_librenms_device_id(obj, "default") is None def test_default_server_key_is_default(self): from netbox_librenms_plugin.utils import get_librenms_device_id obj = MagicMock() obj.cf = {"librenms_id": {"default": 5}} assert get_librenms_device_id(obj) == 5 class TestFindByLibreNMSId: """Tests for find_by_librenms_id().""" def test_queries_server_key_and_legacy_integer(self): """ find_by_librenms_id() issues a Q that covers both the JSON server-key branch and the legacy bare-int branch in a single filter() call. We inspect the Q object's children directly because the two branches must coexist — matching only one would silently miss devices stored in the other format. """ from unittest.mock import MagicMock from django.db.models import Q from netbox_librenms_plugin.utils import find_by_librenms_id mock_model = MagicMock() mock_qs = MagicMock() mock_model.objects.filter.return_value = mock_qs mock_qs.first.return_value = None find_by_librenms_id(mock_model, 42, "default") mock_model.objects.filter.assert_called_once() # Verify the Q predicate covers both the server-key JSON branch and legacy bare-int/string branches call_args = mock_model.objects.filter.call_args q_arg = call_args[0][0] assert isinstance(q_arg, Q) assert q_arg.connector == "OR" # The combined Q should contain four children: JSON key (int), JSON key (str), bare-int, bare-string q_str = str(q_arg) assert "librenms_id__default" in q_str assert "custom_field_data__librenms_id__default" in q_str assert "custom_field_data__librenms_id" in q_str assert "42" in q_str def test_returns_first_matching_object(self): from netbox_librenms_plugin.utils import find_by_librenms_id expected = MagicMock() mock_model = MagicMock() mock_qs = MagicMock() mock_model.objects.filter.return_value = mock_qs mock_qs.first.return_value = expected result = find_by_librenms_id(mock_model, 42, "default") assert result is expected def test_returns_none_when_not_found(self): from unittest.mock import MagicMock from django.db.models import Q from netbox_librenms_plugin.utils import find_by_librenms_id mock_model = MagicMock() mock_qs = MagicMock() mock_model.objects.filter.return_value = mock_qs mock_qs.first.return_value = None result = find_by_librenms_id(mock_model, 999, "production") assert result is None # Any server_key must include legacy bare-int/string fallback conditions # so that devices imported before multi-server support are still found. call_args = mock_model.objects.filter.call_args q_arg = call_args[0][0] assert isinstance(q_arg, Q) q_str = str(q_arg) assert "custom_field_data__librenms_id__production" in q_str assert "custom_field_data__librenms_id" in q_str def test_default_server_key_is_default(self): """find_by_librenms_id() uses "default" as the server key when no key is passed. We inspect the Q predicate's children to confirm the key embedded in the JSON path is exactly "default", not some other fallback value. """ from unittest.mock import MagicMock from django.db.models import Q from netbox_librenms_plugin.utils import find_by_librenms_id mock_model = MagicMock() mock_qs = MagicMock() mock_model.objects.filter.return_value = mock_qs mock_qs.first.return_value = None find_by_librenms_id(mock_model, 42) mock_model.objects.filter.assert_called_once() call_args = mock_model.objects.filter.call_args q_arg = call_args[0][0] assert isinstance(q_arg, Q) assert q_arg.connector == "OR" q_str = str(q_arg) assert "custom_field_data__librenms_id__default" in q_str class TestMigrateLegacyLibreNMSId: """Tests for migrate_legacy_librenms_id().""" def test_returns_true_when_migrated(self): from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 42} result = migrate_legacy_librenms_id(obj, "default") assert result is True def test_migrates_integer_to_dict_format(self): from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 42} migrate_legacy_librenms_id(obj, "production") assert obj.custom_field_data["librenms_id"] == {"production": 42} def test_returns_false_when_already_dict(self): from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": {"default": 42}} result = migrate_legacy_librenms_id(obj, "default") assert result is False def test_returns_false_when_value_is_none(self): from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": None} result = migrate_legacy_librenms_id(obj, "default") assert result is False def test_returns_false_for_boolean_value(self): """bool is a subclass of int; True/False must not be migrated.""" from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": True} assert migrate_legacy_librenms_id(obj, "default") is False assert obj.custom_field_data["librenms_id"] is True # unchanged def test_does_not_call_save(self): """migrate_legacy_librenms_id must NOT call obj.save() — caller is responsible.""" from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 7} migrate_legacy_librenms_id(obj, "default") obj.save.assert_not_called() def test_preserves_value_in_migrated_dict(self): from netbox_librenms_plugin.utils import migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 99} migrate_legacy_librenms_id(obj, "secondary") assert obj.custom_field_data["librenms_id"]["secondary"] == 99 class TestLibreNMSIdRoundtrip: """get_librenms_device_id should see the value set by set_librenms_device_id.""" def test_set_then_get_returns_same_value(self): from netbox_librenms_plugin.utils import get_librenms_device_id, set_librenms_device_id obj = MagicMock() obj.custom_field_data = {} obj.cf = obj.custom_field_data # make cf a live view of custom_field_data set_librenms_device_id(obj, 42, "production") result = get_librenms_device_id(obj, "production") assert result == 42 def test_set_multiple_servers_get_correct_each(self): from netbox_librenms_plugin.utils import get_librenms_device_id, set_librenms_device_id obj = MagicMock() obj.custom_field_data = {} obj.cf = obj.custom_field_data set_librenms_device_id(obj, 10, "primary") set_librenms_device_id(obj, 20, "secondary") assert get_librenms_device_id(obj, "primary") == 10 assert get_librenms_device_id(obj, "secondary") == 20 def test_migrate_then_get_returns_value(self): from netbox_librenms_plugin.utils import get_librenms_device_id, migrate_legacy_librenms_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 55} obj.cf = obj.custom_field_data migrate_legacy_librenms_id(obj, "default") result = get_librenms_device_id(obj, "default") assert result == 55 class TestSetLibreNMSDeviceId: """Tests for set_librenms_device_id in utils.py.""" def test_stores_int_for_valid_device_id(self): """Valid integer device_id is stored under server_key.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {"librenms_id": None} set_librenms_device_id(obj, 42, server_key="primary") assert obj.custom_field_data["librenms_id"] == {"primary": 42} def test_invalid_device_id_not_stored(self): """Non-integer device_id is rejected and nothing is written.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {} set_librenms_device_id(obj, "not-an-int", server_key="primary") assert "librenms_id" not in obj.custom_field_data def test_invalid_device_id_does_not_overwrite_existing(self): """Existing valid value is preserved when new device_id is invalid.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {"librenms_id": {"primary": 10}} set_librenms_device_id(obj, None, server_key="primary") assert obj.custom_field_data["librenms_id"] == {"primary": 10} def test_legacy_bare_int_blocks_write(self): """Legacy bare-integer value blocks the write (no silent migration).""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {"librenms_id": 7} set_librenms_device_id(obj, 99, server_key="secondary") # Write must be skipped; user must use the migration workflow. assert obj.custom_field_data["librenms_id"] == 7 def test_adds_new_server_key_to_existing_dict(self): """Adding a new server key preserves existing keys.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {"librenms_id": {"primary": 5}} set_librenms_device_id(obj, 20, server_key="secondary") assert obj.custom_field_data["librenms_id"] == {"primary": 5, "secondary": 20} def test_string_integer_is_coerced(self): """String '42' is coerced to int 42.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {} set_librenms_device_id(obj, "42", server_key="primary") assert obj.custom_field_data["librenms_id"] == {"primary": 42} def test_unexpected_cf_type_reset_to_empty(self): """If custom_field_data has unexpected type for librenms_id, it is reset.""" from netbox_librenms_plugin.utils import set_librenms_device_id obj = MagicMock() obj.custom_field_data = {"librenms_id": "unexpected-string"} set_librenms_device_id(obj, 5, server_key="primary") assert obj.custom_field_data["librenms_id"] == {"primary": 5}