first commit
Some checks failed
ci / deploy (push) Has been cancelled
CodeQL Advanced / Analyze (actions) (push) Has been cancelled
CodeQL Advanced / Analyze (javascript-typescript) (push) Has been cancelled
CodeQL Advanced / Analyze (python) (push) Has been cancelled

This commit is contained in:
Vlastislav Svatek
2026-06-05 10:39:05 +02:00
commit 673e67106e
217 changed files with 76612 additions and 0 deletions

View File

@@ -0,0 +1,216 @@
"""Tests for netbox_librenms_plugin.__init__ module.
Covers the _ensure_librenms_id_custom_field post_migrate signal handler.
"""
from unittest.mock import MagicMock, patch
# =============================================================================
# TestEnsureLibreNMSIdCustomField - 6 tests
# =============================================================================
class TestEnsureLibreNMSIdCustomField:
"""Test _ensure_librenms_id_custom_field signal handler."""
def setup_method(self):
"""Reset per-alias execution tracking before each test."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
_ensure_librenms_id_custom_field._executed_aliases = set()
def _setup_cf_mock(self, MockCustomField, mock_cf, created):
"""Wire MockCustomField so that objects.using(alias).get_or_create(...) returns (mock_cf, created)."""
MockCustomField.objects.using.return_value.get_or_create.return_value = (mock_cf, created)
def _setup_ct_mock(self, MockContentType, mock_ct):
"""Wire MockContentType so that objects.db_manager(alias).get_for_model(...) returns mock_ct."""
MockContentType.objects.db_manager.return_value.get_for_model.return_value = mock_ct
@patch("dcim.models.Interface", new_callable=MagicMock)
@patch("dcim.models.Device", new_callable=MagicMock)
@patch("virtualization.models.VMInterface", new_callable=MagicMock)
@patch("virtualization.models.VirtualMachine", new_callable=MagicMock)
@patch("django.contrib.contenttypes.models.ContentType")
@patch("extras.models.CustomField")
def test_creates_custom_field_when_missing(
self, MockCustomField, MockContentType, mock_vm, mock_vmif, mock_device, mock_iface
):
"""Custom field is created with correct defaults when it does not exist."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
mock_cf = MagicMock()
mock_cf.object_types.values_list.return_value = []
self._setup_cf_mock(MockCustomField, mock_cf, True)
mock_ct = MagicMock()
mock_ct.pk = 1
self._setup_ct_mock(MockContentType, mock_ct)
with patch("logging.getLogger") as mock_get_logger:
_ensure_librenms_id_custom_field(sender=None)
MockCustomField.objects.using.return_value.get_or_create.assert_called_once_with(
name="librenms_id",
defaults={
"type": "json",
"label": "LibreNMS ID",
"description": "LibreNMS Device ID for synchronization (auto-created by plugin)",
"required": False,
"ui_visible": "if-set",
"ui_editable": "yes",
"is_cloneable": False,
},
)
# Should have added content types for all 4 models
assert mock_cf.object_types.add.call_count == 4
# Should log when created
mock_get_logger.assert_called_with("netbox_librenms_plugin")
def test_skips_when_already_executed(self):
"""Handler is a no-op on second invocation for the same DB alias."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
_ensure_librenms_id_custom_field._executed_aliases = {"default"}
with patch("extras.models.CustomField") as MockCustomField:
_ensure_librenms_id_custom_field(sender=None)
MockCustomField.objects.using.assert_not_called()
@patch("dcim.models.Interface", new_callable=MagicMock)
@patch("dcim.models.Device", new_callable=MagicMock)
@patch("virtualization.models.VMInterface", new_callable=MagicMock)
@patch("virtualization.models.VirtualMachine", new_callable=MagicMock)
@patch("django.contrib.contenttypes.models.ContentType")
@patch("extras.models.CustomField")
def test_existing_field_not_recreated(
self, MockCustomField, MockContentType, mock_vm, mock_vmif, mock_device, mock_iface
):
"""When custom field already exists, it is not recreated but types are checked."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
mock_cf = MagicMock()
mock_cf.object_types.values_list.return_value = [1, 2, 3, 4]
self._setup_cf_mock(MockCustomField, mock_cf, False)
mock_ct = MagicMock()
mock_ct.pk = 1
self._setup_ct_mock(MockContentType, mock_ct)
_ensure_librenms_id_custom_field(sender=None)
# All pks already present, no types should be added
mock_cf.object_types.add.assert_not_called()
@patch("dcim.models.Interface", new_callable=MagicMock)
@patch("dcim.models.Device", new_callable=MagicMock)
@patch("virtualization.models.VMInterface", new_callable=MagicMock)
@patch("virtualization.models.VirtualMachine", new_callable=MagicMock)
@patch("django.contrib.contenttypes.models.ContentType")
@patch("extras.models.CustomField")
def test_adds_missing_content_types(
self, MockCustomField, MockContentType, mock_vm, mock_vmif, mock_device, mock_iface
):
"""When some content types are missing, only those are added."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
mock_cf = MagicMock()
mock_cf.object_types.values_list.return_value = [1, 2]
self._setup_cf_mock(MockCustomField, mock_cf, False)
ct_existing = MagicMock()
ct_existing.pk = 1
ct_new = MagicMock()
ct_new.pk = 99
MockContentType.objects.db_manager.return_value.get_for_model.side_effect = [
ct_existing,
ct_existing,
ct_new,
ct_new,
]
_ensure_librenms_id_custom_field(sender=None)
assert mock_cf.object_types.add.call_count == 2
mock_cf.object_types.add.assert_any_call(ct_new)
@patch("extras.models.CustomField")
def test_exception_does_not_propagate(self, MockCustomField):
"""Exceptions during custom field creation are caught and logged."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
MockCustomField.objects.using.return_value.get_or_create.side_effect = Exception("DB not ready")
with patch("logging.getLogger") as mock_get_logger:
# Should not raise
_ensure_librenms_id_custom_field(sender=None)
# Verify the exception was logged
logger_instance = mock_get_logger.return_value
logger_instance.exception.assert_called_once()
call_args = logger_instance.exception.call_args
assert "librenms_id" in call_args[0][0]
@patch("dcim.models.Interface", new_callable=MagicMock)
@patch("dcim.models.Device", new_callable=MagicMock)
@patch("virtualization.models.VMInterface", new_callable=MagicMock)
@patch("virtualization.models.VirtualMachine", new_callable=MagicMock)
@patch("django.contrib.contenttypes.models.ContentType")
@patch("extras.models.CustomField")
def test_no_log_when_field_already_exists(
self, MockCustomField, MockContentType, mock_vm, mock_vmif, mock_device, mock_iface
):
"""No log message when the custom field already existed."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
mock_cf = MagicMock()
mock_cf.object_types.values_list.return_value = [1, 2, 3, 4]
self._setup_cf_mock(MockCustomField, mock_cf, False)
mock_ct = MagicMock()
mock_ct.pk = 1
self._setup_ct_mock(MockContentType, mock_ct)
with patch("logging.getLogger") as mock_get_logger:
_ensure_librenms_id_custom_field(sender=None)
# When the field already exists (created=False), the info log should
# not be emitted. We verify via the logger instance rather than
# asserting getLogger was never called, which is fragile.
logger_instance = mock_get_logger.return_value
logger_instance.info.assert_not_called()
@patch("dcim.models.Interface", new_callable=MagicMock)
@patch("dcim.models.Device", new_callable=MagicMock)
@patch("virtualization.models.VMInterface", new_callable=MagicMock)
@patch("virtualization.models.VirtualMachine", new_callable=MagicMock)
@patch("django.contrib.contenttypes.models.ContentType")
@patch("extras.models.CustomField")
def test_integer_field_migrated_to_json(
self, MockCustomField, MockContentType, mock_vm, mock_vmif, mock_device, mock_iface
):
"""When existing field has type='integer', it is migrated to 'json' and saved on the given alias."""
from netbox_librenms_plugin import _ensure_librenms_id_custom_field
db_alias = "other"
mock_cf = MagicMock()
mock_cf.type = "integer"
mock_cf.object_types.values_list.return_value = [1, 2, 3, 4]
self._setup_cf_mock(MockCustomField, mock_cf, False)
mock_ct = MagicMock()
mock_ct.pk = 1
self._setup_ct_mock(MockContentType, mock_ct)
_ensure_librenms_id_custom_field(sender=None, using=db_alias)
assert mock_cf.type == "json"
# Alias must flow through both the CustomField lookup and the ContentType lookup,
# exercising the per-alias guard path (_executed_aliases).
MockCustomField.objects.using.assert_called_with(db_alias)
MockContentType.objects.db_manager.assert_called_with(db_alias)
mock_cf.save.assert_called_once_with(using=db_alias, update_fields=["type"])
assert db_alias in _ensure_librenms_id_custom_field._executed_aliases