1348 lines
51 KiB
Python
1348 lines
51 KiB
Python
"""
|
|
Comprehensive tests for LibreNMSAPI client.
|
|
|
|
This module provides 100% test coverage for netbox_librenms_plugin/librenms_api.py,
|
|
with particular focus on HTTP method correctness to prevent regression bugs.
|
|
"""
|
|
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
import pytest
|
|
import requests
|
|
|
|
# Import the autouse fixture from helpers
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 1: Initialization (3 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIInit:
|
|
"""Test LibreNMSAPI initialization and configuration loading."""
|
|
|
|
def test_init_with_multi_server_config(self, mock_librenms_config):
|
|
"""Verify initialization with multi-server configuration."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
assert api.librenms_url == "https://librenms.example.com"
|
|
assert api.api_token == "test-token"
|
|
assert api.cache_timeout == 300
|
|
assert api.verify_ssl is True
|
|
|
|
def test_init_with_legacy_config(self, mock_librenms_config):
|
|
"""Verify initialization with legacy single-server config."""
|
|
mock_config = mock_librenms_config["mock_config"]
|
|
|
|
# Return None for 'servers' to trigger legacy path
|
|
def config_side_effect(plugin, key, default=None):
|
|
if key == "servers":
|
|
return None
|
|
legacy = {
|
|
"librenms_url": "https://legacy.example.com",
|
|
"api_token": "legacy-token",
|
|
"cache_timeout": 600,
|
|
"verify_ssl": False,
|
|
}
|
|
return legacy.get(key, default)
|
|
|
|
mock_config.side_effect = config_side_effect
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI()
|
|
|
|
assert api.librenms_url == "https://legacy.example.com"
|
|
|
|
def test_init_missing_config_raises_valueerror(self, mock_librenms_config):
|
|
"""Verify ValueError raised when configuration is missing."""
|
|
mock_config = mock_librenms_config["mock_config"]
|
|
mock_config.return_value = None
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
with pytest.raises(ValueError):
|
|
LibreNMSAPI(server_key="nonexistent")
|
|
|
|
def test_init_nonexistent_server_key_raises_keyerror(self, mock_librenms_config):
|
|
"""Verify KeyError raised when specific server_key doesn't exist."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
with pytest.raises(KeyError, match="nonexistent"):
|
|
LibreNMSAPI(server_key="nonexistent")
|
|
|
|
def test_init_default_falls_back_to_first_server(self, mock_librenms_config):
|
|
"""Verify 'default' key falls back to first configured server."""
|
|
mock_config = mock_librenms_config["mock_config"]
|
|
mock_config.return_value = {
|
|
"primary": {
|
|
"librenms_url": "https://primary.example.com",
|
|
"api_token": "primary-token",
|
|
}
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
assert api.server_key == "primary"
|
|
assert api.librenms_url == "https://primary.example.com"
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 2: Connection Testing (4 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIConnection:
|
|
"""Test connection testing functionality."""
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_connection_success(self, mock_get, mock_librenms_config):
|
|
"""Verify successful connection test."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"system": [{"local_ver": "24.1.0"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.test_connection()
|
|
|
|
assert result is not None
|
|
assert "error" not in result
|
|
mock_get.assert_called_once()
|
|
assert "/api/v0/system" in mock_get.call_args[0][0]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_connection_auth_failure_401(self, mock_get, mock_librenms_config):
|
|
"""Verify 401 unauthorized handling."""
|
|
mock_get.return_value.status_code = 401
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.test_connection()
|
|
|
|
assert result.get("error") is True
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_connection_auth_failure_403(self, mock_get, mock_librenms_config):
|
|
"""Verify 403 forbidden handling."""
|
|
mock_get.return_value.status_code = 403
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.test_connection()
|
|
|
|
assert result.get("error") is True
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_connection_timeout(self, mock_get, mock_librenms_config):
|
|
"""Verify timeout exception handling."""
|
|
mock_get.side_effect = requests.exceptions.Timeout("Connection timed out")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.test_connection()
|
|
|
|
assert result.get("error") is True
|
|
assert "timeout" in result.get("message", "").lower()
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 3: HTTP Methods - CRITICAL (18 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIHttpMethods:
|
|
"""
|
|
CRITICAL: Verify each API method uses the correct HTTP verb.
|
|
|
|
These tests prevent regression bugs where HTTP methods are accidentally
|
|
changed during refactoring (e.g., GET changed to DELETE).
|
|
"""
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_info_uses_get(self, mock_get, mock_post, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_info uses GET, never DELETE."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 1}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_info(device_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
mock_post.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_device_uses_post(self, mock_post, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify add_device uses POST."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {"status": "ok", "device_id": 42}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.add_device(
|
|
data={
|
|
"hostname": "test.example.com",
|
|
"snmp_version": "v2c",
|
|
"community": "public",
|
|
}
|
|
)
|
|
|
|
mock_post.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.patch")
|
|
def test_update_device_field_uses_patch(self, mock_patch, mock_delete, mock_librenms_config):
|
|
"""Verify update_device_field uses PATCH."""
|
|
mock_patch.return_value.status_code = 200
|
|
mock_patch.return_value.json.return_value = {"status": "ok"}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.update_device_field(device_id=1, field_data={"field": ["location"], "data": ["DC1"]})
|
|
|
|
mock_patch.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_id_by_ip_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_id_by_ip uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 10}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_id_by_ip("192.168.1.1")
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_id_by_hostname_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_id_by_hostname uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 20}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_id_by_hostname("test-host")
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_ports_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_ports uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "ports": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_ports(device_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_locations_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_locations uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "locations": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_locations()
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_location_uses_post(self, mock_post, mock_delete, mock_librenms_config):
|
|
"""Verify add_location uses POST."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Location created #1",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.add_location(location_data={"location": "DC1"})
|
|
|
|
mock_post.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.patch")
|
|
def test_update_location_uses_patch(self, mock_patch, mock_delete, mock_librenms_config):
|
|
"""Verify update_location uses PATCH."""
|
|
mock_patch.return_value.status_code = 200
|
|
mock_patch.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Location updated",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.update_location(location_name="DC1", location_data={"location": "DC1-Updated"})
|
|
|
|
mock_patch.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_links_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_links uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "links": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_links(device_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_ips_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_ips uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "addresses": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_ips(device_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_port_by_id_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_port_by_id uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "port": [{}]}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_port_by_id(port_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_inventory_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_device_inventory uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "inventory": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_device_inventory(device_id=1)
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_poller_groups_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_poller_groups uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "groups": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_poller_groups()
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_inventory_filtered_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify get_inventory_filtered uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
# Return non-empty inventory so it doesn't fall back to get_device_inventory
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"inventory": [{"entPhysicalClass": "chassis"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.get_inventory_filtered(device_id=1, ent_physical_class="chassis")
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.delete")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_list_devices_uses_get(self, mock_get, mock_delete, mock_librenms_config):
|
|
"""Verify list_devices uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "devices": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.list_devices()
|
|
|
|
mock_get.assert_called_once()
|
|
mock_delete.assert_not_called()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_test_connection_uses_get(self, mock_get, mock_librenms_config):
|
|
"""Verify test_connection uses GET."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok"}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
api.test_connection()
|
|
|
|
mock_get.assert_called_once()
|
|
|
|
|
|
# ====================================================================================
|
|
# Test Class 4: Device Lookup (6 tests)
|
|
# ====================================================================================
|
|
|
|
|
|
class TestLibreNMSAPIDeviceLookup:
|
|
"""Test device lookup functionality."""
|
|
|
|
def test_get_librenms_id_from_custom_field(self, mock_librenms_config):
|
|
"""Returns ID when already stored in cf['librenms_id']."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {"librenms_id": 42}
|
|
|
|
result = api.get_librenms_id(device)
|
|
assert result == 42
|
|
|
|
def test_get_librenms_id_normalizes_string_to_int(self, mock_librenms_config):
|
|
"""Returns int for string-stored librenms_id; read path uses auto_save=False so no write-back."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {"librenms_id": "42"}
|
|
device.custom_field_data = {"librenms_id": "42"}
|
|
|
|
result = api.get_librenms_id(device)
|
|
assert result == 42
|
|
# auto_save=False in this read path — normalization is NOT written back
|
|
device.save.assert_not_called()
|
|
|
|
def test_get_librenms_id_empty_string_falls_through_to_discovery(self, mock_librenms_config):
|
|
"""An empty-string librenms_id is treated as not set (falls through to API discovery)."""
|
|
from unittest.mock import patch
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {"librenms_id": ""}
|
|
device.primary_ip = None
|
|
|
|
with patch.object(api, "get_device_id_by_hostname", return_value=None):
|
|
result = api.get_librenms_id(device)
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.cache")
|
|
def test_get_librenms_id_from_cache(self, mock_cache, mock_librenms_config):
|
|
"""Returns ID from Django cache when not in custom field."""
|
|
mock_cache.get.return_value = 99
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {}
|
|
|
|
result = api.get_librenms_id(device)
|
|
assert result == 99
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.cache")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_librenms_id_by_ip_lookup(self, mock_get, mock_cache, mock_librenms_config):
|
|
"""Performs IP lookup and caches result."""
|
|
mock_cache.get.return_value = None
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 55}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {}
|
|
device.primary_ip4 = MagicMock()
|
|
device.primary_ip4.address = MagicMock()
|
|
device.primary_ip4.address.ip = "10.0.0.1"
|
|
|
|
result = api.get_librenms_id(device)
|
|
assert result == 55
|
|
mock_cache.set.assert_called_once()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.cache")
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_librenms_id_by_hostname_lookup(self, mock_get, mock_cache, mock_librenms_config):
|
|
"""Falls back to hostname lookup."""
|
|
mock_cache.get.return_value = None
|
|
|
|
# First call (IP lookup) returns empty, second call (hostname lookup) returns device
|
|
call_count = [0]
|
|
|
|
def side_effect(*args, **kwargs):
|
|
resp = MagicMock()
|
|
resp.status_code = 200
|
|
call_count[0] += 1
|
|
if call_count[0] == 2: # Second call is hostname lookup
|
|
resp.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 77}],
|
|
}
|
|
else:
|
|
resp.json.return_value = {"status": "ok", "devices": []}
|
|
return resp
|
|
|
|
mock_get.side_effect = side_effect
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
device = MagicMock()
|
|
device.name = "test-device"
|
|
device.cf = {}
|
|
device.primary_ip4 = MagicMock()
|
|
device.primary_ip4.address = MagicMock()
|
|
device.primary_ip4.address.ip = "10.0.0.1"
|
|
|
|
result = api.get_librenms_id(device)
|
|
assert result == 77
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_id_by_ip_not_found(self, mock_get, mock_librenms_config):
|
|
"""Returns None when IP not found in LibreNMS."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "devices": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.get_device_id_by_ip("192.168.99.99")
|
|
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_id_by_hostname_not_found(self, mock_get, mock_librenms_config):
|
|
"""Returns None when hostname not found."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "devices": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.get_device_id_by_hostname("nonexistent-host")
|
|
|
|
assert result is None
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 5: Device Operations (6 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIDeviceOperations:
|
|
"""Test device CRUD operations."""
|
|
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_device_success(self, mock_post, mock_librenms_config):
|
|
"""Verify successful device addition."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Device added successfully",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.add_device(
|
|
data={
|
|
"hostname": "test-device.example.com",
|
|
"snmp_version": "v2c",
|
|
"community": "public",
|
|
}
|
|
)
|
|
|
|
assert result[0] is True
|
|
assert result[1] == "Device added successfully."
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_device_snmpv1_success(self, mock_post, mock_librenms_config):
|
|
"""Verify successful device addition using SNMPv1."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Device added successfully",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.add_device(
|
|
data={
|
|
"hostname": "legacy-device.example.com",
|
|
"snmp_version": "v1",
|
|
"community": "public",
|
|
}
|
|
)
|
|
|
|
assert result[0] is True
|
|
assert result[1] == "Device added successfully."
|
|
# Verify the payload includes correct snmpver and community
|
|
call_args = mock_post.call_args
|
|
payload = call_args.kwargs.get("json") or call_args[1].get("json")
|
|
assert payload["snmpver"] == "v1"
|
|
assert payload["community"] == "public"
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_device_duplicate_error(self, mock_post, mock_librenms_config):
|
|
"""Verify duplicate device handling."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "error",
|
|
"message": "Device already exists",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.add_device(
|
|
data={
|
|
"hostname": "duplicate-device.example.com",
|
|
"snmp_version": "v2c",
|
|
"community": "public",
|
|
}
|
|
)
|
|
|
|
assert result[0] is False
|
|
assert "Device already exists" in result[1]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_device_snmpv3_success(self, mock_post, mock_librenms_config):
|
|
"""Verify successful device addition using SNMPv3 with all required fields."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Device added successfully",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.add_device(
|
|
data={
|
|
"hostname": "secure-device.example.com",
|
|
"snmp_version": "v3",
|
|
"authlevel": "authPriv",
|
|
"authname": "snmpuser",
|
|
"authpass": "authpassword123",
|
|
"authalgo": "SHA",
|
|
"cryptopass": "cryptopassword456",
|
|
"cryptoalgo": "AES",
|
|
}
|
|
)
|
|
|
|
assert result[0] is True
|
|
assert result[1] == "Device added successfully."
|
|
# Verify the payload includes correct snmpver and all v3 fields
|
|
call_args = mock_post.call_args
|
|
payload = call_args.kwargs.get("json") or call_args[1].get("json")
|
|
assert payload["snmpver"] == "v3"
|
|
assert payload["authlevel"] == "authPriv"
|
|
assert payload["authname"] == "snmpuser"
|
|
assert payload["authpass"] == "authpassword123"
|
|
assert payload["authalgo"] == "SHA"
|
|
assert payload["cryptopass"] == "cryptopassword456"
|
|
assert payload["cryptoalgo"] == "AES"
|
|
# Ensure community is NOT included for v3
|
|
assert "community" not in payload
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.patch")
|
|
def test_update_device_field_success(self, mock_patch, mock_librenms_config):
|
|
"""Verify successful device field update."""
|
|
mock_patch.return_value.status_code = 200
|
|
mock_patch.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Device field updated",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, message = api.update_device_field(device_id=123, field_data={"field": "notes", "data": "Updated note"})
|
|
|
|
assert success is True
|
|
assert "updated" in message.lower()
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_info_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving device info."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 123, "hostname": "test-device"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, device_data = api.get_device_info(device_id=123)
|
|
|
|
assert success is True
|
|
assert device_data is not None
|
|
assert device_data["device_id"] == 123
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_info_not_found(self, mock_get, mock_librenms_config):
|
|
"""Empty devices list returns (False, None) without raising."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "devices": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
success, result = api.get_device_info(1)
|
|
assert success is False
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_list_devices_with_filters(self, mock_get, mock_librenms_config):
|
|
"""Verify listing devices with filter parameter."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 1}, {"device_id": 2}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, devices = api.list_devices(filters={"type": "network"})
|
|
|
|
assert success is True
|
|
assert len(devices) == 2
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 6: Location Operations (4 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPILocationOperations:
|
|
"""Test location CRUD operations."""
|
|
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_locations_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving all locations."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"locations": [{"id": 1, "location": "DC1"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, locations = api.get_locations()
|
|
|
|
assert success is True
|
|
assert len(locations) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_location_success(self, mock_post, mock_librenms_config):
|
|
"""Verify successful location addition."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Location created #5",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, result_dict = api.add_location(location_data={"location": "DC2"})
|
|
|
|
assert success is True
|
|
assert result_dict["id"] == "5"
|
|
assert "message" in result_dict
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_add_location_error(self, mock_post, mock_librenms_config):
|
|
"""Verify location addition error handling."""
|
|
mock_post.return_value.status_code = 500
|
|
mock_post.return_value.json.return_value = {
|
|
"status": "error",
|
|
"message": "Invalid location data",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, error_msg = api.add_location(location_data={})
|
|
|
|
assert success is False
|
|
assert "Invalid location data" in error_msg
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.patch")
|
|
def test_update_location_success(self, mock_patch, mock_librenms_config):
|
|
"""Verify successful location update."""
|
|
mock_patch.return_value.status_code = 200
|
|
mock_patch.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"message": "Location updated",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, message = api.update_location(location_name="DC1", location_data={"location": "DC1-Updated"})
|
|
|
|
assert success is True
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.patch")
|
|
def test_update_location_not_found(self, mock_patch, mock_librenms_config):
|
|
"""Verify updating non-existent location."""
|
|
mock_patch.return_value.status_code = 404
|
|
mock_patch.return_value.json.return_value = {
|
|
"status": "error",
|
|
"message": "Location not found",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, message = api.update_location(location_name="NonExistent", location_data={})
|
|
|
|
assert success is False
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 7: Ports and Inventory (9 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIPortsAndInventory:
|
|
"""Test ports and inventory operations."""
|
|
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_ports_all(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving all ports for a device."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"ports": [{"port_id": 1}, {"port_id": 2}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, data = api.get_ports(device_id=123)
|
|
|
|
assert success is True
|
|
assert "ports" in data
|
|
assert len(data["ports"]) == 2
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_port_by_id_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving port by ID."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"port": [{"port_id": 1, "ifName": "GigabitEthernet0/1"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, port_data = api.get_port_by_id(port_id=1)
|
|
|
|
assert success is True
|
|
assert port_data is not None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_port_by_id_error(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of port retrieval error."""
|
|
mock_get.side_effect = requests.exceptions.RequestException("Connection error")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, error_msg = api.get_port_by_id(port_id=999)
|
|
|
|
assert success is False
|
|
assert isinstance(error_msg, str)
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_inventory_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving device inventory."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"inventory": [{"entPhysicalClass": "chassis"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, inventory = api.get_device_inventory(device_id=123)
|
|
|
|
assert success is True
|
|
assert len(inventory) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_inventory_filtered_by_class(self, mock_get, mock_librenms_config):
|
|
"""Verify filtering inventory by physical class."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"inventory": [{"entPhysicalClass": "chassis", "entPhysicalName": "Chassis"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, inventory = api.get_inventory_filtered(device_id=123, ent_physical_class="chassis")
|
|
|
|
assert success is True
|
|
assert len(inventory) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_inventory_filtered_by_container(self, mock_get, mock_librenms_config):
|
|
"""Verify filtering inventory by container."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"inventory": [{"entPhysicalContainedIn": "0"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, inventory = api.get_inventory_filtered(device_id=123, ent_physical_contained_in=0)
|
|
|
|
assert success is True
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_links_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving device links."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"links": [{"id": 1, "local_port_id": 10, "remote_port_id": 20}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, links_dict = api.get_device_links(device_id=123)
|
|
|
|
assert success is True
|
|
assert "links" in links_dict
|
|
assert len(links_dict["links"]) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_ips_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving device IP addresses."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"addresses": [{"ipv4_address": "10.0.0.1"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, ips = api.get_device_ips(device_id=123)
|
|
|
|
assert success is True
|
|
assert len(ips) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_device_ips_empty(self, mock_get, mock_librenms_config):
|
|
"""Verify handling device with no IPs."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "addresses": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, ips = api.get_device_ips(device_id=123)
|
|
|
|
assert success is True
|
|
assert len(ips) == 0
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 8: Poller and Devices (4 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIPollerAndDevices:
|
|
"""Test poller groups and device listing operations."""
|
|
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_poller_groups_success(self, mock_get, mock_librenms_config):
|
|
"""Verify retrieving poller groups."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"get_poller_group": [{"id": 1, "group_name": "primary"}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, groups = api.get_poller_groups()
|
|
|
|
assert success is True
|
|
assert len(groups) == 1
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_get_poller_groups_empty(self, mock_get, mock_librenms_config):
|
|
"""Verify handling empty poller groups."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"get_poller_group": [],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, groups = api.get_poller_groups()
|
|
|
|
assert success is True
|
|
assert len(groups) == 0
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_list_devices_all(self, mock_get, mock_librenms_config):
|
|
"""Verify listing all devices."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "ok",
|
|
"devices": [{"device_id": 1}, {"device_id": 2}, {"device_id": 3}],
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, devices = api.list_devices()
|
|
|
|
assert success is True
|
|
assert len(devices) == 3
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_list_devices_empty(self, mock_get, mock_librenms_config):
|
|
"""Verify handling empty device list."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.return_value = {"status": "ok", "devices": []}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, devices = api.list_devices()
|
|
|
|
assert success is True
|
|
assert len(devices) == 0
|
|
|
|
|
|
# =============================================================================
|
|
# Test Class 9: Error Handling (6 tests)
|
|
# =============================================================================
|
|
|
|
|
|
class TestLibreNMSAPIErrorHandling:
|
|
"""Test error handling and edge cases."""
|
|
|
|
pytest_plugins = ["netbox_librenms_plugin.tests.test_librenms_api_helpers"]
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_network_error_handling(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of network errors."""
|
|
mock_get.side_effect = requests.exceptions.ConnectionError("Network unreachable")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, result = api.get_device_info(device_id=123)
|
|
|
|
assert success is False
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_timeout_error_handling(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of timeout errors."""
|
|
mock_get.side_effect = requests.exceptions.Timeout("Request timed out")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, result = api.get_device_info(device_id=123)
|
|
|
|
assert success is False
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_invalid_json_response(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of invalid JSON responses — ValueError is now caught gracefully."""
|
|
mock_get.return_value.status_code = 200
|
|
mock_get.return_value.json.side_effect = ValueError("Invalid JSON")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
# ValueError is now caught, returning (False, None) instead of propagating
|
|
success, result = api.get_device_info(device_id=123)
|
|
assert success is False
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_http_500_error_handling(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of HTTP 500 errors."""
|
|
mock_get.return_value.status_code = 500
|
|
mock_get.return_value.json.return_value = {
|
|
"status": "error",
|
|
"message": "Internal server error",
|
|
}
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
success, result = api.get_device_info(device_id=123)
|
|
|
|
assert success is False
|
|
assert result is None
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.post")
|
|
def test_malformed_api_response(self, mock_post, mock_librenms_config):
|
|
"""Verify handling of malformed API responses."""
|
|
mock_post.return_value.status_code = 200
|
|
mock_post.return_value.json.return_value = {} # Missing expected fields
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.add_device(
|
|
data={
|
|
"hostname": "test.example.com",
|
|
"snmp_version": "v2c",
|
|
"community": "public",
|
|
}
|
|
)
|
|
|
|
# Should handle missing fields gracefully
|
|
assert result[0] is False
|
|
|
|
@patch("netbox_librenms_plugin.librenms_api.requests.get")
|
|
def test_ssl_verification_error(self, mock_get, mock_librenms_config):
|
|
"""Verify handling of SSL verification errors."""
|
|
mock_get.side_effect = requests.exceptions.SSLError("SSL certificate verification failed")
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
result = api.test_connection()
|
|
|
|
assert result.get("error") is True
|
|
|
|
|
|
# ====================================================================================
|
|
# Test Class 5-9 continuing in next part due to length...
|
|
# Run `make unittest` to execute all tests
|
|
# ====================================================================================
|
|
|
|
|
|
# ====================================================================================
|
|
# Guard tests: int conversion guard and VLAN dict guard
|
|
# ====================================================================================
|
|
|
|
|
|
class TestGetLibreNMSIdIntGuard:
|
|
"""Tests for the int conversion guard in get_librenms_id."""
|
|
|
|
def test_non_integer_string_from_ip_returns_none(self, mock_librenms_config):
|
|
"""When get_device_id_by_ip returns a non-int string, _store_librenms_id must not be called."""
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
obj = MagicMock()
|
|
obj.primary_ip.address.ip = "192.168.1.1"
|
|
obj.primary_ip.dns_name = ""
|
|
obj.name = "test-device"
|
|
obj._meta.model_name = "device"
|
|
obj.pk = 1
|
|
|
|
with (
|
|
patch.object(api, "_get_cache_key", return_value="test_cache_key"),
|
|
patch("netbox_librenms_plugin.librenms_api.cache") as mock_cache,
|
|
patch.object(api, "get_device_id_by_ip", return_value="not-an-int"),
|
|
patch.object(api, "get_device_id_by_hostname", return_value=None),
|
|
patch.object(api, "_store_librenms_id") as mock_store,
|
|
):
|
|
mock_cache.get.return_value = None
|
|
result = api.get_librenms_id(obj)
|
|
|
|
assert result is None
|
|
mock_store.assert_not_called()
|
|
|
|
def test_valid_integer_string_stores_and_returns(self, mock_librenms_config):
|
|
"""When get_device_id_by_ip returns a valid int string, it should be stored and returned."""
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
obj = MagicMock()
|
|
obj.primary_ip.address.ip = "192.168.1.1"
|
|
obj.primary_ip.dns_name = ""
|
|
obj.name = "test-device"
|
|
obj._meta.model_name = "device"
|
|
obj.pk = 1
|
|
|
|
with (
|
|
patch.object(api, "_get_cache_key", return_value="test_cache_key"),
|
|
patch("netbox_librenms_plugin.librenms_api.cache") as mock_cache,
|
|
patch.object(api, "get_device_id_by_ip", return_value="42"),
|
|
patch.object(api, "_store_librenms_id") as mock_store,
|
|
):
|
|
mock_cache.get.return_value = None
|
|
result = api.get_librenms_id(obj)
|
|
|
|
assert result == 42
|
|
mock_store.assert_called_once_with(obj, 42)
|
|
|
|
|
|
class TestVlanEntryDictGuard:
|
|
"""Tests for the isinstance(vlan_entry, dict) guard in _parse_port_vlan_info."""
|
|
|
|
def test_non_dict_entry_is_skipped(self, mock_librenms_config):
|
|
"""Non-dict entries in vlans_data should be skipped without error."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
port_data = {
|
|
"ifName": "eth0",
|
|
"ifDescr": "eth0",
|
|
"port_id": 1,
|
|
"ifTrunk": "dot1Q",
|
|
"ifVlan": None,
|
|
"vlans": [{"vlan": 10, "untagged": 1}, "bad_entry", {"vlan": 20}],
|
|
}
|
|
result = api.parse_port_vlan_data(port_data)
|
|
|
|
# Only VIDs 10 and 20 should be parsed; string entry is skipped
|
|
assert result["untagged_vlan"] == 10
|
|
assert 20 in result["tagged_vlans"]
|
|
assert len(result["tagged_vlans"]) == 1
|
|
|
|
def test_mixed_bad_entries_no_exception(self, mock_librenms_config):
|
|
"""Multiple non-dict entries mixed with valid dicts should not raise."""
|
|
from netbox_librenms_plugin.librenms_api import LibreNMSAPI
|
|
|
|
api = LibreNMSAPI(server_key="default")
|
|
|
|
port_data = {
|
|
"ifName": "eth0",
|
|
"ifDescr": "eth0",
|
|
"port_id": 2,
|
|
"ifTrunk": "dot1Q",
|
|
"ifVlan": None,
|
|
"vlans": [None, 123, "unexpected", {"vlan": 30}],
|
|
}
|
|
result = api.parse_port_vlan_data(port_data)
|
|
|
|
assert result["tagged_vlans"] == [30]
|
|
assert result["untagged_vlan"] is None
|