564 lines
21 KiB
Python
564 lines
21 KiB
Python
"""
|
|
Tests for interface VLAN sync functionality (Phase 2).
|
|
|
|
Tests cover:
|
|
- VlanAssignmentMixin methods
|
|
- Port VLAN enrichment
|
|
- VLAN sync action
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
# Import the autouse fixture from helpers
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
|
|
class TestVlanAssignmentMixin:
|
|
"""Tests for VlanAssignmentMixin methods."""
|
|
|
|
def test_get_vlan_groups_for_device_includes_site_scoped(self, mock_librenms_config):
|
|
"""Test that VLAN groups scoped to device's site are included."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
# Create mock device with site
|
|
mock_device = MagicMock()
|
|
mock_device.site = MagicMock()
|
|
mock_device.site.pk = 1
|
|
mock_device.site.region = None
|
|
mock_device.site.group = None
|
|
mock_device.location = None
|
|
mock_device.rack = None
|
|
|
|
# Mock the VLAN group query
|
|
mock_site_group = MagicMock()
|
|
mock_site_group.name = "Site VLANs"
|
|
mock_site_group.pk = 10
|
|
|
|
with patch.object(mixin, "_get_vlan_groups_for_scope") as mock_get_scope:
|
|
mock_get_scope.return_value = [mock_site_group]
|
|
with patch("ipam.models.VLANGroup") as mock_vlan_group_class:
|
|
mock_vlan_group_class.objects.filter.return_value = []
|
|
|
|
mixin.get_vlan_groups_for_device(mock_device)
|
|
|
|
# Verify site scope was queried
|
|
assert mock_get_scope.called
|
|
|
|
def test_get_vlan_groups_for_device_includes_global(self, mock_librenms_config):
|
|
"""Test that global VLAN groups (no scope) are included."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
# Create mock device with no location context
|
|
mock_device = MagicMock()
|
|
mock_device.site = None
|
|
mock_device.location = None
|
|
mock_device.rack = None
|
|
|
|
with patch.object(mixin, "_get_vlan_groups_for_scope") as mock_get_scope:
|
|
mock_get_scope.return_value = []
|
|
with patch("ipam.models.VLANGroup") as mock_vlan_group_class:
|
|
mock_global_group = MagicMock()
|
|
mock_global_group.name = "Global VLANs"
|
|
mock_global_group.pk = 20
|
|
mock_vlan_group_class.objects.filter.return_value = [mock_global_group]
|
|
|
|
mixin.get_vlan_groups_for_device(mock_device)
|
|
|
|
# Verify global scope was queried
|
|
mock_vlan_group_class.objects.filter.assert_called_with(scope_type__isnull=True)
|
|
|
|
def test_select_most_specific_group_prefers_rack(self, mock_librenms_config):
|
|
"""Test that rack-scoped groups are preferred over site-scoped."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
# Create mock device with rack
|
|
mock_device = MagicMock()
|
|
mock_device.rack = MagicMock()
|
|
mock_device.rack.pk = 1
|
|
mock_device.site = MagicMock()
|
|
mock_device.site.pk = 2
|
|
mock_device.site.region = None
|
|
mock_device.site.group = None
|
|
mock_device.location = None
|
|
|
|
# Create mock groups with different scopes
|
|
mock_rack_group = MagicMock()
|
|
mock_rack_group.scope_type = MagicMock()
|
|
mock_rack_group.scope_type.pk = 100 # Rack content type
|
|
mock_rack_group.scope_id = 1
|
|
|
|
mock_site_group = MagicMock()
|
|
mock_site_group.scope_type = MagicMock()
|
|
mock_site_group.scope_type.pk = 101 # Site content type
|
|
mock_site_group.scope_id = 2
|
|
|
|
with patch("django.contrib.contenttypes.models.ContentType") as mock_ct:
|
|
# Mock ContentType lookups
|
|
mock_ct.objects.get_for_model.side_effect = lambda model: MagicMock(pk=100 if "Rack" in str(model) else 101)
|
|
|
|
result = mixin._select_most_specific_group([mock_rack_group, mock_site_group], mock_device)
|
|
|
|
# Rack-scoped should be preferred
|
|
assert result == mock_rack_group
|
|
|
|
def test_select_most_specific_group_returns_none_for_ambiguous(self, mock_librenms_config):
|
|
"""Test that None is returned when multiple groups have same priority."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
# Create mock device
|
|
mock_device = MagicMock()
|
|
mock_device.site = MagicMock()
|
|
mock_device.site.pk = 1
|
|
mock_device.site.region = None
|
|
mock_device.site.group = None
|
|
mock_device.rack = None
|
|
mock_device.location = None
|
|
|
|
# Create two groups with same scope (both site-scoped to same site)
|
|
mock_group1 = MagicMock()
|
|
mock_group1.scope_type = MagicMock()
|
|
mock_group1.scope_type.pk = 101
|
|
mock_group1.scope_id = 1
|
|
|
|
mock_group2 = MagicMock()
|
|
mock_group2.scope_type = MagicMock()
|
|
mock_group2.scope_type.pk = 101
|
|
mock_group2.scope_id = 1
|
|
|
|
with patch("django.contrib.contenttypes.models.ContentType") as mock_ct:
|
|
mock_ct.objects.get_for_model.return_value = MagicMock(pk=101)
|
|
|
|
result = mixin._select_most_specific_group([mock_group1, mock_group2], mock_device)
|
|
|
|
# Ambiguous - should return None
|
|
assert result is None
|
|
|
|
def test_get_ancestors_returns_hierarchy(self, mock_librenms_config):
|
|
"""Test that _get_ancestors returns full parent chain."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
# Create mock location hierarchy
|
|
mock_grandparent = MagicMock()
|
|
mock_grandparent.parent = None
|
|
|
|
mock_parent = MagicMock()
|
|
mock_parent.parent = mock_grandparent
|
|
|
|
mock_location = MagicMock()
|
|
mock_location.parent = mock_parent
|
|
|
|
ancestors = mixin._get_ancestors(mock_location)
|
|
|
|
assert len(ancestors) == 3
|
|
assert ancestors[0] == mock_location
|
|
assert ancestors[1] == mock_parent
|
|
assert ancestors[2] == mock_grandparent
|
|
|
|
def test_find_vlan_in_group_prefers_specified_group(self, mock_librenms_config):
|
|
"""Test that _find_vlan_in_group prefers the specified group."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_vlan_in_group = MagicMock()
|
|
mock_vlan_global = MagicMock()
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {
|
|
(100, 5): mock_vlan_in_group,
|
|
(100, None): mock_vlan_global,
|
|
},
|
|
"vid_to_vlans": {
|
|
100: [mock_vlan_in_group, mock_vlan_global],
|
|
},
|
|
}
|
|
|
|
result = mixin._find_vlan_in_group(100, 5, lookup_maps)
|
|
|
|
assert result == mock_vlan_in_group
|
|
|
|
def test_find_vlan_in_group_falls_back_to_global(self, mock_librenms_config):
|
|
"""Test that _find_vlan_in_group falls back to global VLAN."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_vlan_global = MagicMock()
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {
|
|
(100, None): mock_vlan_global,
|
|
},
|
|
"vid_to_vlans": {
|
|
100: [mock_vlan_global],
|
|
},
|
|
}
|
|
|
|
# Request group 5 which doesn't have VLAN 100
|
|
result = mixin._find_vlan_in_group(100, 5, lookup_maps)
|
|
|
|
assert result == mock_vlan_global
|
|
|
|
def test_find_vlan_in_group_returns_none_if_not_found(self, mock_librenms_config):
|
|
"""Test that _find_vlan_in_group returns None if VLAN not found."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {},
|
|
"vid_to_vlans": {},
|
|
}
|
|
|
|
result = mixin._find_vlan_in_group(999, None, lookup_maps)
|
|
|
|
assert result is None
|
|
|
|
|
|
class TestPortVlanEnrichment:
|
|
"""Tests for port VLAN data enrichment."""
|
|
|
|
pytest_plugins = ["tests.test_librenms_api_helpers"]
|
|
|
|
@patch("requests.get")
|
|
def test_parse_port_vlan_data_access_port(self, mock_get, mock_librenms_config):
|
|
"""Test parsing access port VLAN data."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
port_data = {
|
|
"port_id": 1234,
|
|
"ifName": "Gi1/0/1",
|
|
"ifDescr": "GigabitEthernet1/0/1",
|
|
"ifVlan": "100",
|
|
"ifTrunk": None,
|
|
}
|
|
|
|
result = api.parse_port_vlan_data(port_data, "ifName")
|
|
|
|
assert result["port_id"] == 1234
|
|
assert result["interface_name"] == "Gi1/0/1"
|
|
assert result["mode"] == "access"
|
|
assert result["untagged_vlan"] == 100
|
|
assert result["tagged_vlans"] == []
|
|
|
|
@patch("requests.get")
|
|
def test_parse_port_vlan_data_trunk_port(self, mock_get, mock_librenms_config):
|
|
"""Test parsing trunk port VLAN data."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
port_data = {
|
|
"port_id": 5678,
|
|
"ifName": "Te1/1/1",
|
|
"ifDescr": "TenGigabitEthernet1/1/1",
|
|
"ifVlan": "90",
|
|
"ifTrunk": "dot1Q",
|
|
"vlans": [
|
|
{"vlan": 90, "untagged": 1, "state": "unknown"},
|
|
{"vlan": 50, "untagged": 0, "state": "forwarding"},
|
|
{"vlan": 60, "untagged": 0, "state": "forwarding"},
|
|
],
|
|
}
|
|
|
|
result = api.parse_port_vlan_data(port_data, "ifName")
|
|
|
|
assert result["port_id"] == 5678
|
|
assert result["interface_name"] == "Te1/1/1"
|
|
assert result["mode"] == "tagged"
|
|
assert result["untagged_vlan"] == 90
|
|
assert sorted(result["tagged_vlans"]) == [50, 60]
|
|
|
|
@patch("requests.get")
|
|
def test_parse_port_vlan_data_uses_interface_name_field(self, mock_get, mock_librenms_config):
|
|
"""Test that parse_port_vlan_data respects interface_name_field parameter."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
port_data = {
|
|
"port_id": 1234,
|
|
"ifName": "Gi1/0/1",
|
|
"ifDescr": "GigabitEthernet1/0/1",
|
|
"ifVlan": "100",
|
|
"ifTrunk": None,
|
|
}
|
|
|
|
result = api.parse_port_vlan_data(port_data, "ifDescr")
|
|
|
|
assert result["interface_name"] == "GigabitEthernet1/0/1"
|
|
|
|
|
|
class TestInterfaceVlanSync:
|
|
"""Tests for interface VLAN sync action."""
|
|
|
|
pytest_plugins = ["tests.test_librenms_api_helpers"]
|
|
|
|
def test_update_interface_vlan_assignment_access_mode(self, mock_librenms_config):
|
|
"""Test that access mode is set correctly for untagged-only ports."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.tagged_vlans = MagicMock()
|
|
|
|
mock_vlan = MagicMock()
|
|
mock_vlan.vid = 100
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {(100, None): mock_vlan},
|
|
"vid_to_vlans": {100: [mock_vlan]},
|
|
}
|
|
|
|
vlan_data = {
|
|
"untagged_vlan": 100,
|
|
"tagged_vlans": [],
|
|
}
|
|
|
|
mixin._update_interface_vlan_assignment(mock_interface, vlan_data, None, lookup_maps)
|
|
|
|
assert mock_interface.mode == "access"
|
|
assert mock_interface.untagged_vlan == mock_vlan
|
|
mock_interface.tagged_vlans.clear.assert_called_once()
|
|
|
|
def test_update_interface_vlan_assignment_tagged_mode(self, mock_librenms_config):
|
|
"""Test that tagged mode is set for trunk ports."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.tagged_vlans = MagicMock()
|
|
|
|
mock_vlan_100 = MagicMock()
|
|
mock_vlan_100.vid = 100
|
|
mock_vlan_200 = MagicMock()
|
|
mock_vlan_200.vid = 200
|
|
mock_vlan_300 = MagicMock()
|
|
mock_vlan_300.vid = 300
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {
|
|
(100, None): mock_vlan_100,
|
|
(200, None): mock_vlan_200,
|
|
(300, None): mock_vlan_300,
|
|
},
|
|
"vid_to_vlans": {
|
|
100: [mock_vlan_100],
|
|
200: [mock_vlan_200],
|
|
300: [mock_vlan_300],
|
|
},
|
|
}
|
|
|
|
vlan_data = {
|
|
"untagged_vlan": 100,
|
|
"tagged_vlans": [200, 300],
|
|
}
|
|
|
|
mixin._update_interface_vlan_assignment(mock_interface, vlan_data, None, lookup_maps)
|
|
|
|
assert mock_interface.mode == "tagged"
|
|
assert mock_interface.untagged_vlan == mock_vlan_100
|
|
mock_interface.tagged_vlans.set.assert_called_once_with([mock_vlan_200, mock_vlan_300])
|
|
|
|
def test_update_interface_vlan_assignment_missing_vlans(self, mock_librenms_config):
|
|
"""Test that missing VLANs are tracked in result."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.tagged_vlans = MagicMock()
|
|
|
|
# Empty lookup maps - no VLANs exist in NetBox
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {},
|
|
"vid_to_vlans": {},
|
|
}
|
|
|
|
vlan_data = {
|
|
"untagged_vlan": 100,
|
|
"tagged_vlans": [200, 300],
|
|
}
|
|
|
|
result = mixin._update_interface_vlan_assignment(mock_interface, vlan_data, None, lookup_maps)
|
|
|
|
assert result["missing_vlans"] == [100, 200, 300]
|
|
assert mock_interface.untagged_vlan is None
|
|
mock_interface.tagged_vlans.set.assert_called_once_with([])
|
|
|
|
def test_update_interface_vlan_assignment_respects_group_selection(self, mock_librenms_config):
|
|
"""Test that VLAN group selection is respected."""
|
|
from netbox_librenms_plugin.views.mixins import VlanAssignmentMixin
|
|
|
|
mixin = VlanAssignmentMixin()
|
|
|
|
mock_interface = MagicMock()
|
|
mock_interface.tagged_vlans = MagicMock()
|
|
|
|
mock_vlan_group1 = MagicMock()
|
|
mock_vlan_group1.vid = 100
|
|
mock_vlan_global = MagicMock()
|
|
mock_vlan_global.vid = 100
|
|
|
|
lookup_maps = {
|
|
"vid_group_to_vlan": {
|
|
(100, 5): mock_vlan_group1,
|
|
(100, None): mock_vlan_global,
|
|
},
|
|
"vid_to_vlans": {
|
|
100: [mock_vlan_group1, mock_vlan_global],
|
|
},
|
|
}
|
|
|
|
vlan_data = {
|
|
"untagged_vlan": 100,
|
|
"tagged_vlans": [],
|
|
}
|
|
|
|
# Request VLAN from group 5
|
|
mixin._update_interface_vlan_assignment(mock_interface, vlan_data, 5, lookup_maps)
|
|
|
|
# Should use group-specific VLAN
|
|
assert mock_interface.untagged_vlan == mock_vlan_group1
|
|
|
|
|
|
class TestInterfaceCssClassGroupMatching:
|
|
"""
|
|
Tests for group-aware VLAN CSS class functions in utils.py.
|
|
|
|
Verifies that VLAN group mismatch (same VID but different group) produces
|
|
orange (text-warning) instead of green (text-success).
|
|
"""
|
|
|
|
# -- get_untagged_vlan_css_class --
|
|
|
|
def test_untagged_vid_match_group_match_returns_green(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 60, True, [], group_matches=True) == "text-success"
|
|
|
|
def test_untagged_vid_match_group_mismatch_returns_orange(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 60, True, [], group_matches=False) == "text-warning"
|
|
|
|
def test_untagged_vid_differs_group_irrelevant(self, mock_librenms_config):
|
|
"""Different VIDs -> text-warning regardless of group_matches."""
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 100, True, [], group_matches=True) == "text-warning"
|
|
|
|
def test_untagged_not_in_netbox_ignores_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 60, False, [], group_matches=True) == "text-danger"
|
|
|
|
def test_untagged_missing_vlan_ignores_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 60, True, [60], group_matches=True) == "text-danger"
|
|
|
|
def test_untagged_no_netbox_vlan_returns_red(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, None, True, [], group_matches=True) == "text-danger"
|
|
|
|
def test_untagged_default_group_matches_is_true(self, mock_librenms_config):
|
|
"""Without group_matches param, defaults to True (backward compat)."""
|
|
from netbox_librenms_plugin.utils import get_untagged_vlan_css_class
|
|
|
|
assert get_untagged_vlan_css_class(60, 60, True, []) == "text-success"
|
|
|
|
# -- get_tagged_vlan_css_class --
|
|
|
|
def test_tagged_vid_present_group_match_returns_green(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {60, 100}, True, [], group_matches=True) == "text-success"
|
|
|
|
def test_tagged_vid_present_group_mismatch_returns_orange(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {60, 100}, True, [], group_matches=False) == "text-warning"
|
|
|
|
def test_tagged_vid_absent_group_irrelevant(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {100}, True, [], group_matches=True) == "text-danger"
|
|
|
|
def test_tagged_not_in_netbox_ignores_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {60}, False, [], group_matches=True) == "text-danger"
|
|
|
|
def test_tagged_missing_vlan_ignores_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {60}, True, [60], group_matches=True) == "text-danger"
|
|
|
|
def test_tagged_default_group_matches_is_true(self, mock_librenms_config):
|
|
"""Without group_matches param, defaults to True (backward compat)."""
|
|
from netbox_librenms_plugin.utils import get_tagged_vlan_css_class
|
|
|
|
assert get_tagged_vlan_css_class(60, {60}, True, []) == "text-success"
|
|
|
|
# -- check_vlan_group_matches --
|
|
|
|
def test_check_group_matches_untagged_same_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("U", 60, 5, 5, {}, 60, set()) is True
|
|
|
|
def test_check_group_matches_untagged_different_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("U", 60, 10, 5, {}, 60, set()) is False
|
|
|
|
def test_check_group_matches_untagged_vid_differs(self, mock_librenms_config):
|
|
"""When VIDs don't match, group comparison is irrelevant -> True."""
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("U", 60, 10, 5, {}, 100, set()) is True
|
|
|
|
def test_check_group_matches_tagged_same_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("T", 60, 5, None, {60: 5}, None, {60}) is True
|
|
|
|
def test_check_group_matches_tagged_different_group(self, mock_librenms_config):
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("T", 60, 10, None, {60: 5}, None, {60}) is False
|
|
|
|
def test_check_group_matches_tagged_vid_absent(self, mock_librenms_config):
|
|
"""When VID is not tagged in NetBox, group comparison irrelevant -> True."""
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("T", 60, 10, None, {}, None, set()) is True
|
|
|
|
def test_check_group_matches_global_to_global(self, mock_librenms_config):
|
|
"""Both NetBox VLAN and selected have no group (global) -> match."""
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("U", 60, None, None, {}, 60, set()) is True
|
|
|
|
def test_check_group_matches_global_vs_group(self, mock_librenms_config):
|
|
"""NetBox VLAN is global, selected is a specific group -> mismatch."""
|
|
from netbox_librenms_plugin.utils import check_vlan_group_matches
|
|
|
|
assert check_vlan_group_matches("U", 60, 5, None, {}, 60, set()) is False
|