1194 lines
48 KiB
Python
1194 lines
48 KiB
Python
"""
|
|
Coverage tests for views/sync/interfaces.py
|
|
|
|
SyncInterfacesView + DeleteNetBoxInterfacesView
|
|
Target: 95%+ coverage
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Helpers
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def _make_request(post_data=None, get_data=None):
|
|
"""Build a mock request with proper POST / GET dicts."""
|
|
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[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 {}
|
|
return req
|
|
|
|
|
|
def _denied_response():
|
|
resp = MagicMock()
|
|
resp.status_code = 403
|
|
return resp
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.get_required_permissions_for_object_type
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewPermissions:
|
|
def test_device_type_returns_interface_perms(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
from dcim.models import Interface
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
perms = view.get_required_permissions_for_object_type("device")
|
|
assert ("change", Interface) in perms
|
|
|
|
def test_vm_type_returns_vminterface_perms(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
from virtualization.models import VMInterface
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
perms = view.get_required_permissions_for_object_type("virtualmachine")
|
|
assert ("change", VMInterface) in perms
|
|
|
|
def test_invalid_type_raises_http404(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
from django.http import Http404
|
|
import pytest
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
with pytest.raises(Http404):
|
|
view.get_required_permissions_for_object_type("invalid")
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.get_object
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewGetObject:
|
|
def test_get_device(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
mock_device = MagicMock()
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device):
|
|
result = view.get_object("device", 1)
|
|
assert result is mock_device
|
|
|
|
def test_get_vm(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
mock_vm = MagicMock()
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_vm):
|
|
result = view.get_object("virtualmachine", 2)
|
|
assert result is mock_vm
|
|
|
|
def test_invalid_type_raises_http404(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
from django.http import Http404
|
|
import pytest
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
with pytest.raises(Http404):
|
|
view.get_object("invalid", 1)
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.get_selected_interfaces
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewGetSelectedInterfaces:
|
|
def test_empty_selection_returns_none(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
req = _make_request(post_data={})
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.messages") as mock_msgs:
|
|
result = view.get_selected_interfaces(req, "ifName")
|
|
assert result is None
|
|
mock_msgs.error.assert_called_once()
|
|
|
|
def test_with_selection_returns_list(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
req = _make_request(post_data={"select": ["Gi0/1", "Gi0/2"]})
|
|
result = view.get_selected_interfaces(req, "ifName")
|
|
assert result == ["Gi0/1", "Gi0/2"]
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.get_cached_ports_data
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewGetCachedPortsData:
|
|
def test_cache_miss_warns_and_returns_none(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.get_cache_key = MagicMock(return_value="k")
|
|
req = _make_request()
|
|
mock_obj = MagicMock(pk=1)
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages") as mock_msgs,
|
|
):
|
|
mock_cache.get.return_value = None
|
|
result = view.get_cached_ports_data(req, mock_obj, "default")
|
|
|
|
assert result is None
|
|
mock_msgs.warning.assert_called_once()
|
|
|
|
def test_cache_hit_returns_ports(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.get_cache_key = MagicMock(return_value="k")
|
|
req = _make_request()
|
|
mock_obj = MagicMock(pk=1)
|
|
ports = [{"ifName": "Gi0/1"}]
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache:
|
|
mock_cache.get.return_value = {"ports": ports}
|
|
result = view.get_cached_ports_data(req, mock_obj, "default")
|
|
|
|
assert result == ports
|
|
|
|
def test_no_server_key_uses_librenms_api(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.get_cache_key = MagicMock(return_value="k")
|
|
mock_api = MagicMock(server_key="mykey")
|
|
req = _make_request()
|
|
mock_obj = MagicMock(pk=1)
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages"),
|
|
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
|
|
):
|
|
mock_cache.get.return_value = None
|
|
view.get_cached_ports_data(req, mock_obj, None)
|
|
|
|
# get_cache_key should have been called with (obj, "ports", resolved_server_key)
|
|
view.get_cache_key.assert_called_once_with(mock_obj, "ports", "mykey")
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.post — full flows
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewPost:
|
|
def test_permission_denied_returns_early(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.require_all_permissions = MagicMock(return_value=_denied_response())
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
req = _make_request(post_data={"select": ["Gi0/1"]})
|
|
result = view.post(req, "device", 1)
|
|
assert result.status_code == 403
|
|
|
|
def test_no_selection_redirects(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.require_all_permissions = MagicMock(return_value=None)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
mock_api = MagicMock(server_key="default")
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
req = _make_request(post_data={}) # No selection
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_interface_name_field", return_value="ifName"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages") as mock_msgs,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.redirect") as mock_redirect,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.reverse", return_value="/sync/"),
|
|
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
|
|
):
|
|
view.post(req, "device", 1)
|
|
|
|
mock_msgs.error.assert_called_once()
|
|
mock_redirect.assert_called_once()
|
|
|
|
def test_cache_miss_redirects(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.require_all_permissions = MagicMock(return_value=None)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
mock_api = MagicMock(server_key="default")
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
req = _make_request(post_data={"select": ["Gi0/1"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_interface_name_field", return_value="ifName"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.redirect") as mock_redirect,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.reverse", return_value="/sync/"),
|
|
patch.object(type(view), "get_vlan_groups_for_device", return_value=[]),
|
|
patch.object(view.__class__, "get_cache_key", return_value="k"),
|
|
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
|
|
):
|
|
mock_cache.get.return_value = None
|
|
view.post(req, "device", 1)
|
|
|
|
mock_redirect.assert_called()
|
|
|
|
def test_device_post_success(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.require_all_permissions = MagicMock(return_value=None)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
mock_api = MagicMock(server_key="default")
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
ports = [{"ifName": "Gi0/1", "port_id": 10}]
|
|
req = _make_request(post_data={"select": ["Gi0/1"], "server_key": "default"})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_interface_name_field", return_value="ifName"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages") as mock_msgs,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.redirect") as mock_redirect,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.reverse", return_value="/sync/"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
patch.object(view, "sync_interface"),
|
|
patch.object(type(view), "get_vlan_groups_for_device", return_value=[]),
|
|
patch.object(view.__class__, "get_cache_key", return_value="k"),
|
|
patch.object(view.__class__, "_build_vlan_lookup_maps", return_value={}),
|
|
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
|
|
):
|
|
mock_cache.get.return_value = {"ports": ports}
|
|
view.post(req, "device", 1)
|
|
|
|
mock_msgs.success.assert_called_once()
|
|
mock_redirect.assert_called_once()
|
|
|
|
def test_vm_post_success(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.require_all_permissions = MagicMock(return_value=None)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
mock_api = MagicMock(server_key="default")
|
|
|
|
mock_vm = MagicMock(pk=5)
|
|
ports = [{"ifName": "eth0", "port_id": 20}]
|
|
req = _make_request(post_data={"select": ["eth0"], "server_key": "default"})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_vm),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_interface_name_field", return_value="ifName"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.cache") as mock_cache,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.messages") as mock_msgs,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.redirect") as mock_redirect,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.reverse", return_value="/sync/"),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
patch.object(view, "sync_interface"),
|
|
patch.object(type(view), "get_vlan_groups_for_device", return_value=[]),
|
|
patch.object(view.__class__, "get_cache_key", return_value="k"),
|
|
patch.object(view.__class__, "_build_vlan_lookup_maps", return_value={}),
|
|
patch.object(type(view), "librenms_api", new_callable=lambda: property(lambda s: mock_api)),
|
|
):
|
|
mock_cache.get.return_value = {"ports": ports}
|
|
view.post(req, "virtualmachine", 5)
|
|
|
|
mock_msgs.success.assert_called_once()
|
|
mock_redirect.assert_called_once()
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.sync_interface — Device paths
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewSyncInterfaceDevice:
|
|
def _make_view(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request()
|
|
view._post_server_key = "default"
|
|
view._lookup_maps = {}
|
|
view.interface_name_field = "ifName"
|
|
view.update_interface_attributes = MagicMock()
|
|
view._sync_interface_vlans = MagicMock()
|
|
return view
|
|
|
|
def test_device_interface_created(self):
|
|
from dcim.models import Device
|
|
|
|
view = self._make_view()
|
|
# __class__ = Device makes isinstance(mock_device, Device) → True
|
|
mock_device = MagicMock()
|
|
mock_device.__class__ = Device
|
|
mock_device.virtual_chassis = None
|
|
|
|
mock_interface = MagicMock()
|
|
librenms_port = {"ifName": "Gi0/1", "port_id": None}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls:
|
|
mock_intf_cls.objects.get_or_create.return_value = (mock_interface, True)
|
|
view.get_netbox_interface_type = MagicMock(return_value="1000base-t")
|
|
view.sync_interface(mock_device, librenms_port, [], "ifName")
|
|
|
|
mock_intf_cls.objects.get_or_create.assert_called_once()
|
|
view.update_interface_attributes.assert_called_once()
|
|
|
|
def test_device_selection_with_vc_valid(self):
|
|
from dcim.models import Device
|
|
|
|
view = self._make_view()
|
|
view.request = _make_request(post_data={"device_selection_Gi0/1": "2"})
|
|
|
|
mock_device = MagicMock()
|
|
mock_device.__class__ = Device
|
|
mock_device.id = 1
|
|
mock_vc = MagicMock()
|
|
mock_vc.members.values_list.return_value = [1, 2]
|
|
mock_device.virtual_chassis = mock_vc
|
|
|
|
mock_target_device = MagicMock()
|
|
mock_target_device.__class__ = Device
|
|
mock_target_device.id = 2
|
|
|
|
mock_interface = MagicMock()
|
|
librenms_port = {"ifName": "Gi0/1", "port_id": None}
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch.object(Device, "objects") as mock_device_objects,
|
|
):
|
|
mock_device_objects.get.return_value = mock_target_device
|
|
mock_intf_cls.objects.get_or_create.return_value = (mock_interface, True)
|
|
view.get_netbox_interface_type = MagicMock(return_value="other")
|
|
view.sync_interface(mock_device, librenms_port, [], "ifName")
|
|
|
|
mock_intf_cls.objects.get_or_create.assert_called_once_with(device=mock_target_device, name="Gi0/1")
|
|
|
|
def test_device_selection_invalid_defaults_to_obj(self):
|
|
from dcim.models import Device
|
|
|
|
view = self._make_view()
|
|
view.request = _make_request(post_data={"device_selection_Gi0/1": "99"})
|
|
|
|
mock_device = MagicMock()
|
|
mock_device.__class__ = Device
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
mock_other_device = MagicMock()
|
|
mock_other_device.__class__ = Device
|
|
mock_other_device.id = 99 # Different id, no VC → falls back to obj
|
|
|
|
mock_interface = MagicMock()
|
|
librenms_port = {"ifName": "Gi0/1", "port_id": None}
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch.object(Device, "objects") as mock_device_objects,
|
|
):
|
|
mock_device_objects.get.return_value = mock_other_device
|
|
mock_intf_cls.objects.get_or_create.return_value = (mock_interface, True)
|
|
view.get_netbox_interface_type = MagicMock(return_value="other")
|
|
view.sync_interface(mock_device, librenms_port, [], "ifName")
|
|
|
|
# Should use mock_device (obj), not mock_other_device
|
|
call_kwargs = mock_intf_cls.objects.get_or_create.call_args[1]
|
|
assert call_kwargs["device"] is mock_device
|
|
|
|
def test_device_selection_does_not_exist_defaults_to_obj(self):
|
|
from dcim.models import Device
|
|
|
|
view = self._make_view()
|
|
view.request = _make_request(post_data={"device_selection_Gi0/1": "999"})
|
|
|
|
mock_device = MagicMock()
|
|
mock_device.__class__ = Device
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
mock_interface = MagicMock()
|
|
librenms_port = {"ifName": "Gi0/1", "port_id": None}
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch.object(Device, "objects") as mock_device_objects,
|
|
):
|
|
mock_device_objects.get.side_effect = Device.DoesNotExist()
|
|
mock_intf_cls.objects.get_or_create.return_value = (mock_interface, True)
|
|
view.get_netbox_interface_type = MagicMock(return_value="other")
|
|
view.sync_interface(mock_device, librenms_port, [], "ifName")
|
|
|
|
call_kwargs = mock_intf_cls.objects.get_or_create.call_args[1]
|
|
assert call_kwargs["device"] is mock_device
|
|
|
|
|
|
class TestSyncInterfacesViewSyncInterfaceVM:
|
|
def test_vm_interface_created(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
from virtualization.models import VirtualMachine
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request()
|
|
view._post_server_key = "default"
|
|
view._lookup_maps = {}
|
|
view.interface_name_field = "ifName"
|
|
view.update_interface_attributes = MagicMock()
|
|
view._sync_interface_vlans = MagicMock()
|
|
|
|
mock_vm = MagicMock()
|
|
mock_vm.__class__ = VirtualMachine
|
|
mock_vm_interface = MagicMock()
|
|
librenms_port = {"ifName": "eth0", "port_id": None}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.VMInterface") as mock_vmintf_cls:
|
|
mock_vmintf_cls.objects.get_or_create.return_value = (mock_vm_interface, True)
|
|
view.sync_interface(mock_vm, librenms_port, [], "ifName")
|
|
|
|
mock_vmintf_cls.objects.get_or_create.assert_called_once()
|
|
view.update_interface_attributes.assert_called_once()
|
|
|
|
def test_invalid_obj_raises_value_error(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
import pytest
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request()
|
|
librenms_port = {"ifName": "eth0"}
|
|
|
|
with pytest.raises(ValueError):
|
|
view.sync_interface(MagicMock(), librenms_port, [], "ifName")
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.get_netbox_interface_type
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewGetNetboxInterfaceType:
|
|
def test_with_speed_uses_speed_mapping(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
mock_mapping = MagicMock()
|
|
mock_mapping.netbox_type = "1000base-t"
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=1000),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.InterfaceTypeMapping") as mock_cls,
|
|
):
|
|
# mappings = objects.filter(...); speed_mapping = mappings.filter(...).order_by(...).first()
|
|
mock_cls.objects.filter.return_value.filter.return_value.order_by.return_value.first.return_value = (
|
|
mock_mapping
|
|
)
|
|
result = view.get_netbox_interface_type({"ifType": "ethernetCsmacd", "ifSpeed": 1000000000})
|
|
|
|
assert result == "1000base-t"
|
|
|
|
def test_no_speed_uses_null_mapping(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
mock_mapping = MagicMock()
|
|
mock_mapping.netbox_type = "virtual"
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=None),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.InterfaceTypeMapping") as mock_cls,
|
|
):
|
|
mock_qs = MagicMock()
|
|
mock_qs.filter.return_value.first.return_value = mock_mapping
|
|
mock_cls.objects.filter.return_value = mock_qs
|
|
result = view.get_netbox_interface_type({"ifType": "softwareLoopback", "ifSpeed": None})
|
|
|
|
assert result == "virtual"
|
|
|
|
def test_no_mapping_returns_other(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=None),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.InterfaceTypeMapping") as mock_cls,
|
|
):
|
|
mock_qs = MagicMock()
|
|
mock_qs.filter.return_value.first.return_value = None
|
|
mock_cls.objects.filter.return_value = mock_qs
|
|
result = view.get_netbox_interface_type({"ifType": "unknown", "ifSpeed": None})
|
|
|
|
assert result == "other"
|
|
|
|
def test_speed_mapping_falls_back_to_null(self):
|
|
"""When speed mapping returns None, falls back to null-speed mapping."""
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
mock_null_mapping = MagicMock()
|
|
mock_null_mapping.netbox_type = "other"
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=1000),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.InterfaceTypeMapping") as mock_cls,
|
|
):
|
|
mock_speed_qs = MagicMock()
|
|
mock_speed_qs.order_by.return_value.first.return_value = None # No speed match
|
|
mock_null_qs = MagicMock()
|
|
mock_null_qs.first.return_value = mock_null_mapping
|
|
mock_qs = MagicMock()
|
|
mock_qs.filter.side_effect = [mock_speed_qs, mock_null_qs]
|
|
mock_cls.objects.filter.return_value = mock_qs
|
|
result = view.get_netbox_interface_type({"ifType": "ethernetCsmacd", "ifSpeed": 1000})
|
|
|
|
assert result == "other"
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.handle_mac_address
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewHandleMacAddress:
|
|
def test_no_mac_address_does_nothing(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
interface = MagicMock()
|
|
view.handle_mac_address(interface, None)
|
|
interface.mac_addresses.add.assert_not_called()
|
|
|
|
def test_new_mac_created_and_added(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
interface = MagicMock()
|
|
interface.mac_addresses.filter.return_value.first.return_value = None
|
|
mock_mac = MagicMock()
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.MACAddress") as mock_mac_cls:
|
|
mock_mac_cls.objects.create.return_value = mock_mac
|
|
view.handle_mac_address(interface, "aa:bb:cc:dd:ee:ff")
|
|
|
|
interface.mac_addresses.add.assert_called_once_with(mock_mac)
|
|
|
|
def test_existing_mac_added_without_create(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
existing_mac = MagicMock()
|
|
interface = MagicMock()
|
|
interface.mac_addresses.filter.return_value.first.return_value = existing_mac
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.MACAddress") as mock_mac_cls:
|
|
view.handle_mac_address(interface, "aa:bb:cc:dd:ee:ff")
|
|
|
|
mock_mac_cls.objects.create.assert_not_called()
|
|
interface.mac_addresses.add.assert_called_once_with(existing_mac)
|
|
|
|
def test_primary_mac_assigned_if_attribute_exists(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
|
|
# Use a stub with explicit attributes to enforce the guard on primary_mac_address
|
|
class InterfaceStub:
|
|
primary_mac_address = None
|
|
|
|
def __init__(self):
|
|
self.mac_addresses = MagicMock()
|
|
self.mac_addresses.filter.return_value.first.return_value = None
|
|
|
|
interface = InterfaceStub()
|
|
mock_mac = MagicMock()
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.MACAddress") as mock_mac_cls:
|
|
mock_mac_cls.objects.create.return_value = mock_mac
|
|
view.handle_mac_address(interface, "aa:bb:cc:dd:ee:ff")
|
|
|
|
assert interface.primary_mac_address == mock_mac
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.update_interface_attributes
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewUpdateInterfaceAttributes:
|
|
def _make_view(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request()
|
|
view._post_server_key = "default"
|
|
view.handle_mac_address = MagicMock()
|
|
return view
|
|
|
|
def test_basic_attributes_set(self):
|
|
from dcim.models import Interface
|
|
|
|
view = self._make_view()
|
|
interface = MagicMock()
|
|
interface.__class__ = Interface
|
|
librenms_port = {
|
|
"ifName": "Gi0/1",
|
|
"ifType": "ethernetCsmacd",
|
|
"ifSpeed": 1000000000,
|
|
"ifAlias": "uplink",
|
|
"ifMtu": 1500,
|
|
"port_id": None,
|
|
"ifAdminStatus": "up",
|
|
}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=1000000):
|
|
view.update_interface_attributes(interface, librenms_port, "1000base-t", [], "ifName")
|
|
|
|
interface.save.assert_called_once()
|
|
assert interface.name == "Gi0/1"
|
|
assert interface.type == "1000base-t"
|
|
assert interface.speed == 1000000
|
|
assert interface.description == "uplink"
|
|
assert interface.mtu == 1500
|
|
assert interface.enabled is True
|
|
|
|
def test_excluded_columns_skipped(self):
|
|
from dcim.models import Interface
|
|
|
|
view = self._make_view()
|
|
# MagicMock(spec=Interface) passes isinstance check; explicit attr init makes changes detectable
|
|
interface = MagicMock(spec=Interface)
|
|
interface.name = None
|
|
interface.type = None
|
|
interface.speed = None
|
|
interface.description = None
|
|
interface.mtu = None
|
|
interface.enabled = None
|
|
interface.mac_address = None
|
|
interface.save = MagicMock()
|
|
librenms_port = {
|
|
"ifName": "Gi0/1",
|
|
"ifType": "ethernetCsmacd",
|
|
"ifSpeed": 0,
|
|
"ifAlias": None,
|
|
"ifMtu": None,
|
|
"port_id": None,
|
|
"ifAdminStatus": None,
|
|
}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=0):
|
|
view.handle_mac_address = MagicMock()
|
|
view.update_interface_attributes(
|
|
interface,
|
|
librenms_port,
|
|
"other",
|
|
["name", "type", "speed", "description", "mtu", "enabled", "mac_address"],
|
|
"ifName",
|
|
)
|
|
|
|
# save still called
|
|
interface.save.assert_called_once()
|
|
# MAC handler must not be invoked when "mac_address" is in excluded_columns
|
|
view.handle_mac_address.assert_not_called()
|
|
# All other excluded attributes remain at their initial None
|
|
assert interface.name is None
|
|
assert interface.type is None
|
|
assert interface.speed is None
|
|
assert interface.description is None
|
|
assert interface.mtu is None
|
|
assert interface.enabled is None
|
|
|
|
def test_admin_status_down_sets_disabled(self):
|
|
from dcim.models import Interface
|
|
|
|
view = self._make_view()
|
|
interface = MagicMock()
|
|
interface.__class__ = Interface
|
|
librenms_port = {
|
|
"ifName": "Gi0/1",
|
|
"ifType": None,
|
|
"ifSpeed": None,
|
|
"ifAlias": None,
|
|
"ifMtu": None,
|
|
"port_id": None,
|
|
"ifAdminStatus": "down",
|
|
}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=None):
|
|
view.update_interface_attributes(interface, librenms_port, None, [], "ifName")
|
|
|
|
assert interface.enabled is False
|
|
|
|
def test_port_id_calls_set_librenms_device_id(self):
|
|
from dcim.models import Interface
|
|
|
|
view = self._make_view()
|
|
interface = MagicMock()
|
|
interface.__class__ = Interface
|
|
librenms_port = {
|
|
"ifName": "Gi0/1",
|
|
"ifType": None,
|
|
"ifSpeed": None,
|
|
"ifAlias": None,
|
|
"ifMtu": None,
|
|
"port_id": 42,
|
|
"ifAdminStatus": "up",
|
|
}
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=None),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.set_librenms_device_id") as mock_set,
|
|
):
|
|
view.update_interface_attributes(interface, librenms_port, None, [], "ifName")
|
|
|
|
mock_set.assert_called_once_with(interface, 42, "default")
|
|
|
|
def test_ifalias_not_set_when_same_as_name(self):
|
|
"""ifAlias should not overwrite when equal to interface name."""
|
|
from dcim.models import Interface
|
|
|
|
view = self._make_view()
|
|
# MagicMock(spec=Interface) with explicit init so plain assignments are detectable
|
|
interface = MagicMock(spec=Interface)
|
|
interface.description = None
|
|
interface.save = MagicMock()
|
|
librenms_port = {
|
|
"ifName": "Gi0/1",
|
|
"ifType": None,
|
|
"ifSpeed": None,
|
|
"ifAlias": "Gi0/1", # Same as interface name → should not set description
|
|
"ifMtu": None,
|
|
"port_id": None,
|
|
"ifAdminStatus": "up",
|
|
}
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.convert_speed_to_kbps", return_value=None):
|
|
view.update_interface_attributes(interface, librenms_port, None, [], "ifName")
|
|
|
|
# description should remain None since ifAlias == interface_name
|
|
assert interface.description is None
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView._sync_interface_vlans
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewSyncInterfaceVlans:
|
|
def test_no_vlans_calls_update_with_empty(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request()
|
|
view._lookup_maps = {}
|
|
view._update_interface_vlan_assignment = MagicMock()
|
|
|
|
interface = MagicMock()
|
|
librenms_port = {}
|
|
|
|
view._sync_interface_vlans(interface, librenms_port, "Gi0/1")
|
|
|
|
view._update_interface_vlan_assignment.assert_called_once()
|
|
|
|
def test_with_vlans_builds_group_map(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.request = _make_request(post_data={"vlan_group_Gi0_1_100": "5"})
|
|
view._lookup_maps = {}
|
|
view._update_interface_vlan_assignment = MagicMock()
|
|
|
|
interface = MagicMock()
|
|
librenms_port = {"untagged_vlan": 100, "tagged_vlans": [200]}
|
|
|
|
view._sync_interface_vlans(interface, librenms_port, "Gi0/1")
|
|
|
|
call_args = view._update_interface_vlan_assignment.call_args
|
|
vlan_group_map = call_args[0][2]
|
|
assert vlan_group_map.get("100") == "5"
|
|
|
|
|
|
# ===========================================================================
|
|
# SyncInterfacesView.sync_selected_interfaces
|
|
# ===========================================================================
|
|
|
|
|
|
class TestSyncInterfacesViewSyncSelected:
|
|
def test_syncs_matching_ports(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import SyncInterfacesView
|
|
|
|
view = object.__new__(SyncInterfacesView)
|
|
view.interface_name_field = "ifName"
|
|
view.sync_interface = MagicMock()
|
|
|
|
ports_data = [{"ifName": "Gi0/1"}, {"ifName": "Gi0/2"}]
|
|
selected = ["Gi0/1"]
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.transaction"):
|
|
view.sync_selected_interfaces(MagicMock(), selected, ports_data, [], "ifName")
|
|
|
|
assert view.sync_interface.call_count == 1
|
|
call_args = view.sync_interface.call_args
|
|
assert call_args[0][1]["ifName"] == "Gi0/1"
|
|
|
|
|
|
# ===========================================================================
|
|
# DeleteNetBoxInterfacesView
|
|
# ===========================================================================
|
|
|
|
|
|
class TestDeleteNetBoxInterfacesViewPermissions:
|
|
def test_device_returns_interface_delete_perm(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from dcim.models import Interface
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
perms = view.get_required_permissions_for_object_type("device")
|
|
assert ("delete", Interface) in perms
|
|
|
|
def test_vm_returns_vminterface_delete_perm(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from virtualization.models import VMInterface
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
perms = view.get_required_permissions_for_object_type("virtualmachine")
|
|
assert ("delete", VMInterface) in perms
|
|
|
|
def test_invalid_type_raises_http404(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from django.http import Http404
|
|
import pytest
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
with pytest.raises(Http404):
|
|
view.get_required_permissions_for_object_type("invalid")
|
|
|
|
|
|
class TestDeleteNetBoxInterfacesViewPost:
|
|
def test_permission_denied_returns_early(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=_denied_response())
|
|
|
|
req = _make_request(post_data={"interface_ids": ["1"]})
|
|
result = view.post(req, "device", 1)
|
|
assert result.status_code == 403
|
|
|
|
def test_empty_interface_ids_returns_400(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from django.http import JsonResponse
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
req = _make_request(post_data={}) # No interface_ids
|
|
|
|
with patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device):
|
|
result = view.post(req, "device", 1)
|
|
|
|
assert isinstance(result, JsonResponse)
|
|
assert result.status_code == 400
|
|
|
|
def test_invalid_object_type_raises_http404(self):
|
|
"""Invalid object_type raises Http404 from get_required_permissions_for_object_type."""
|
|
import pytest
|
|
from django.http import Http404
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
req = _make_request(post_data={"interface_ids": ["1"]})
|
|
with pytest.raises(Http404):
|
|
view.post(req, "badtype", 1)
|
|
|
|
def test_device_interface_deleted_successfully(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from django.http import JsonResponse
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.name = "Gi0/1"
|
|
mock_interface.device_id = 1
|
|
|
|
req = _make_request(post_data={"interface_ids": ["5"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_intf_cls.objects.get.return_value = mock_interface
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
mock_intf_cls.DoesNotExist = _DNE
|
|
|
|
result = view.post(req, "device", 1)
|
|
|
|
assert isinstance(result, JsonResponse)
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 1
|
|
mock_interface.delete.assert_called_once()
|
|
|
|
def test_vm_interface_deleted_successfully(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from virtualization.models import VMInterface
|
|
from django.http import JsonResponse
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_vm = MagicMock(pk=2)
|
|
mock_vm.id = 2
|
|
|
|
mock_interface = MagicMock(spec=VMInterface)
|
|
mock_interface.name = "eth0"
|
|
mock_interface.virtual_machine_id = 2
|
|
|
|
req = _make_request(post_data={"interface_ids": ["7"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_vm),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.VMInterface") as mock_vmintf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_vmintf_cls.objects.get.return_value = mock_interface
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
mock_vmintf_cls.DoesNotExist = _DNE
|
|
|
|
result = view.post(req, "virtualmachine", 2)
|
|
|
|
assert isinstance(result, JsonResponse)
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 1
|
|
mock_interface.delete.assert_called_once()
|
|
|
|
def test_device_interface_wrong_device_adds_error(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.name = "Gi0/1"
|
|
mock_interface.device_id = 99 # Wrong device
|
|
|
|
req = _make_request(post_data={"interface_ids": ["5"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_intf_cls.objects.get.return_value = mock_interface
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
mock_intf_cls.DoesNotExist = _DNE
|
|
|
|
result = view.post(req, "device", 1)
|
|
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 0
|
|
assert len(data["errors"]) == 1
|
|
|
|
def test_device_interface_with_vc_wrong_member_adds_error(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
mock_device.id = 1
|
|
mock_vc = MagicMock()
|
|
mock_member = MagicMock()
|
|
mock_member.id = 1
|
|
mock_vc.members.all.return_value = [mock_member]
|
|
mock_device.virtual_chassis = mock_vc
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.name = "Gi0/1"
|
|
mock_interface.device_id = 999 # Not in VC
|
|
|
|
req = _make_request(post_data={"interface_ids": ["5"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_intf_cls.objects.get.return_value = mock_interface
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
mock_intf_cls.DoesNotExist = _DNE
|
|
|
|
result = view.post(req, "device", 1)
|
|
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 0
|
|
assert len(data["errors"]) == 1
|
|
|
|
def test_interface_not_found_adds_error(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
req = _make_request(post_data={"interface_ids": ["999"]})
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_intf_cls.DoesNotExist = _DNE
|
|
mock_intf_cls.objects.get.side_effect = _DNE()
|
|
|
|
result = view.post(req, "device", 1)
|
|
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 0
|
|
assert len(data["errors"]) == 1
|
|
|
|
def test_vm_interface_wrong_vm_adds_error(self):
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from virtualization.models import VMInterface
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_vm = MagicMock(pk=2)
|
|
mock_vm.id = 2
|
|
|
|
mock_interface = MagicMock(spec=VMInterface)
|
|
mock_interface.name = "eth0"
|
|
mock_interface.virtual_machine_id = 99 # Wrong VM
|
|
|
|
req = _make_request(post_data={"interface_ids": ["7"]})
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_vm),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.VMInterface") as mock_vmintf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_vmintf_cls.objects.get.return_value = mock_interface
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
mock_vmintf_cls.DoesNotExist = _DNE
|
|
|
|
result = view.post(req, "virtualmachine", 2)
|
|
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 0
|
|
assert len(data["errors"]) == 1
|
|
|
|
def test_response_includes_errors_in_message(self):
|
|
"""When errors exist, message mentions error count."""
|
|
from netbox_librenms_plugin.views.sync.interfaces import DeleteNetBoxInterfacesView
|
|
from dcim.models import Interface
|
|
import json
|
|
|
|
view = object.__new__(DeleteNetBoxInterfacesView)
|
|
view.get_required_permissions_for_object_type = MagicMock(return_value=[])
|
|
view.require_all_permissions_json = MagicMock(return_value=None)
|
|
|
|
mock_device = MagicMock(pk=1)
|
|
mock_device.id = 1
|
|
mock_device.virtual_chassis = None
|
|
|
|
# Two interfaces: one that belongs, one that doesn't
|
|
mock_interface_ok = MagicMock(spec=Interface)
|
|
mock_interface_ok.name = "Gi0/1"
|
|
mock_interface_ok.device_id = 1
|
|
|
|
mock_interface_bad = MagicMock(spec=Interface)
|
|
mock_interface_bad.name = "Gi0/2"
|
|
mock_interface_bad.device_id = 99 # Wrong device
|
|
|
|
req = _make_request(post_data={"interface_ids": ["5", "6"]})
|
|
|
|
call_count = [0]
|
|
|
|
def get_side_effect(id):
|
|
call_count[0] += 1
|
|
if call_count[0] == 1:
|
|
return mock_interface_ok
|
|
return mock_interface_bad
|
|
|
|
class _DNE(Exception):
|
|
pass
|
|
|
|
with (
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.get_object_or_404", return_value=mock_device),
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.Interface") as mock_intf_cls,
|
|
patch("netbox_librenms_plugin.views.sync.interfaces.transaction"),
|
|
):
|
|
mock_intf_cls.DoesNotExist = _DNE
|
|
mock_intf_cls.objects.get.side_effect = get_side_effect
|
|
result = view.post(req, "device", 1)
|
|
|
|
data = json.loads(result.content)
|
|
assert data["deleted_count"] == 1
|
|
assert "error" in data["message"]
|
|
mock_interface_ok.delete.assert_called_once()
|