Files
netbox-librenms-plugin/netbox_librenms_plugin/tests/test_coverage_sync_views2.py
Vlastislav Svatek 673e67106e
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
first commit
2026-06-05 10:39:05 +02:00

2241 lines
98 KiB
Python

"""
Coverage tests for views/sync/ (cables, devices, interfaces, ip_addresses, locations, vlans).
All DB interactions are mocked via MagicMock. No @pytest.mark.django_db.
"""
from unittest.mock import MagicMock, patch
# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------
def _make_request(post_data=None, get_data=None, user=None):
req = MagicMock()
_post = post_data or {}
post_mock = MagicMock()
post_mock.get = lambda k, d=None: _post.get(k, d)
post_mock.getlist = lambda k: (
_post.get(k) if isinstance(_post.get(k), list) else ([] if k not in _post else [_post[k]])
)
req.POST = post_mock
req.GET = get_data or {}
req.user = user or MagicMock()
req.META = {}
req.htmx = False
return req
def _denied_response():
resp = MagicMock()
resp.status_code = 403
return resp
# ===========================================================================
# views/sync/cables.py — SyncCablesView
# ===========================================================================
class TestSyncCablesViewPermissionDenied:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=_denied_response())
view.request = _make_request()
with patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404") as mock_get:
result = view.post(view.request, pk=1)
assert result.status_code == 403
mock_get.assert_not_called()
class TestSyncCablesViewCacheMiss:
def test_cache_miss_redirects_with_error(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["1"], "device_selection_1": "1"})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect") as mock_redirect,
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = None
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
mock_redirect.assert_called_once()
class TestSyncCablesViewNoSelection:
def test_no_selection_redirects_with_error(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = {"links": [{"local_port_id": "99"}]}
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
class TestSyncCablesViewSuccessPath:
def test_valid_cable_created(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["port1"]})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
local_iface = MagicMock(pk=10)
remote_iface = MagicMock(pk=20)
link_data = {
"local_port_id": "port1",
"local_port": "Gi0/1",
"netbox_local_interface_id": 10,
"netbox_remote_interface_id": 20,
}
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.cables.Cable") as mock_cable_cls,
patch("netbox_librenms_plugin.views.sync.cables.Interface") as mock_iface_cls,
patch("netbox_librenms_plugin.views.sync.cables.transaction"),
patch("netbox_librenms_plugin.views.sync.cables.ContentType") as mock_ct,
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_ct.objects.get_for_model.return_value = MagicMock()
mock_cache.get.return_value = {"links": [link_data]}
local_iface.device_id = mock_device.id # match device_id to skip VC re-lookup
mock_iface_cls.objects.get.side_effect = [local_iface, remote_iface]
mock_cable_cls.objects.filter.return_value.exists.return_value = False
view.post(view.request, pk=1)
mock_cable_cls.objects.create.assert_called_once()
mock_msgs.success.assert_called_once()
class TestSyncCablesViewDuplicateCable:
def test_duplicate_cable_shows_warning(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["port1"]})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
link_data = {
"local_port_id": "port1",
"local_port": "Gi0/1",
"netbox_local_interface_id": 10,
"netbox_remote_interface_id": 20,
}
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.cables.Cable") as mock_cable_cls,
patch("netbox_librenms_plugin.views.sync.cables.Interface") as mock_iface_cls,
patch("netbox_librenms_plugin.views.sync.cables.transaction"),
patch("netbox_librenms_plugin.views.sync.cables.ContentType") as mock_ct,
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_ct.objects.get_for_model.return_value = MagicMock()
mock_cache.get.return_value = {"links": [link_data]}
local_iface = MagicMock(pk=10)
local_iface.device_id = mock_device.id # match device_id to skip VC re-lookup
remote_iface = MagicMock(pk=20)
mock_iface_cls.objects.get.side_effect = [local_iface, remote_iface]
mock_cable_cls.objects.filter.return_value.exists.return_value = True
view.post(view.request, pk=1)
mock_msgs.warning.assert_called_once()
class TestSyncCablesViewMissingRemote:
def test_interface_does_not_exist_shows_error(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["port1"]})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
link_data = {
"local_port_id": "port1",
"local_port": "Gi0/1",
"netbox_local_interface_id": 10,
"netbox_remote_interface_id": 20,
}
class _DNE(Exception):
pass
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.cables.Interface") as mock_iface_cls,
patch("netbox_librenms_plugin.views.sync.cables.transaction"),
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = {"links": [link_data]}
mock_iface_cls.DoesNotExist = _DNE
mock_iface_cls.objects.get.side_effect = _DNE()
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
class TestSyncCablesViewMissingLinkData:
def test_no_matching_link_data_reports_invalid(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["port_unknown"]})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.transaction"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = {"links": [{"local_port_id": "other_port"}]}
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
class TestSyncCablesViewInvalidLinkData:
def test_missing_netbox_ids_reports_invalid(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.require_all_permissions = MagicMock(return_value=None)
view.request = _make_request(post_data={"select": ["port1"]})
view.get_cache_key = MagicMock(return_value="key")
view._post_server_key = "default"
mock_device = MagicMock(pk=1)
# Missing netbox_local_interface_id
link_data = {"local_port_id": "port1", "local_port": "Gi0/1", "netbox_remote_interface_id": 20}
with (
patch("netbox_librenms_plugin.views.sync.cables.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.cables.redirect"),
patch("netbox_librenms_plugin.views.sync.cables.transaction"),
patch("netbox_librenms_plugin.views.sync.cables.reverse", return_value="/sync/"),
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = {"links": [link_data]}
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
class TestSyncCablesViewHelpers:
def test_get_selected_interfaces_returns_none_when_empty(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
req = _make_request(post_data={})
result = view.get_selected_interfaces(req, MagicMock(id=1))
assert result is None
def test_get_selected_interfaces_builds_list(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
req = _make_request(post_data={"select": ["port1", "port2"], "device_selection_port1": "5"})
result = view.get_selected_interfaces(req, MagicMock(id=1))
assert len(result) == 2
assert result[0]["local_port_id"] == "port1"
assert result[0]["device_id"] == "5"
# port2 defaults to initial_device.id
assert result[1]["device_id"] == 1
def test_validate_prerequisites_false_on_no_cache(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.request = _make_request()
with patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs:
result = view.validate_prerequisites(None, ["some"])
assert result is False
mock_msgs.error.assert_called_once()
def test_validate_prerequisites_false_on_no_selection(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.request = _make_request()
with patch("netbox_librenms_plugin.views.sync.cables.messages"):
result = view.validate_prerequisites([{"port": "x"}], None)
assert result is False
def test_verify_cable_creation_requirements_true(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
data = {"netbox_local_interface_id": 1, "netbox_remote_interface_id": 2}
assert view.verify_cable_creation_requirements(data) is True
def test_verify_cable_creation_requirements_false(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
data = {"netbox_local_interface_id": 1}
assert view.verify_cable_creation_requirements(data) is False
def test_display_sync_results_all_branches(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
req = _make_request()
results = {
"valid": ["Gi0/1"],
"invalid": ["Gi0/2"],
"duplicate": ["Gi0/3"],
"missing_remote": ["Gi0/4"],
}
with patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs:
view.display_sync_results(req, results)
assert mock_msgs.success.call_count == 1
assert mock_msgs.error.call_count == 2
assert mock_msgs.warning.call_count == 1
def test_display_sync_results_empty(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
req = _make_request()
results = {"valid": [], "invalid": [], "duplicate": [], "missing_remote": []}
with patch("netbox_librenms_plugin.views.sync.cables.messages") as mock_msgs:
view.display_sync_results(req, results)
mock_msgs.success.assert_not_called()
mock_msgs.warning.assert_not_called()
def test_get_cached_links_data_no_data(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
with (
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = None
result = view.get_cached_links_data(_make_request(), MagicMock())
assert result is None
def test_get_cached_links_data_returns_links(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
with (
patch("netbox_librenms_plugin.views.sync.cables.cache") as mock_cache,
patch.object(
type(view), "librenms_api", new_callable=lambda: property(lambda s: MagicMock(server_key="default"))
),
):
mock_cache.get.return_value = {"links": [{"local_port_id": "p"}]}
result = view.get_cached_links_data(_make_request(), MagicMock())
assert result == [{"local_port_id": "p"}]
def test_check_existing_cable(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
local = MagicMock(pk=1)
remote = MagicMock(pk=2)
with (
patch("netbox_librenms_plugin.views.sync.cables.Cable") as mock_cable_cls,
patch("netbox_librenms_plugin.views.sync.cables.ContentType") as mock_ct,
):
mock_ct.objects.get_for_model.return_value = MagicMock()
mock_cable_cls.objects.filter.return_value.exists.return_value = True
result = view.check_existing_cable(local, remote)
assert result is True
class TestSyncCablesViewProcessInterfaceSyncException:
"""Lines 147-149: outer except Exception handler in process_interface_sync."""
def test_process_single_interface_exception_caught(self):
from netbox_librenms_plugin.views.sync.cables import SyncCablesView
view = object.__new__(SyncCablesView)
view.request = _make_request()
interface = {"local_port_id": "port1"}
cached_links = []
mock_transaction = MagicMock()
# Make __exit__ NOT suppress exceptions (return False)
mock_transaction.atomic.return_value.__exit__.return_value = False
with (
patch("netbox_librenms_plugin.views.sync.cables.transaction", mock_transaction),
patch.object(view, "process_single_interface", side_effect=RuntimeError("test error")),
):
results = view.process_interface_sync([interface], cached_links)
assert "port1" in results["invalid"]
class TestAddDeviceToLibreNMSViewPermission:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=_denied_response())
view.request = _make_request(post_data={"snmp_version": "v2c"})
result = view.post(view.request, object_id=1)
assert result.status_code == 403
class TestAddDeviceToLibreNMSViewFormInvalid:
def test_invalid_form_shows_errors(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
post_data = {"v1v2-snmp_version": "v2c", "object_type": "device"}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
):
mock_device_cls.objects.get.return_value = mock_device
view.request = _make_request(post_data=post_data)
view.object = mock_device
# Provide an invalid form (missing required hostname)
view.post(view.request, object_id=1)
# Should show form errors
assert mock_msgs.error.call_count >= 0 # form validation may or may not find errors
class TestAddDeviceToLibreNMSViewFormValid:
def test_valid_v2c_form_calls_api(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
mock_api = MagicMock()
mock_api.server_key = "default"
mock_api.add_device.return_value = (True, "Device added")
mock_form = MagicMock()
mock_form.is_valid.return_value = True
mock_form.cleaned_data = {
"hostname": "router.example.com",
"snmp_version": "v2c",
"community": "public",
"force_add": False,
"port": None,
"transport": None,
"port_association_mode": None,
"poller_group": None,
}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch("netbox_librenms_plugin.views.sync.devices.AddToLIbreSNMPV1V2", return_value=mock_form),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_device_cls.objects.get.return_value = mock_device
post_data = {"v1v2-snmp_version": "v2c", "object_type": "device"}
view.request = _make_request(post_data=post_data)
view.object = mock_device
view.post(view.request, object_id=1)
mock_api.add_device.assert_called_once()
mock_msgs.success.assert_called_once()
class TestAddDeviceToLibreNMSViewFormValidExtraFields:
"""Covers lines 74-78: transport and port_association_mode optional fields."""
def test_valid_form_with_transport_and_pam(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
mock_api = MagicMock()
mock_api.server_key = "default"
mock_api.add_device.return_value = (True, "Device added")
mock_form = MagicMock()
mock_form.is_valid.return_value = True
mock_form.cleaned_data = {
"hostname": "router.example.com",
"snmp_version": "v2c",
"community": "public",
"force_add": False,
"port": 161,
"transport": "udp6",
"port_association_mode": 2,
"poller_group": None,
}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages"),
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch("netbox_librenms_plugin.views.sync.devices.AddToLIbreSNMPV1V2", return_value=mock_form),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_device_cls.objects.get.return_value = mock_device
post_data = {"v1v2-snmp_version": "v2c", "object_type": "device"}
view.request = _make_request(post_data=post_data)
view.object = mock_device
view.post(view.request, object_id=1)
call_kwargs = mock_api.add_device.call_args[0][0]
assert call_kwargs.get("transport") == "udp6"
assert call_kwargs.get("port_association_mode") == 2
def test_invalid_poller_group_ignored(self):
"""Covers lines 81-82: except (ValueError, TypeError) for non-int poller_group."""
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
mock_api = MagicMock()
mock_api.server_key = "default"
mock_api.add_device.return_value = (True, "Device added")
mock_form = MagicMock()
mock_form.is_valid.return_value = True
mock_form.cleaned_data = {
"hostname": "router.example.com",
"snmp_version": "v2c",
"community": "public",
"force_add": False,
"port": None,
"transport": None,
"port_association_mode": None,
"poller_group": "not-a-number", # Triggers except path
}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages"),
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch("netbox_librenms_plugin.views.sync.devices.AddToLIbreSNMPV1V2", return_value=mock_form),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_device_cls.objects.get.return_value = mock_device
post_data = {"v1v2-snmp_version": "v2c", "object_type": "device"}
view.request = _make_request(post_data=post_data)
view.object = mock_device
view.post(view.request, object_id=1)
call_kwargs = mock_api.add_device.call_args[0][0]
assert "poller_group" not in call_kwargs
class TestAddDeviceToLibreNMSViewV3:
def test_v3_form_submits_v3_data(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
view.request = _make_request()
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
mock_api = MagicMock()
mock_api.add_device.return_value = (False, "Error")
mock_form = MagicMock()
mock_form.is_valid.return_value = True
mock_form.cleaned_data = {
"hostname": "router.example.com",
"snmp_version": "v3",
"authlevel": "authPriv",
"authname": "user",
"authpass": "auth123",
"authalgo": "MD5",
"cryptopass": "priv123",
"cryptoalgo": "DES",
"force_add": False,
"port": None,
"transport": None,
"port_association_mode": None,
"poller_group": None,
}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch("netbox_librenms_plugin.views.sync.devices.AddToLIbreSNMPV3", return_value=mock_form),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_device_cls.objects.get.return_value = mock_device
post_data = {"v3-snmp_version": "v3", "object_type": "device"}
view.request = _make_request(post_data=post_data)
view.object = mock_device
view.post(view.request, object_id=1)
mock_api.add_device.assert_called_once()
mock_msgs.error.assert_called_once()
class TestAddDeviceToLibreNMSViewUnknownVersion:
def test_unknown_snmp_version_shows_error(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.get_absolute_url.return_value = "/device/1/"
mock_api = MagicMock()
mock_form = MagicMock()
mock_form.is_valid.return_value = True
mock_form.cleaned_data = {
"hostname": "router.example.com",
"snmp_version": "v99", # Unknown version
"force_add": False,
"port": None,
"transport": None,
"port_association_mode": None,
"poller_group": None,
}
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_device_cls,
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch("netbox_librenms_plugin.views.sync.devices.AddToLIbreSNMPV3", return_value=mock_form),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_device_cls.objects.get.return_value = mock_device
post_data = {"object_type": "device"}
view.request = _make_request(post_data=post_data)
view.object = mock_device
view.post(view.request, object_id=1)
mock_msgs.error.assert_called()
class TestAddDeviceToLibreNMSViewGetFormClass:
def test_v1v2_form_class(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView, AddToLIbreSNMPV1V2
view = object.__new__(AddDeviceToLibreNMSView)
view.request = _make_request(post_data={"snmp_version": "v2c"})
assert view.get_form_class() is AddToLIbreSNMPV1V2
def test_v3_form_class(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView, AddToLIbreSNMPV3
view = object.__new__(AddDeviceToLibreNMSView)
view.request = _make_request(post_data={"snmp_version": "v3"})
assert view.get_form_class() is AddToLIbreSNMPV3
def test_v1v2_via_prefix(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView, AddToLIbreSNMPV1V2
view = object.__new__(AddDeviceToLibreNMSView)
view.request = _make_request(post_data={"v1v2-snmp_version": "v1"})
assert view.get_form_class() is AddToLIbreSNMPV1V2
def test_get_object_virtualmachine(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
mock_vm = MagicMock()
with patch("netbox_librenms_plugin.views.sync.devices.get_object_or_404", return_value=mock_vm):
result = view.get_object(5, object_type="virtualmachine")
assert result is mock_vm
def test_get_object_device_not_found_falls_back_to_vm(self):
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
mock_vm = MagicMock()
class _DNE(Exception):
pass
with (
patch("netbox_librenms_plugin.views.sync.devices.Device") as mock_dev_cls,
patch("netbox_librenms_plugin.views.sync.devices.get_object_or_404", return_value=mock_vm),
):
mock_dev_cls.DoesNotExist = _DNE
mock_dev_cls.objects.get.side_effect = _DNE()
result = view.get_object(5)
assert result is mock_vm
def test_form_valid_with_poller_group(self):
"""poller_group valid int is passed to API."""
from netbox_librenms_plugin.views.sync.devices import AddDeviceToLibreNMSView
view = object.__new__(AddDeviceToLibreNMSView)
view.request = _make_request()
mock_api = MagicMock()
mock_api.add_device.return_value = (True, "ok")
view.object = MagicMock()
view.object.get_absolute_url.return_value = "/d/"
mock_form = MagicMock()
mock_form.cleaned_data = {
"hostname": "h.example.com",
"snmp_version": "v2c",
"community": "public",
"force_add": False,
"port": 161,
"transport": "udp",
"port_association_mode": None,
"poller_group": "2",
}
with (
patch("netbox_librenms_plugin.views.sync.devices.messages"),
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.form_valid(mock_form, snmp_version="v2c")
call_args = mock_api.add_device.call_args[0][0]
assert call_args["poller_group"] == 2
class TestUpdateDeviceLocationView:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.devices import UpdateDeviceLocationView
view = object.__new__(UpdateDeviceLocationView)
view.require_write_permission = MagicMock(return_value=_denied_response())
view.request = _make_request()
result = view.post(view.request, pk=1)
assert result.status_code == 403
def test_device_with_site_updates_location(self):
from netbox_librenms_plugin.views.sync.devices import UpdateDeviceLocationView
view = object.__new__(UpdateDeviceLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.site.name = "London"
mock_api = MagicMock()
mock_api.get_librenms_id.return_value = 42
mock_api.update_device_field.return_value = (True, "ok")
with (
patch("netbox_librenms_plugin.views.sync.devices.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.request = _make_request()
view.post(view.request, pk=1)
mock_api.update_device_field.assert_called_once()
mock_msgs.success.assert_called_once()
def test_device_with_site_api_failure(self):
from netbox_librenms_plugin.views.sync.devices import UpdateDeviceLocationView
view = object.__new__(UpdateDeviceLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.site.name = "London"
mock_api = MagicMock()
mock_api.get_librenms_id.return_value = 42
mock_api.update_device_field.return_value = (False, "Connection refused")
with (
patch("netbox_librenms_plugin.views.sync.devices.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.request = _make_request()
view.post(view.request, pk=1)
mock_msgs.error.assert_called_once()
def test_device_no_site_shows_warning(self):
from netbox_librenms_plugin.views.sync.devices import UpdateDeviceLocationView
view = object.__new__(UpdateDeviceLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_device = MagicMock()
mock_device.site = None
mock_api = MagicMock()
with (
patch("netbox_librenms_plugin.views.sync.devices.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.devices.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.devices.redirect"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.request = _make_request()
view.post(view.request, pk=1)
mock_msgs.warning.assert_called_once()
# ===========================================================================
# views/sync/ip_addresses.py — SyncIPAddressesView
# ===========================================================================
class TestSyncIPAddressesViewPermissionDenied:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=_denied_response())
view.request = _make_request()
result = view.post(view.request, object_type="device", pk=1)
assert result.status_code == 403
class TestSyncIPAddressesViewCacheMiss:
def test_cache_miss_redirects(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_device = MagicMock(pk=1)
mock_api = MagicMock(server_key="default")
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = None
view.request = _make_request(post_data={"select": ["192.168.1.1/24"]})
view.post(view.request, object_type="device", pk=1)
mock_msgs.error.assert_called_once()
class TestSyncIPAddressesViewNoSelection:
def test_no_selection_redirects(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_device = MagicMock(pk=1)
mock_api = MagicMock(server_key="default")
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"ip_addresses": [{"ip_address": "10.0.0.1"}]}
view.request = _make_request(post_data={})
view.post(view.request, object_type="device", pk=1)
mock_msgs.error.assert_called_once()
class TestSyncIPAddressesViewCreateIP:
def test_new_ip_is_created(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_device = MagicMock(pk=1)
mock_api = MagicMock(server_key="default")
ip_data = {
"ip_address": "10.0.0.1",
"ip_with_mask": "10.0.0.1/24",
"interface_url": "http://localhost/api/dcim/interfaces/5/",
}
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.IPAddress") as mock_ip_cls,
patch("netbox_librenms_plugin.views.sync.ip_addresses.Interface") as mock_iface_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"ip_addresses": [ip_data]}
mock_ip_cls.objects.filter.return_value.first.return_value = None
mock_iface_cls.objects.get.return_value = MagicMock()
view.request = _make_request(post_data={"select": ["10.0.0.1"]})
view.post(view.request, object_type="device", pk=1)
mock_ip_cls.objects.create.assert_called_once()
mock_msgs.success.assert_called()
class TestSyncIPAddressesViewUpdateIP:
def test_existing_ip_updated_when_interface_differs(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_device = MagicMock(pk=1)
mock_api = MagicMock(server_key="default")
ip_data = {
"ip_address": "10.0.0.1",
"ip_with_mask": "10.0.0.1/24",
"interface_url": "http://localhost/api/dcim/interfaces/5/",
}
mock_existing_ip = MagicMock()
mock_existing_ip.assigned_object = MagicMock() # Different from mock_iface
mock_existing_ip.vrf = None
mock_iface = MagicMock()
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.IPAddress") as mock_ip_cls,
patch("netbox_librenms_plugin.views.sync.ip_addresses.Interface") as mock_iface_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"ip_addresses": [ip_data]}
mock_ip_cls.objects.filter.return_value.first.return_value = mock_existing_ip
mock_iface_cls.objects.get.return_value = mock_iface
view.request = _make_request(post_data={"select": ["10.0.0.1"]})
view.post(view.request, object_type="device", pk=1)
mock_existing_ip.save.assert_called_once()
mock_msgs.success.assert_called()
class TestSyncIPAddressesViewUnchangedIP:
def test_unchanged_ip_shows_warning(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_device = MagicMock(pk=1)
mock_api = MagicMock(server_key="default")
ip_data = {
"ip_address": "10.0.0.1",
"ip_with_mask": "10.0.0.1/24",
"interface_url": None,
}
mock_existing_ip = MagicMock()
mock_existing_ip.assigned_object = None
mock_existing_ip.vrf = None
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.IPAddress") as mock_ip_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"ip_addresses": [ip_data]}
mock_ip_cls.objects.filter.return_value.first.return_value = mock_existing_ip
view.request = _make_request(post_data={"select": ["10.0.0.1"]})
view.post(view.request, object_type="device", pk=1)
mock_msgs.warning.assert_called()
class TestSyncIPAddressesViewHelpers:
def test_get_object_device(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
mock_device = MagicMock()
with patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_device):
result = view.get_object("device", 1)
assert result is mock_device
def test_get_object_virtualmachine(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
mock_vm = MagicMock()
with patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_vm):
result = view.get_object("virtualmachine", 1)
assert result is mock_vm
def test_get_object_invalid_type_raises(self):
from django.http import Http404
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
try:
view.get_object("unknown", 1)
assert False, "Should have raised Http404"
except Http404:
pass
def test_get_vrf_selection_none_when_no_vrf(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
req = _make_request(post_data={})
result = view.get_vrf_selection(req, "10.0.0.1")
assert result is None
def test_get_vrf_selection_returns_vrf(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
mock_vrf = MagicMock()
req = _make_request(post_data={"vrf_10.0.0.1": "3"})
with patch("netbox_librenms_plugin.views.sync.ip_addresses.VRF") as mock_vrf_cls:
mock_vrf_cls.objects.get.return_value = mock_vrf
result = view.get_vrf_selection(req, "10.0.0.1")
assert result is mock_vrf
def test_get_vrf_selection_not_found_returns_none(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
req = _make_request(post_data={"vrf_10.0.0.1": "99"})
class _DNE(Exception):
pass
with patch("netbox_librenms_plugin.views.sync.ip_addresses.VRF") as mock_vrf_cls:
mock_vrf_cls.DoesNotExist = _DNE
mock_vrf_cls.objects.get.side_effect = _DNE()
result = view.get_vrf_selection(req, "10.0.0.1")
assert result is None
def test_get_ip_tab_url_device(self):
from dcim.models import Device
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view._post_server_key = "prod"
mock_api = MagicMock(server_key="prod")
mock_device = MagicMock(spec=Device, pk=5)
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/device/5/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
url = view.get_ip_tab_url(mock_device)
assert "ipaddresses" in url
assert "server_key=prod" in url
def test_display_sync_results_all_branches(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
req = _make_request()
results = {
"created": ["10.0.0.1"],
"updated": ["10.0.0.2"],
"unchanged": ["10.0.0.3"],
"failed": ["10.0.0.4"],
}
with patch("netbox_librenms_plugin.views.sync.ip_addresses.messages") as mock_msgs:
view.display_sync_results(req, results)
assert mock_msgs.success.call_count == 2
assert mock_msgs.warning.call_count == 1
assert mock_msgs.error.call_count == 1
def test_get_ip_tab_url_vm(self):
from virtualization.models import VirtualMachine
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view._post_server_key = "default"
mock_api = MagicMock(server_key="default")
mock_vm = MagicMock(spec=VirtualMachine, pk=7)
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/vm/7/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
url = view.get_ip_tab_url(mock_vm)
assert "ipaddresses" in url
def test_get_ip_tab_url_no_server_key(self):
from dcim.models import Device
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view._post_server_key = None
mock_api = MagicMock(server_key=None)
mock_device = MagicMock(spec=Device, pk=5)
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/device/5/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
url = view.get_ip_tab_url(mock_device)
assert "server_key" not in url
class TestSyncIPAddressesViewVMInterface:
def test_vm_interface_resolved(self):
from netbox_librenms_plugin.views.sync.ip_addresses import SyncIPAddressesView
view = object.__new__(SyncIPAddressesView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_vm = MagicMock()
mock_vm.pk = 2
mock_api = MagicMock(server_key="default")
ip_data = {
"ip_address": "10.0.0.5",
"ip_with_mask": "10.0.0.5/24",
"interface_url": "http://localhost/api/virtualization/interfaces/9/",
}
with (
patch("netbox_librenms_plugin.views.sync.ip_addresses.get_object_or_404", return_value=mock_vm),
patch("netbox_librenms_plugin.views.sync.ip_addresses.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.ip_addresses.messages"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.redirect"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.transaction"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.ip_addresses.IPAddress") as mock_ip_cls,
patch("netbox_librenms_plugin.views.sync.ip_addresses.VMInterface") as mock_vmiface_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"ip_addresses": [ip_data]}
mock_ip_cls.objects.filter.return_value.first.return_value = None
mock_vmiface_cls.objects.get.return_value = MagicMock()
view.request = _make_request(post_data={"select": ["10.0.0.5"]})
view.post(view.request, object_type="virtualmachine", pk=2)
mock_vmiface_cls.objects.get.assert_called_once()
# ===========================================================================
# views/sync/vlans.py — SyncVLANsView
# ===========================================================================
class TestSyncVLANsViewPermissionDenied:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=_denied_response())
view.request = _make_request()
result = view.post(view.request, object_type="device", object_id=1)
assert result.status_code == 403
class TestSyncVLANsViewInvalidAction:
def test_invalid_action_shows_error(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.request = _make_request(post_data={"action": "bad_action"})
view.post(view.request, object_type="device", object_id=1)
mock_msgs.error.assert_called_once()
class TestSyncVLANsViewNoSelection:
def test_no_selection_shows_error(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = {"some": "data"}
view.request = _make_request(post_data={"action": "create_vlans"})
view.post(view.request, object_type="device", object_id=1)
mock_msgs.error.assert_called_once()
class TestSyncVLANsViewCacheMiss:
def test_cache_miss_shows_error(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = None
view.request = _make_request(post_data={"action": "create_vlans", "select": ["100"]})
view.post(view.request, object_type="device", object_id=1)
mock_msgs.error.assert_called_once()
class TestSyncVLANsViewCreateVLAN:
def test_new_vlan_created(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan = MagicMock()
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Management"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, True)
view.request = _make_request(post_data={"action": "create_vlans", "select": ["100"]})
view.post(view.request, object_type="device", object_id=1)
mock_vlan_cls.objects.get_or_create.assert_called_once()
mock_msgs.success.assert_called_once()
class TestSyncVLANsViewUpdateVLAN:
def test_existing_vlan_name_updated(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan = MagicMock()
mock_vlan.name = "OldName"
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Management"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, False)
view.request = _make_request(post_data={"action": "create_vlans", "select": ["100"]})
view.post(view.request, object_type="device", object_id=1)
mock_vlan.save.assert_called_once()
class TestSyncVLANsViewUnchangedVLAN:
def test_unchanged_vlan_counts_as_skipped(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan = MagicMock()
mock_vlan.name = "Management" # Same name as librenms → unchanged
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Management"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, False)
view.request = _make_request(post_data={"action": "create_vlans", "select": ["100"]})
view.post(view.request, object_type="device", object_id=1)
mock_msgs.success.assert_called()
assert "unchanged" in str(mock_msgs.success.call_args_list)
class TestSyncVLANsViewWithGroup:
def test_vlan_created_in_group(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan_group = MagicMock(pk=3)
mock_vlan = MagicMock()
librenms_vlans = [{"vlan_vlan": 200, "vlan_name": "Production"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch("netbox_librenms_plugin.views.sync.vlans.VLANGroup") as mock_group_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_group_cls.objects.get.return_value = mock_vlan_group
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, True)
view.request = _make_request(post_data={"action": "create_vlans", "select": ["200"], "vlan_group_200": "3"})
view.post(view.request, object_type="device", object_id=1)
call_kwargs = mock_vlan_cls.objects.get_or_create.call_args[1]
assert call_kwargs.get("group") is mock_vlan_group or mock_vlan_cls.objects.get_or_create.called
def test_invalid_vlan_group_id_falls_back_to_global(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan = MagicMock()
librenms_vlans = [{"vlan_vlan": 200, "vlan_name": "Production"}]
class _DNE(Exception):
pass
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch("netbox_librenms_plugin.views.sync.vlans.VLANGroup") as mock_group_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_group_cls.DoesNotExist = _DNE
mock_group_cls.objects.get.side_effect = _DNE()
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, True)
view.request = _make_request(
post_data={"action": "create_vlans", "select": ["200"], "vlan_group_200": "99"}
)
view.post(view.request, object_type="device", object_id=1)
# Falls back to global VLAN (group=None)
call_kwargs = mock_vlan_cls.objects.get_or_create.call_args[1]
assert call_kwargs.get("group") is None
def test_invalid_vid_string_skipped(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Mgmt"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
# "not-a-vid" → int() fails → skip
view.request = _make_request(post_data={"action": "create_vlans", "select": ["not-a-vid"]})
view.post(view.request, object_type="device", object_id=1)
# no VLAN created, shows "no VLANs" warning
mock_vlan_cls.objects.get_or_create.assert_not_called()
def test_unknown_vid_in_cache_skipped(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Mgmt"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
# VID 999 not in cached vlans → skipped
view.request = _make_request(post_data={"action": "create_vlans", "select": ["999"]})
view.post(view.request, object_type="device", object_id=1)
mock_vlan_cls.objects.get_or_create.assert_not_called()
def test_no_vlans_shows_warning(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
librenms_vlans = [{"vlan_vlan": 100, "vlan_name": "Mgmt"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
view.request = _make_request(post_data={"action": "create_vlans", "select": ["not-a-vid"]})
view.post(view.request, object_type="device", object_id=1)
mock_msgs.warning.assert_called()
class TestSyncVLANsViewGroupedUpdateSkip:
"""Lines 134-139: grouped VLAN update (elif) and unchanged (else) paths."""
def test_grouped_vlan_name_updated(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan_group = MagicMock(pk=3)
mock_vlan = MagicMock()
mock_vlan.name = "OldGroupedName" # Different from librenms → update path
librenms_vlans = [{"vlan_vlan": 300, "vlan_name": "NewGroupedName"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch("netbox_librenms_plugin.views.sync.vlans.VLANGroup") as mock_group_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_group_cls.objects.get.return_value = mock_vlan_group
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, False) # exists
view.request = _make_request(post_data={"action": "create_vlans", "select": ["300"], "vlan_group_300": "3"})
view.post(view.request, object_type="device", object_id=1)
mock_vlan.save.assert_called_once()
def test_grouped_vlan_unchanged_skipped(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view.require_all_permissions = MagicMock(return_value=None)
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
mock_api = MagicMock(server_key="default")
mock_device = MagicMock(pk=1)
mock_vlan_group = MagicMock(pk=3)
mock_vlan = MagicMock()
mock_vlan.name = "SameName" # Same name → skipped_count path
librenms_vlans = [{"vlan_vlan": 300, "vlan_name": "SameName"}]
with (
patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device),
patch("netbox_librenms_plugin.views.sync.vlans.cache") as mock_cache,
patch("netbox_librenms_plugin.views.sync.vlans.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.vlans.redirect"),
patch("netbox_librenms_plugin.views.sync.vlans.transaction"),
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.VLAN") as mock_vlan_cls,
patch("netbox_librenms_plugin.views.sync.vlans.VLANGroup") as mock_group_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_cache.get.return_value = librenms_vlans
mock_group_cls.objects.get.return_value = mock_vlan_group
mock_vlan_cls.objects.get_or_create.return_value = (mock_vlan, False)
view.request = _make_request(post_data={"action": "create_vlans", "select": ["300"], "vlan_group_300": "3"})
view.post(view.request, object_type="device", object_id=1)
mock_vlan.save.assert_not_called()
mock_msgs.success.assert_called()
assert "unchanged" in str(mock_msgs.success.call_args_list)
def test_get_object_device(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
mock_device = MagicMock()
with patch("netbox_librenms_plugin.views.sync.vlans.get_object_or_404", return_value=mock_device):
result = view.get_object("device", 1)
assert result is mock_device
def test_get_object_invalid_raises(self):
from django.http import Http404
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
try:
view.get_object("vm_type", 1)
assert False, "Should have raised Http404"
except Http404:
pass
def test_redirect_with_server_key(self):
from netbox_librenms_plugin.views.sync.vlans import SyncVLANsView
view = object.__new__(SyncVLANsView)
view._post_server_key = "production"
mock_api = MagicMock(server_key="production")
with (
patch("netbox_librenms_plugin.views.sync.vlans.reverse", return_value="/device/1/sync/"),
patch("netbox_librenms_plugin.views.sync.vlans.redirect") as mock_redirect,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view._redirect("device", 1)
call_url = mock_redirect.call_args[0][0]
assert "server_key=production" in call_url
# ===========================================================================
# views/sync/locations.py — SyncSiteLocationView
# ===========================================================================
class TestSyncSiteLocationViewPost:
def test_permission_denied_returns_early(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=_denied_response())
view.request = _make_request(post_data={"action": "create", "pk": "1"})
result = view.post(view.request)
assert result.status_code == 403
def test_missing_pk_shows_error(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
view.request = _make_request(post_data={"action": "create"})
view.post(view.request)
mock_msgs.error.assert_called_once()
def test_site_not_found_shows_error(self):
from django.core.exceptions import ObjectDoesNotExist
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.side_effect = ObjectDoesNotExist()
view.request = _make_request(post_data={"action": "create", "pk": "99"})
view.post(view.request)
mock_msgs.error.assert_called_once()
def test_unknown_action_shows_error(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_site = MagicMock()
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "banana", "pk": "1"})
view.post(view.request)
mock_msgs.error.assert_called_once()
class TestSyncSiteLocationViewCreate:
def test_create_without_coords_shows_warning(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_site = MagicMock()
mock_site.name = "London"
mock_site.latitude = None
mock_site.longitude = None
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "create", "pk": "1"})
view.post(view.request)
mock_msgs.warning.assert_called_once()
def test_create_success(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.add_location.return_value = (True, "ok")
mock_site = MagicMock()
mock_site.name = "London"
mock_site.latitude = 51.5
mock_site.longitude = -0.12
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "create", "pk": "1"})
view.post(view.request)
mock_msgs.success.assert_called_once()
def test_create_failure(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.add_location.return_value = (False, "Server error")
mock_site = MagicMock()
mock_site.name = "London"
mock_site.latitude = 51.5
mock_site.longitude = -0.12
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "create", "pk": "1"})
view.post(view.request)
mock_msgs.error.assert_called_once()
class TestSyncSiteLocationViewUpdate:
def test_update_without_coords_shows_warning(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_site = MagicMock()
mock_site.name = "Berlin"
mock_site.latitude = None
mock_site.longitude = 13.4
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "update", "pk": "1"})
view.post(view.request)
mock_msgs.warning.assert_called_once()
def test_update_api_failure_fetching_locations(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.get_locations.return_value = (False, "Connection error")
mock_site = MagicMock()
mock_site.name = "Berlin"
mock_site.latitude = 52.5
mock_site.longitude = 13.4
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "update", "pk": "1"})
view.post(view.request)
mock_msgs.error.assert_called_once()
def test_update_no_matching_location(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.get_locations.return_value = (True, [{"location": "Paris"}]) # No Berlin
mock_site = MagicMock()
mock_site.name = "Berlin"
mock_site.slug = "berlin"
mock_site.latitude = 52.5
mock_site.longitude = 13.4
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "update", "pk": "1"})
view.post(view.request)
mock_msgs.error.assert_called_once()
def test_update_success(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.get_locations.return_value = (True, [{"location": "Berlin"}])
mock_api.update_location.return_value = (True, "ok")
mock_site = MagicMock()
mock_site.name = "Berlin"
mock_site.slug = "berlin"
mock_site.latitude = 52.5
mock_site.longitude = 13.4
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "update", "pk": "1"})
view.post(view.request)
mock_msgs.success.assert_called_once()
def test_update_failure(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.require_write_permission = MagicMock(return_value=None)
mock_api = MagicMock()
mock_api.get_locations.return_value = (True, [{"location": "Berlin"}])
mock_api.update_location.return_value = (False, "Not found in LibreNMS")
mock_site = MagicMock()
mock_site.name = "Berlin"
mock_site.slug = "berlin"
mock_site.latitude = 52.5
mock_site.longitude = 13.4
with (
patch("netbox_librenms_plugin.views.sync.locations.messages") as mock_msgs,
patch("netbox_librenms_plugin.views.sync.locations.redirect"),
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.get.return_value = mock_site
view.request = _make_request(post_data={"action": "update", "pk": "1"})
view.post(view.request)
mock_msgs.error.assert_called_once()
class TestSyncSiteLocationViewHelpers:
def test_match_site_by_name(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "London"
site.slug = "london"
locations = [{"location": "London"}, {"location": "Berlin"}]
result = view.match_site_with_location(site, locations)
assert result["location"] == "London"
def test_match_site_by_slug(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "LondonDC"
site.slug = "london"
locations = [{"location": "london"}]
result = view.match_site_with_location(site, locations)
assert result is not None
def test_match_site_not_found(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "Tokyo"
site.slug = "tokyo"
locations = [{"location": "London"}]
result = view.match_site_with_location(site, locations)
assert result is None
def test_check_coordinates_match_true(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
assert view.check_coordinates_match(51.5, -0.12, "51.5", "-0.12") is True
def test_check_coordinates_match_false(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
assert view.check_coordinates_match(51.5, -0.12, "52.0", "-0.12") is False
def test_check_coordinates_none_returns_false(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
assert view.check_coordinates_match(None, -0.12, "51.5", "-0.12") is False
def test_build_location_data_with_name(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "London"
site.latitude = "51.5"
site.longitude = "-0.12"
data = view.build_location_data(site)
assert data["location"] == "London"
assert data["lat"] == "51.5"
def test_build_location_data_without_name(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "London"
site.latitude = "51.5"
site.longitude = "-0.12"
data = view.build_location_data(site, include_name=False)
assert "location" not in data
def test_get_site_by_pk_returns_none_on_not_found(self):
from django.core.exceptions import ObjectDoesNotExist
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
with patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls:
mock_site_cls.objects.get.side_effect = ObjectDoesNotExist()
result = view.get_site_by_pk(99)
assert result is None
def test_get_queryset_api_failure_returns_empty(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
view.request = _make_request()
view.filterset = None
mock_api = MagicMock()
mock_api.get_locations.return_value = (False, "Error")
with (
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.all.return_value = []
result = view.get_queryset()
assert result == []
def test_create_sync_data_no_match(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "Atlantis"
site.slug = "atlantis"
locations = [{"location": "London"}]
sync_data = view.create_sync_data(site, locations)
assert sync_data.is_synced is False
assert sync_data.librenms_location is None
def test_create_sync_data_with_match(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
site = MagicMock()
site.name = "London"
site.slug = "london"
site.latitude = 51.5
site.longitude = -0.12
locations = [{"location": "London", "lat": "51.5", "lng": "-0.12"}]
sync_data = view.create_sync_data(site, locations)
assert sync_data.is_synced is True
class TestSyncSiteLocationViewSuperMethods:
"""Lines 26-28, 32-35: get_table and get_context_data (call super())."""
def test_get_table_configures_table(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
mock_table = MagicMock()
mock_request = MagicMock()
view.request = mock_request
with patch("netbox_librenms_plugin.views.sync.locations.SingleTableView.get_table", return_value=mock_table):
result = view.get_table()
mock_table.configure.assert_called_once_with(mock_request)
assert result is mock_table
def test_get_context_data_adds_filter_form(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
mock_queryset = MagicMock()
mock_filterset_cls = MagicMock()
mock_filterset_instance = MagicMock()
mock_filterset_cls.return_value = mock_filterset_instance
view.filterset = mock_filterset_cls
view.request = MagicMock()
view.request.GET = {}
view.get_queryset = MagicMock(return_value=mock_queryset)
parent_ctx = {"some_key": "some_value"}
with patch(
"netbox_librenms_plugin.views.sync.locations.SingleTableView.get_context_data", return_value=parent_ctx
):
result = view.get_context_data()
assert "filter_form" in result
assert result["filter_form"] is mock_filterset_instance.form
class TestSyncSiteLocationViewGetQuerysetFilterset:
"""Lines 44-49: filterset branch in get_queryset."""
def test_get_queryset_with_filterset_and_get_params(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
mock_api = MagicMock(server_key="default")
mock_request = MagicMock()
mock_request.GET = {"name": "London"}
view.request = mock_request
view.filterset = MagicMock()
view._post_server_key = "default"
view.get_cache_key = MagicMock(return_value="k")
view.get_librenms_locations = MagicMock(return_value=(True, [{"location": "London"}]))
view.create_sync_data = MagicMock(side_effect=lambda s, _locs: s)
mock_site = MagicMock()
mock_site.name = "London"
mock_qs = MagicMock()
view.filterset.return_value.qs = mock_qs
with (
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.all.return_value = [mock_site]
result = view.get_queryset()
assert result is mock_qs
def test_get_queryset_no_get_params_returns_list(self):
from netbox_librenms_plugin.views.sync.locations import SyncSiteLocationView
view = object.__new__(SyncSiteLocationView)
mock_api = MagicMock(server_key="default")
mock_request = MagicMock()
mock_request.GET = {} # Empty GET → falls through to return sync_data (line 49)
view.request = mock_request
view.filterset = None # Falsy → line 47 skipped
view.get_librenms_locations = MagicMock(return_value=(True, [{"location": "London"}]))
mock_site = MagicMock()
mock_site.name = "London"
view.create_sync_data = MagicMock(side_effect=lambda s, _locs: s)
with (
patch("netbox_librenms_plugin.views.sync.locations.Site") as mock_site_cls,
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
):
mock_site_cls.objects.all.return_value = [mock_site]
result = view.get_queryset()
assert result == [mock_site]