mirror of
https://github.com/TheNetworkGuy/netbox-zabbix-sync.git
synced 2026-03-21 20:18:38 -06:00
464 lines
19 KiB
Python
464 lines
19 KiB
Python
"""Tests for the Hostgroup class in the hostgroups module."""
|
|
|
|
import unittest
|
|
from unittest.mock import MagicMock, patch
|
|
|
|
from netbox_zabbix_sync.modules.exceptions import HostgroupError
|
|
from netbox_zabbix_sync.modules.hostgroups import Hostgroup
|
|
|
|
|
|
class TestHostgroups(unittest.TestCase):
|
|
"""Test class for Hostgroup functionality."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
# Create mock logger
|
|
self.mock_logger = MagicMock()
|
|
|
|
# *** Mock NetBox Device setup ***
|
|
# Create mock device with all properties
|
|
self.mock_device = MagicMock()
|
|
self.mock_device.name = "test-device"
|
|
|
|
# Set up site information
|
|
site = MagicMock()
|
|
site.name = "TestSite"
|
|
|
|
# Set up region information
|
|
region = MagicMock()
|
|
region.name = "TestRegion"
|
|
# Ensure region string representation returns the name
|
|
region.__str__.return_value = "TestRegion"
|
|
site.region = region
|
|
|
|
# Set up site group information
|
|
site_group = MagicMock()
|
|
site_group.name = "TestSiteGroup"
|
|
# Ensure site group string representation returns the name
|
|
site_group.__str__.return_value = "TestSiteGroup"
|
|
site.group = site_group
|
|
|
|
self.mock_device.site = site
|
|
|
|
# Set up role information (varies based on NetBox version)
|
|
self.mock_device_role = MagicMock()
|
|
self.mock_device_role.name = "TestRole"
|
|
# Ensure string representation returns the name
|
|
self.mock_device_role.__str__.return_value = "TestRole"
|
|
self.mock_device.device_role = self.mock_device_role
|
|
self.mock_device.role = self.mock_device_role
|
|
|
|
# Set up tenant information
|
|
tenant = MagicMock()
|
|
tenant.name = "TestTenant"
|
|
# Ensure tenant string representation returns the name
|
|
tenant.__str__.return_value = "TestTenant"
|
|
tenant_group = MagicMock()
|
|
tenant_group.name = "TestTenantGroup"
|
|
# Ensure tenant group string representation returns the name
|
|
tenant_group.__str__.return_value = "TestTenantGroup"
|
|
tenant.group = tenant_group
|
|
self.mock_device.tenant = tenant
|
|
|
|
# Set up platform information
|
|
platform = MagicMock()
|
|
platform.name = "TestPlatform"
|
|
self.mock_device.platform = platform
|
|
|
|
# Device-specific properties
|
|
device_type = MagicMock()
|
|
manufacturer = MagicMock()
|
|
manufacturer.name = "TestManufacturer"
|
|
device_type.manufacturer = manufacturer
|
|
self.mock_device.device_type = device_type
|
|
|
|
location = MagicMock()
|
|
location.name = "TestLocation"
|
|
# Ensure location string representation returns the name
|
|
location.__str__.return_value = "TestLocation"
|
|
self.mock_device.location = location
|
|
|
|
rack = MagicMock()
|
|
rack.name = "TestRack"
|
|
self.mock_device.rack = rack
|
|
|
|
# Custom fields — empty_cf is intentionally None to test the empty CF path
|
|
self.mock_device.custom_fields = {"test_cf": "TestCF", "empty_cf": None}
|
|
|
|
# *** Mock NetBox VM setup ***
|
|
# Create mock VM with all properties
|
|
self.mock_vm = MagicMock()
|
|
self.mock_vm.name = "test-vm"
|
|
|
|
# Reuse site from device
|
|
self.mock_vm.site = site
|
|
|
|
# Set up role for VM
|
|
self.mock_vm.role = self.mock_device_role
|
|
|
|
# Set up tenant for VM (same as device)
|
|
self.mock_vm.tenant = tenant
|
|
|
|
# Set up platform for VM (same as device)
|
|
self.mock_vm.platform = platform
|
|
|
|
# VM-specific properties
|
|
cluster = MagicMock()
|
|
cluster.name = "TestCluster"
|
|
cluster_type = MagicMock()
|
|
cluster_type.name = "TestClusterType"
|
|
cluster.type = cluster_type
|
|
self.mock_vm.cluster = cluster
|
|
|
|
# Custom fields
|
|
self.mock_vm.custom_fields = {"test_cf": "TestCF"}
|
|
|
|
# Mock data for nesting tests
|
|
self.mock_regions_data = [
|
|
{"name": "ParentRegion", "parent": None, "_depth": 0},
|
|
{"name": "TestRegion", "parent": "ParentRegion", "_depth": 1},
|
|
]
|
|
|
|
self.mock_groups_data = [
|
|
{"name": "ParentSiteGroup", "parent": None, "_depth": 0},
|
|
{"name": "TestSiteGroup", "parent": "ParentSiteGroup", "_depth": 1},
|
|
]
|
|
|
|
def test_device_hostgroup_creation(self):
|
|
"""Test basic device hostgroup creation."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Test the string representation
|
|
self.assertEqual(str(hostgroup), "Hostgroup for dev test-device")
|
|
|
|
# Check format options were set correctly
|
|
self.assertEqual(hostgroup.format_options["site"], "TestSite")
|
|
self.assertEqual(hostgroup.format_options["region"], "TestRegion")
|
|
self.assertEqual(hostgroup.format_options["site_group"], "TestSiteGroup")
|
|
self.assertEqual(hostgroup.format_options["role"], "TestRole")
|
|
self.assertEqual(hostgroup.format_options["tenant"], "TestTenant")
|
|
self.assertEqual(hostgroup.format_options["tenant_group"], "TestTenantGroup")
|
|
self.assertEqual(hostgroup.format_options["platform"], "TestPlatform")
|
|
self.assertEqual(hostgroup.format_options["manufacturer"], "TestManufacturer")
|
|
self.assertEqual(hostgroup.format_options["location"], "TestLocation")
|
|
self.assertEqual(hostgroup.format_options["rack"], "TestRack")
|
|
|
|
def test_vm_hostgroup_creation(self):
|
|
"""Test basic VM hostgroup creation."""
|
|
hostgroup = Hostgroup("vm", self.mock_vm, "4.0", self.mock_logger)
|
|
|
|
# Test the string representation
|
|
self.assertEqual(str(hostgroup), "Hostgroup for vm test-vm")
|
|
|
|
# Check format options were set correctly
|
|
self.assertEqual(hostgroup.format_options["site"], "TestSite")
|
|
self.assertEqual(hostgroup.format_options["region"], "TestRegion")
|
|
self.assertEqual(hostgroup.format_options["site_group"], "TestSiteGroup")
|
|
self.assertEqual(hostgroup.format_options["role"], "TestRole")
|
|
self.assertEqual(hostgroup.format_options["tenant"], "TestTenant")
|
|
self.assertEqual(hostgroup.format_options["tenant_group"], "TestTenantGroup")
|
|
self.assertEqual(hostgroup.format_options["platform"], "TestPlatform")
|
|
self.assertEqual(hostgroup.format_options["cluster"], "TestCluster")
|
|
self.assertEqual(hostgroup.format_options["cluster_type"], "TestClusterType")
|
|
|
|
def test_invalid_object_type(self):
|
|
"""Test that an invalid object type raises an exception."""
|
|
with self.assertRaises(HostgroupError):
|
|
Hostgroup("invalid", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
def test_device_hostgroup_formats(self):
|
|
"""Test different hostgroup formats for devices."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Custom format: site/region
|
|
custom_result = hostgroup.generate("site/region")
|
|
self.assertEqual(custom_result, "TestSite/TestRegion")
|
|
|
|
# Custom format: site/tenant/platform/location
|
|
complex_result = hostgroup.generate("site/tenant/platform/location")
|
|
self.assertEqual(
|
|
complex_result, "TestSite/TestTenant/TestPlatform/TestLocation"
|
|
)
|
|
|
|
def test_vm_hostgroup_formats(self):
|
|
"""Test different hostgroup formats for VMs."""
|
|
hostgroup = Hostgroup("vm", self.mock_vm, "4.0", self.mock_logger)
|
|
|
|
# Default format: cluster/role
|
|
default_result = hostgroup.generate("cluster/role")
|
|
self.assertEqual(default_result, "TestCluster/TestRole")
|
|
|
|
# Custom format: site/tenant
|
|
custom_result = hostgroup.generate("site/tenant")
|
|
self.assertEqual(custom_result, "TestSite/TestTenant")
|
|
|
|
# Custom format: cluster/cluster_type/platform
|
|
complex_result = hostgroup.generate("cluster/cluster_type/platform")
|
|
self.assertEqual(complex_result, "TestCluster/TestClusterType/TestPlatform")
|
|
|
|
def test_device_netbox_version_differences(self):
|
|
"""Test hostgroup generation with different NetBox versions.
|
|
|
|
device_role (v2/v3) and role (v4+) are set to different values so the
|
|
test can verify that the correct attribute is read for each version.
|
|
"""
|
|
# Build a device with deliberately different names on each role attribute
|
|
versioned_device = MagicMock()
|
|
versioned_device.name = "versioned-device"
|
|
versioned_device.site = self.mock_device.site
|
|
versioned_device.tenant = self.mock_device.tenant
|
|
versioned_device.platform = self.mock_device.platform
|
|
versioned_device.location = self.mock_device.location
|
|
versioned_device.rack = self.mock_device.rack
|
|
versioned_device.device_type = self.mock_device.device_type
|
|
versioned_device.custom_fields = self.mock_device.custom_fields
|
|
|
|
old_role = MagicMock()
|
|
old_role.name = "OldRole"
|
|
new_role = MagicMock()
|
|
new_role.name = "NewRole"
|
|
versioned_device.device_role = old_role # read by NetBox v2 / v3 code path
|
|
versioned_device.role = new_role # read by NetBox v4+ code path
|
|
|
|
# v2 must use device_role
|
|
hostgroup_v2 = Hostgroup("dev", versioned_device, "2.11", self.mock_logger)
|
|
self.assertEqual(hostgroup_v2.format_options["role"], "OldRole")
|
|
|
|
# v3 must also use device_role
|
|
hostgroup_v3 = Hostgroup("dev", versioned_device, "3.5", self.mock_logger)
|
|
self.assertEqual(hostgroup_v3.format_options["role"], "OldRole")
|
|
|
|
# v4+ must use role
|
|
hostgroup_v4 = Hostgroup("dev", versioned_device, "4.0", self.mock_logger)
|
|
self.assertEqual(hostgroup_v4.format_options["role"], "NewRole")
|
|
|
|
def test_custom_field_lookup(self):
|
|
"""Test custom field lookup functionality."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Test custom field exists and is populated
|
|
cf_result = hostgroup.custom_field_lookup("test_cf")
|
|
self.assertTrue(cf_result["result"])
|
|
self.assertEqual(cf_result["cf"], "TestCF")
|
|
|
|
# Test custom field doesn't exist
|
|
cf_result = hostgroup.custom_field_lookup("nonexistent_cf")
|
|
self.assertFalse(cf_result["result"])
|
|
self.assertIsNone(cf_result["cf"])
|
|
|
|
# Test custom field exists but has no value (None)
|
|
cf_result = hostgroup.custom_field_lookup("empty_cf")
|
|
self.assertTrue(cf_result["result"]) # key is present
|
|
self.assertIsNone(cf_result["cf"]) # value is empty
|
|
|
|
def test_hostgroup_with_custom_field(self):
|
|
"""Test hostgroup generation including a custom field."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Generate with custom field included
|
|
result = hostgroup.generate("site/test_cf/role")
|
|
self.assertEqual(result, "TestSite/TestCF/TestRole")
|
|
|
|
def test_missing_hostgroup_format_item(self):
|
|
"""Test handling of missing hostgroup format items."""
|
|
# Create a device with minimal attributes
|
|
minimal_device = MagicMock()
|
|
minimal_device.name = "minimal-device"
|
|
minimal_device.site = None
|
|
minimal_device.tenant = None
|
|
minimal_device.platform = None
|
|
minimal_device.custom_fields = {}
|
|
|
|
# Create role
|
|
role = MagicMock()
|
|
role.name = "MinimalRole"
|
|
minimal_device.role = role
|
|
|
|
# Create device_type with manufacturer
|
|
device_type = MagicMock()
|
|
manufacturer = MagicMock()
|
|
manufacturer.name = "MinimalManufacturer"
|
|
device_type.manufacturer = manufacturer
|
|
minimal_device.device_type = device_type
|
|
|
|
# Create hostgroup
|
|
hostgroup = Hostgroup("dev", minimal_device, "4.0", self.mock_logger)
|
|
|
|
# Generate with default format
|
|
result = hostgroup.generate("site/manufacturer/role")
|
|
# Site is missing, so only manufacturer and role should be included
|
|
self.assertEqual(result, "MinimalManufacturer/MinimalRole")
|
|
|
|
# Test with invalid format
|
|
with self.assertRaises(HostgroupError):
|
|
hostgroup.generate("site/nonexistent/role")
|
|
|
|
def test_nested_region_hostgroups(self):
|
|
"""Test hostgroup generation with nested regions."""
|
|
# Mock the build_path function to return a predictable result
|
|
with patch(
|
|
"netbox_zabbix_sync.modules.hostgroups.build_path"
|
|
) as mock_build_path:
|
|
# Configure the mock to return a list of regions in the path
|
|
mock_build_path.return_value = ["ParentRegion", "TestRegion"]
|
|
|
|
# Create hostgroup with nested regions enabled
|
|
hostgroup = Hostgroup(
|
|
"dev",
|
|
self.mock_device,
|
|
"4.0",
|
|
self.mock_logger,
|
|
nested_region_flag=True,
|
|
nb_regions=self.mock_regions_data,
|
|
)
|
|
|
|
# Generate hostgroup with region
|
|
result = hostgroup.generate("site/region/role")
|
|
# Should include the parent region
|
|
self.assertEqual(result, "TestSite/ParentRegion/TestRegion/TestRole")
|
|
|
|
def test_nested_sitegroup_hostgroups(self):
|
|
"""Test hostgroup generation with nested site groups."""
|
|
# Mock the build_path function to return a predictable result
|
|
with patch(
|
|
"netbox_zabbix_sync.modules.hostgroups.build_path"
|
|
) as mock_build_path:
|
|
# Configure the mock to return a list of site groups in the path
|
|
mock_build_path.return_value = ["ParentSiteGroup", "TestSiteGroup"]
|
|
|
|
# Create hostgroup with nested site groups enabled
|
|
hostgroup = Hostgroup(
|
|
"dev",
|
|
self.mock_device,
|
|
"4.0",
|
|
self.mock_logger,
|
|
nested_sitegroup_flag=True,
|
|
nb_groups=self.mock_groups_data,
|
|
)
|
|
|
|
# Generate hostgroup with site_group
|
|
result = hostgroup.generate("site/site_group/role")
|
|
# Should include the parent site group
|
|
self.assertEqual(result, "TestSite/ParentSiteGroup/TestSiteGroup/TestRole")
|
|
|
|
def test_vm_list_based_hostgroup_format(self):
|
|
"""Test VM hostgroup generation with a list-based format."""
|
|
hostgroup = Hostgroup("vm", self.mock_vm, "4.0", self.mock_logger)
|
|
|
|
# Test with a list of format strings
|
|
format_list = ["platform", "role", "cluster_type/cluster"]
|
|
|
|
# Generate hostgroups for each format in the list
|
|
hostgroups = []
|
|
for fmt in format_list:
|
|
result = hostgroup.generate(fmt)
|
|
if result: # Only add non-None results
|
|
hostgroups.append(result)
|
|
|
|
# Verify each expected hostgroup is generated
|
|
self.assertEqual(len(hostgroups), 3) # Should have 3 hostgroups
|
|
self.assertIn("TestPlatform", hostgroups)
|
|
self.assertIn("TestRole", hostgroups)
|
|
self.assertIn("TestClusterType/TestCluster", hostgroups)
|
|
|
|
def test_nested_format_splitting(self):
|
|
"""Test that formats with slashes correctly split and resolve each component."""
|
|
hostgroup = Hostgroup("vm", self.mock_vm, "4.0", self.mock_logger)
|
|
|
|
# Test a format with slashes that should be split
|
|
complex_format = "cluster_type/cluster"
|
|
result = hostgroup.generate(complex_format)
|
|
|
|
# Verify the format is correctly split and each component resolved
|
|
self.assertEqual(result, "TestClusterType/TestCluster")
|
|
|
|
def test_multiple_hostgroup_formats_device(self):
|
|
"""Test device hostgroup generation with multiple formats."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Test with various formats that would be in a list
|
|
formats = [
|
|
"site",
|
|
"manufacturer/role",
|
|
"platform/location",
|
|
"tenant_group/tenant",
|
|
]
|
|
|
|
# Generate and check each format
|
|
results = {}
|
|
for fmt in formats:
|
|
results[fmt] = hostgroup.generate(fmt)
|
|
|
|
# Verify results
|
|
self.assertEqual(results["site"], "TestSite")
|
|
self.assertEqual(results["manufacturer/role"], "TestManufacturer/TestRole")
|
|
self.assertEqual(results["platform/location"], "TestPlatform/TestLocation")
|
|
self.assertEqual(results["tenant_group/tenant"], "TestTenantGroup/TestTenant")
|
|
|
|
def test_literal_string_in_format(self):
|
|
"""Test that quoted literal strings in a format are used verbatim."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# Single-quoted literal
|
|
result = hostgroup.generate("'MyDevices'/role")
|
|
self.assertEqual(result, "MyDevices/TestRole")
|
|
|
|
# Double-quoted literal
|
|
result = hostgroup.generate('"MyDevices"/role')
|
|
self.assertEqual(result, "MyDevices/TestRole")
|
|
|
|
def test_generate_returns_none_when_all_fields_empty(self):
|
|
"""Test that generate() returns None when every format field resolves to no value."""
|
|
empty_device = MagicMock()
|
|
empty_device.name = "empty-device"
|
|
empty_device.site = None
|
|
empty_device.tenant = None
|
|
empty_device.platform = None
|
|
empty_device.role = None
|
|
empty_device.location = None
|
|
empty_device.rack = None
|
|
empty_device.custom_fields = {}
|
|
device_type = MagicMock()
|
|
manufacturer = MagicMock()
|
|
manufacturer.name = "SomeManufacturer"
|
|
device_type.manufacturer = manufacturer
|
|
empty_device.device_type = device_type
|
|
|
|
hostgroup = Hostgroup("dev", empty_device, "4.0", self.mock_logger)
|
|
# site, tenant and platform all have no value → hg_output stays empty → None
|
|
result = hostgroup.generate("site/tenant/platform")
|
|
self.assertIsNone(result)
|
|
|
|
def test_vm_without_cluster(self):
|
|
"""Test that cluster/cluster_type are absent from format_options when VM has no cluster."""
|
|
clusterless_vm = MagicMock()
|
|
clusterless_vm.name = "clusterless-vm"
|
|
clusterless_vm.site = self.mock_vm.site
|
|
clusterless_vm.tenant = self.mock_vm.tenant
|
|
clusterless_vm.platform = self.mock_vm.platform
|
|
clusterless_vm.role = self.mock_device_role
|
|
clusterless_vm.cluster = None
|
|
clusterless_vm.custom_fields = {}
|
|
|
|
hostgroup = Hostgroup("vm", clusterless_vm, "4.0", self.mock_logger)
|
|
|
|
# cluster and cluster_type must not appear in format_options
|
|
self.assertNotIn("cluster", hostgroup.format_options)
|
|
self.assertNotIn("cluster_type", hostgroup.format_options)
|
|
|
|
# Requesting cluster in a format must raise HostgroupError
|
|
with self.assertRaises(HostgroupError):
|
|
hostgroup.generate("cluster/role")
|
|
|
|
def test_empty_custom_field_skipped_in_format(self):
|
|
"""Test that an empty (None) custom field is silently omitted from the hostgroup name."""
|
|
hostgroup = Hostgroup("dev", self.mock_device, "4.0", self.mock_logger)
|
|
|
|
# empty_cf has no value → it is skipped; only site and role appear
|
|
result = hostgroup.generate("site/empty_cf/role")
|
|
self.assertEqual(result, "TestSite/TestRole")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|