Files
netbox-librenms-plugin/netbox_librenms_plugin/tests/test_sync_view_mismatch.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

590 lines
28 KiB
Python

"""
Tests for device mismatch detection in get_librenms_device_info.
Covers the identity cross-matching logic that determines whether a
mismatched_device warning banner is shown on the LibreNMS Sync page,
as well as the resolved_name computation that drives the name-match
icon and sync button.
Match rule: mismatch is False when ANY NetBox identity (device name,
primary IP, DNS name) matches ANY LibreNMS identity (sysName, hostname, ip).
"""
from unittest.mock import MagicMock, patch
def _make_view(librenms_id, device_info, librenms_url="https://librenms.example.com"):
"""Create a minimal BaseLibreNMSSyncView instance with mocked dependencies."""
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
view = object.__new__(BaseLibreNMSSyncView)
view.librenms_id = librenms_id
api = MagicMock()
api.librenms_url = librenms_url
api.get_device_info.return_value = (True, device_info)
api.get_device_inventory.return_value = (True, [])
view._librenms_api = api
return view
def _make_obj(name, primary_ip=None, dns_name=None, virtual_chassis=None, cf=None, vc_position=None, serial=None):
"""Create a mock NetBox device object."""
obj = MagicMock()
obj.name = name
obj.cf = cf or {}
if primary_ip:
obj.primary_ip = MagicMock()
obj.primary_ip.address.ip = primary_ip
obj.primary_ip.dns_name = dns_name or ""
else:
obj.primary_ip = None
obj.virtual_chassis = virtual_chassis
obj.vc_position = vc_position
obj.serial = serial
return obj
def _make_request(use_sysname=None, strip_domain=None):
"""Create a mock request with user preferences for naming settings."""
request = MagicMock()
request.POST = {}
request.GET = {}
config = {}
if use_sysname is not None:
config["plugins.netbox_librenms_plugin.use_sysname"] = use_sysname
if strip_domain is not None:
config["plugins.netbox_librenms_plugin.strip_domain"] = strip_domain
request.user.config.get = lambda path, default=None: config.get(path, default)
return request
class TestMismatchDetection:
"""Tests for identity cross-matching logic."""
# -- No device / API failure -------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_no_librenms_id_returns_not_found(self, mock_hw):
"""No librenms_id means device is not found."""
view = _make_view(librenms_id=None, device_info=None)
result = view.get_librenms_device_info(_make_obj("sw01"))
assert result["found_in_librenms"] is False
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_api_failure_returns_not_found(self, mock_hw):
"""API failure (success=False) means device is not found."""
view = _make_view(librenms_id=42, device_info=None)
view.librenms_api.get_device_info.return_value = (False, None)
result = view.get_librenms_device_info(_make_obj("sw01"))
assert result["found_in_librenms"] is False
assert result["mismatched_device"] is False
# -- Name matches ------------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_exact_sysname_match(self, mock_hw):
"""NetBox name matches LibreNMS sysName (case-insensitive)."""
view = _make_view(42, {"sysName": "SW01", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_netbox_name_matches_librenms_hostname(self, mock_hw):
"""NetBox name matches LibreNMS hostname field."""
view = _make_view(42, {"sysName": "something-else", "hostname": "sw01", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_fqdn_match(self, mock_hw):
"""Full FQDN match -- no mismatch."""
view = _make_view(42, {"sysName": "sw01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01.example.net", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
# -- IP matches --------------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_netbox_ip_matches_librenms_ip(self, mock_hw):
"""NetBox primary IP matches LibreNMS IP -- no mismatch."""
view = _make_view(42, {"sysName": "different", "ip": "10.0.0.1"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_netbox_ip_matches_librenms_hostname_ip(self, mock_hw):
"""LibreNMS hostname is an IP that matches NetBox primary IP."""
view = _make_view(42, {"sysName": "different", "hostname": "10.0.0.1", "ip": "10.0.0.1"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
# -- DNS name matches --------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_dns_name_matches_sysname(self, mock_hw):
"""NetBox DNS name matches LibreNMS sysName."""
view = _make_view(42, {"sysName": "sw01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1", dns_name="sw01.example.net")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_dns_name_matches_librenms_hostname(self, mock_hw):
"""NetBox DNS name matches LibreNMS hostname field."""
view = _make_view(42, {"sysName": "something", "hostname": "sw01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1", dns_name="sw01.example.net")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
# -- Mismatches --------------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_completely_different_is_mismatch(self, mock_hw):
"""No identities overlap -- mismatch."""
view = _make_view(42, {"sysName": "router-01", "hostname": "router-01.corp", "ip": "10.0.0.2"})
obj = _make_obj("switch-05", primary_ip="10.0.0.1", dns_name="switch-05.corp")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is True
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_short_vs_fqdn_matches_via_domain_strip(self, mock_hw):
"""Short name vs FQDN -- matches after domain stripping."""
view = _make_view(42, {"sysName": "sw01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_fqdn_domain_differs_matches_via_domain_strip(self, mock_hw):
"""
Different FQDN domains -- matches because domain-stripped
LibreNMS short name 'sw01' matches NetBox FQDN split 'sw01'.
NetBox name 'sw01.example.net' is compared as-is (no stripping),
but the LibreNMS domain-stripped 'sw01' does NOT appear in the
NetBox identities since NetBox names are not domain-stripped.
However, both sides share the short name via NetBox raw name
normalization — actually NetBox keeps the full name.
"""
view = _make_view(42, {"sysName": "sw01.other.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01.example.net", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
# NetBox identities: {"sw01.example.net", "10.0.0.1"}
# LibreNMS identities: {"sw01.other.net", "sw01", "10.0.0.2"}
# No overlap → mismatch
assert result["mismatched_device"] is True
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_no_netbox_name_no_ip_match(self, mock_hw):
"""No NetBox name and IPs differ -- mismatch."""
view = _make_view(42, {"sysName": "sw01", "ip": "10.0.0.2"})
obj = _make_obj(None, primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is True
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_no_librenms_sysname_no_match(self, mock_hw):
"""No sysName, no hostname, IPs differ -- mismatch."""
view = _make_view(42, {"sysName": None, "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is True
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_no_identities_at_all(self, mock_hw):
"""Both sides have no identities -- mismatch (cannot confirm)."""
view = _make_view(42, {"sysName": None, "ip": None})
obj = _make_obj(None, primary_ip=None)
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is True
# -- Virtual Chassis ---------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_vc_suffix_stripped(self, mock_hw):
"""VC member suffix ' (1)' is stripped before comparison."""
view = _make_view(42, {"sysName": "switch-1", "ip": "10.0.0.2"})
obj = _make_obj("switch-1 (1)", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_vc_different_name_is_mismatch(self, mock_hw):
"""VC member with different name after suffix strip -- mismatch."""
vc = MagicMock()
view = _make_view(42, {"sysName": "switch-1", "ip": "10.0.0.2"})
obj = _make_obj("switch-2 (2)", primary_ip="10.0.0.1", virtual_chassis=vc, cf={"librenms_id": 42})
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
assert result["mismatched_device"] is True
# -- found_in_librenms always True with valid ID -----------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_found_in_librenms_always_true_with_valid_id(self, mock_hw):
"""found_in_librenms is True even when identities mismatch."""
view = _make_view(42, {"sysName": "totally-different", "ip": "10.0.0.2"})
obj = _make_obj("my-device", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["found_in_librenms"] is True
# -- Domain stripping --------------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_domain_strip_hostname(self, mock_hw):
"""LibreNMS hostname FQDN stripped to short name matches NetBox name."""
view = _make_view(42, {"sysName": "other", "hostname": "sw01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_domain_strip_sysname(self, mock_hw):
"""LibreNMS sysName FQDN stripped to short name matches NetBox name."""
view = _make_view(42, {"sysName": "sw01.corp.local", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_domain_strip_no_false_positive(self, mock_hw):
"""Domain stripping doesn't cause false match when short names differ."""
view = _make_view(42, {"sysName": "router01.example.net", "ip": "10.0.0.2"})
obj = _make_obj("switch01", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is True
# -- VC pattern stripping ----------------------------------------------
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.models.LibreNMSSettings.objects")
def test_vc_pattern_strip_default(self, mock_settings_qs, mock_hw):
"""Default VC pattern '-M{position}' is stripped from NetBox name."""
settings_obj = MagicMock()
settings_obj.vc_member_name_pattern = "-M{position}"
mock_settings_qs.first.return_value = settings_obj
view = _make_view(42, {"sysName": "switch01", "ip": "10.0.0.2"})
obj = _make_obj("switch01-M2", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.models.LibreNMSSettings.objects")
def test_vc_pattern_strip_custom(self, mock_settings_qs, mock_hw):
"""Custom VC pattern '-SW{position}' is stripped from NetBox name."""
settings_obj = MagicMock()
settings_obj.vc_member_name_pattern = "-SW{position}"
mock_settings_qs.first.return_value = settings_obj
view = _make_view(42, {"sysName": "switch01", "ip": "10.0.0.2"})
obj = _make_obj("switch01-SW3", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is False
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.models.LibreNMSSettings.objects")
def test_vc_pattern_no_match_leaves_name(self, mock_settings_qs, mock_hw):
"""VC pattern doesn't match -- name unchanged, still mismatched."""
settings_obj = MagicMock()
settings_obj.vc_member_name_pattern = "-M{position}"
mock_settings_qs.first.return_value = settings_obj
view = _make_view(42, {"sysName": "switch01", "ip": "10.0.0.2"})
obj = _make_obj("switch99", primary_ip="10.0.0.1")
result = view.get_librenms_device_info(obj)
assert result["mismatched_device"] is True
# ---------------------------------------------------------------------------
# Tests for VC lookup delegation in get()
# ---------------------------------------------------------------------------
class TestVCLookupDelegation:
"""Verify that BaseLibreNMSSyncView.get() always delegates VC device
resolution to get_librenms_sync_device(), even when the viewed member
has its own librenms_id."""
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.render")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.get_object_or_404")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.get_librenms_sync_device")
def test_vc_member_with_own_id_delegates_to_sync_device(self, mock_sync_device, mock_get_object, mock_render):
"""A VC member with its own librenms_id should still delegate to
get_librenms_sync_device, which may return a different member."""
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
# Viewed device: member A with its own librenms_id
member_a = MagicMock()
member_a.pk = 1
member_a.cf = {"librenms_id": {"default": 42}}
member_a.virtual_chassis = MagicMock()
# Sync device: member B (returned by get_librenms_sync_device)
member_b = MagicMock()
member_b.pk = 2
mock_get_object.return_value = member_a
mock_sync_device.return_value = member_b
view = object.__new__(BaseLibreNMSSyncView)
view.model = MagicMock()
api = MagicMock()
api.server_key = "default"
api.get_librenms_id.return_value = 42
view._librenms_api = api
view.tab = MagicMock()
view.get_context_data = MagicMock(return_value={})
mock_render.return_value = MagicMock()
request = MagicMock()
view.get(request, pk=1)
# get_librenms_sync_device must be called unconditionally for VC members
mock_sync_device.assert_called_once_with(member_a, server_key="default")
# get_librenms_id should be called on the sync device (member_b)
api.get_librenms_id.assert_called_once_with(member_b)
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.render")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.get_object_or_404")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.get_librenms_sync_device")
def test_non_vc_device_skips_sync_device_lookup(self, mock_sync_device, mock_get_object, mock_render):
"""A device without a virtual chassis should not call
get_librenms_sync_device at all."""
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
device = MagicMock()
device.pk = 1
device.virtual_chassis = None
mock_get_object.return_value = device
view = object.__new__(BaseLibreNMSSyncView)
view.model = MagicMock()
api = MagicMock()
api.server_key = "default"
api.get_librenms_id.return_value = 42
view._librenms_api = api
view.tab = MagicMock()
view.get_context_data = MagicMock(return_value={})
mock_render.return_value = MagicMock()
request = MagicMock()
view.get(request, pk=1)
mock_sync_device.assert_not_called()
# ---------------------------------------------------------------------------
# Tests for _build_all_server_mappings
# ---------------------------------------------------------------------------
class TestBuildAllServerMappings:
"""Tests for BaseLibreNMSSyncView._build_all_server_mappings."""
def test_returns_none_for_legacy_int(self, mock_netbox_device):
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
mock_netbox_device.custom_field_data = {"librenms_id": 42}
result = BaseLibreNMSSyncView._build_all_server_mappings(mock_netbox_device, "production")
assert result is None
def test_returns_none_for_missing_cf(self, mock_netbox_device):
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
mock_netbox_device.custom_field_data = {"librenms_id": None}
result = BaseLibreNMSSyncView._build_all_server_mappings(mock_netbox_device, "production")
assert result is None
def test_single_configured_server(self, mock_netbox_device, mock_plugins_config_single_server):
from unittest.mock import patch
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
mock_netbox_device.custom_field_data = {"librenms_id": {"production": 42}}
with patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") as mock_settings:
mock_settings.PLUGINS_CONFIG = mock_plugins_config_single_server
result = BaseLibreNMSSyncView._build_all_server_mappings(mock_netbox_device, "production")
assert result is not None
assert len(result) == 1
entry = result[0]
assert entry["server_key"] == "production"
assert entry["device_id"] == 42
assert entry["display_name"] == "Production LibreNMS"
assert entry["is_configured"] is True
assert entry["is_active"] is True
assert entry["device_url"] == "https://librenms.example.com/device/device=42/"
def test_orphaned_server_is_not_configured(self, mock_netbox_device, mock_plugins_config_empty_servers):
from unittest.mock import patch
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
mock_netbox_device.custom_field_data = {"librenms_id": {"deleted-server": 77}}
with patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") as mock_settings:
mock_settings.PLUGINS_CONFIG = mock_plugins_config_empty_servers
result = BaseLibreNMSSyncView._build_all_server_mappings(mock_netbox_device, "production")
assert result is not None
assert len(result) == 1
entry = result[0]
assert entry["server_key"] == "deleted-server"
assert entry["device_id"] == 77
assert entry["is_configured"] is False
assert entry["is_active"] is False
assert entry["device_url"] is None
def test_multiple_servers_sorted_active_first(self, mock_netbox_device, mock_plugins_config_multi_server_mapping):
from unittest.mock import patch
from netbox_librenms_plugin.views.base.librenms_sync_view import BaseLibreNMSSyncView
mock_netbox_device.custom_field_data = {"librenms_id": {"mock-dev": 99, "production": 42, "old-server": 11}}
with patch("netbox_librenms_plugin.views.base.librenms_sync_view.django_settings") as mock_settings:
mock_settings.PLUGINS_CONFIG = mock_plugins_config_multi_server_mapping
result = BaseLibreNMSSyncView._build_all_server_mappings(mock_netbox_device, "production")
assert result is not None
assert len(result) == 3
# Active (production) first
assert result[0]["server_key"] == "production"
assert result[0]["is_active"] is True
# Configured (mock-dev) second
assert result[1]["server_key"] == "mock-dev"
assert result[1]["is_configured"] is True
assert result[1]["is_active"] is False
# Orphaned last
assert result[2]["server_key"] == "old-server"
assert result[2]["is_configured"] is False
class TestResolvedName:
"""Tests for resolved_name computation using naming preferences."""
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.resolve_naming_preferences")
def test_resolved_name_uses_sysname_by_default(self, mock_prefs, mock_hw):
"""resolved_name defaults to sysName when use_sysname=True."""
mock_prefs.return_value = (True, False)
view = _make_view(42, {"sysName": "sw01.example.com", "hostname": "10.0.0.2", "ip": "10.0.0.2"})
obj = _make_obj("sw01.example.com", primary_ip="10.0.0.2")
request = _make_request(use_sysname=True, strip_domain=False)
result = view.get_librenms_device_info(obj, request)
assert result["librenms_device_details"]["resolved_name"] == "sw01.example.com"
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.resolve_naming_preferences")
def test_resolved_name_with_strip_domain(self, mock_prefs, mock_hw):
"""resolved_name strips domain when strip_domain=True."""
mock_prefs.return_value = (True, True)
view = _make_view(42, {"sysName": "sw01.example.com", "hostname": "10.0.0.2", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.2")
request = _make_request(use_sysname=True, strip_domain=True)
result = view.get_librenms_device_info(obj, request)
assert result["librenms_device_details"]["resolved_name"] == "sw01"
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.resolve_naming_preferences")
def test_resolved_name_use_hostname(self, mock_prefs, mock_hw):
"""resolved_name uses hostname when use_sysname=False."""
mock_prefs.return_value = (False, False)
view = _make_view(42, {"sysName": "sw01.example.com", "hostname": "sw01-mgmt", "ip": "10.0.0.2"})
obj = _make_obj("sw01-mgmt", primary_ip="10.0.0.2")
request = _make_request(use_sysname=False, strip_domain=False)
result = view.get_librenms_device_info(obj, request)
assert result["librenms_device_details"]["resolved_name"] == "sw01-mgmt"
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.resolve_naming_preferences")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view._generate_vc_member_name")
def test_resolved_name_vc_member(self, mock_vc_name, mock_prefs, mock_hw):
"""resolved_name applies VC member naming pattern for VC members."""
mock_prefs.return_value = (True, True)
mock_vc_name.return_value = "sw01-M2"
vc = MagicMock()
view = _make_view(42, {"sysName": "sw01.example.com", "hostname": "10.0.0.2", "ip": "10.0.0.2"})
obj = _make_obj("sw01-M2", primary_ip="10.0.0.2", virtual_chassis=vc, vc_position=2, serial="ABC123")
request = _make_request(use_sysname=True, strip_domain=True)
result = view.get_librenms_device_info(obj, request)
assert result["librenms_device_details"]["resolved_name"] == "sw01-M2"
mock_vc_name.assert_called_once_with("sw01", 2, serial="ABC123")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
def test_resolved_name_falls_back_to_sysname_without_request(self, mock_hw):
"""Without request, resolved_name falls back to raw sysName."""
view = _make_view(42, {"sysName": "sw01.example.com", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.2")
result = view.get_librenms_device_info(obj)
assert result["librenms_device_details"]["resolved_name"] == "sw01.example.com"
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.match_librenms_hardware_to_device_type")
@patch("netbox_librenms_plugin.views.base.librenms_sync_view.resolve_naming_preferences")
def test_resolved_name_non_vc_device_no_vc_pattern(self, mock_prefs, mock_hw):
"""Non-VC device does not apply VC member naming."""
mock_prefs.return_value = (True, True)
view = _make_view(42, {"sysName": "sw01.example.com", "hostname": "10.0.0.2", "ip": "10.0.0.2"})
obj = _make_obj("sw01", primary_ip="10.0.0.2")
obj.virtual_chassis = None
obj.vc_position = None
request = _make_request(use_sysname=True, strip_domain=True)
result = view.get_librenms_device_info(obj, request)
assert result["librenms_device_details"]["resolved_name"] == "sw01"