"""Tests for the PhysicalDevice class in the device module.""" import unittest from unittest.mock import MagicMock, patch from modules.device import PhysicalDevice from modules.exceptions import SyncInventoryError, TemplateError class TestPhysicalDevice(unittest.TestCase): """Test class for PhysicalDevice functionality.""" def setUp(self): """Set up test fixtures.""" # Create mock NetBox device self.mock_nb_device = MagicMock() self.mock_nb_device.id = 123 self.mock_nb_device.name = "test-device" self.mock_nb_device.status.label = "Active" self.mock_nb_device.custom_fields = {"zabbix_hostid": None} self.mock_nb_device.config_context = {} # Set up a primary IP primary_ip = MagicMock() primary_ip.address = "192.168.1.1/24" self.mock_nb_device.primary_ip = primary_ip # Create mock Zabbix API self.mock_zabbix = MagicMock() self.mock_zabbix.version = "6.0" # Mock NetBox journal class self.mock_nb_journal = MagicMock() # Create logger mock self.mock_logger = MagicMock() # Create PhysicalDevice instance with mocks with patch( "modules.device.config", { "device_cf": "zabbix_hostid", "template_cf": "zabbix_template", "templates_config_context": False, "templates_config_context_overrule": False, "traverse_regions": False, "traverse_site_groups": False, "inventory_mode": "disabled", "inventory_sync": False, "device_inventory_map": {}, }, ): self.device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", journal=True, logger=self.mock_logger, ) def test_init(self): """Test the initialization of the PhysicalDevice class.""" # Check that basic properties are set correctly self.assertEqual(self.device.name, "test-device") self.assertEqual(self.device.id, 123) self.assertEqual(self.device.status, "Active") self.assertEqual(self.device.ip, "192.168.1.1") self.assertEqual(self.device.cidr, "192.168.1.1/24") def test_init_no_primary_ip(self): """Test initialization when device has no primary IP.""" # Set primary_ip to None self.mock_nb_device.primary_ip = None # Creating device should raise SyncInventoryError with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): with self.assertRaises(SyncInventoryError): PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) def test_set_basics_with_special_characters(self): """Test _setBasics when device name contains special characters.""" # Set name with special characters that # will actually trigger the special character detection self.mock_nb_device.name = "test-devïce" # We need to patch the search function to simulate finding special characters with patch("modules.device.search") as mock_search, patch( "modules.device.config", {"device_cf": "zabbix_hostid"} ): # Make the search function return True to simulate special characters mock_search.return_value = True device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # With the mocked search function, the name should be changed to NETBOX_ID format self.assertEqual(device.name, f"NETBOX_ID{self.mock_nb_device.id}") # And visible_name should be set to the original name self.assertEqual(device.visible_name, "test-devïce") # use_visible_name flag should be set self.assertTrue(device.use_visible_name) def test_get_templates_context(self): """Test get_templates_context with valid config.""" # Set up config_context with valid template data self.mock_nb_device.config_context = { "zabbix": {"templates": ["Template1", "Template2"]} } # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Test that templates are returned correctly templates = device.get_templates_context() self.assertEqual(templates, ["Template1", "Template2"]) def test_get_templates_context_with_string(self): """Test get_templates_context with a string instead of list.""" # Set up config_context with a string template self.mock_nb_device.config_context = {"zabbix": {"templates": "Template1"}} # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Test that template is wrapped in a list templates = device.get_templates_context() self.assertEqual(templates, ["Template1"]) def test_get_templates_context_no_zabbix_key(self): """Test get_templates_context when zabbix key is missing.""" # Set up config_context without zabbix key self.mock_nb_device.config_context = {} # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Test that TemplateError is raised with self.assertRaises(TemplateError): device.get_templates_context() def test_get_templates_context_no_templates_key(self): """Test get_templates_context when templates key is missing.""" # Set up config_context without templates key self.mock_nb_device.config_context = {"zabbix": {}} # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Test that TemplateError is raised with self.assertRaises(TemplateError): device.get_templates_context() def test_set_template_with_config_context(self): """Test set_template with templates_config_context=True.""" # Set up config_context with templates self.mock_nb_device.config_context = {"zabbix": {"templates": ["Template1"]}} # Mock get_templates_context to return expected templates with patch.object( PhysicalDevice, "get_templates_context", return_value=["Template1"] ): with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call set_template with prefer_config_context=True result = device.set_template( prefer_config_context=True, overrule_custom=False ) # Check result and template names self.assertTrue(result) self.assertEqual(device.zbx_template_names, ["Template1"]) def test_set_inventory_disabled_mode(self): """Test set_inventory with inventory_mode=disabled.""" # Configure with disabled inventory mode config_patch = { "device_cf": "zabbix_hostid", "inventory_mode": "disabled", "inventory_sync": False, } with patch("modules.device.config", config_patch): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call set_inventory with the config patch still active with patch("modules.device.config", config_patch): result = device.set_inventory({}) # Check result self.assertTrue(result) # Default value for disabled inventory self.assertEqual(device.inventory_mode, -1) def test_set_inventory_manual_mode(self): """Test set_inventory with inventory_mode=manual.""" # Configure with manual inventory mode config_patch = { "device_cf": "zabbix_hostid", "inventory_mode": "manual", "inventory_sync": False, } with patch("modules.device.config", config_patch): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call set_inventory with the config patch still active with patch("modules.device.config", config_patch): result = device.set_inventory({}) # Check result self.assertTrue(result) self.assertEqual(device.inventory_mode, 0) # Manual mode def test_set_inventory_automatic_mode(self): """Test set_inventory with inventory_mode=automatic.""" # Configure with automatic inventory mode config_patch = { "device_cf": "zabbix_hostid", "inventory_mode": "automatic", "inventory_sync": False, } with patch("modules.device.config", config_patch): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call set_inventory with the config patch still active with patch("modules.device.config", config_patch): result = device.set_inventory({}) # Check result self.assertTrue(result) self.assertEqual(device.inventory_mode, 1) # Automatic mode def test_set_inventory_with_inventory_sync(self): """Test set_inventory with inventory_sync=True.""" # Configure with inventory sync enabled config_patch = { "device_cf": "zabbix_hostid", "inventory_mode": "manual", "inventory_sync": True, "device_inventory_map": {"name": "name", "serial": "serialno_a"}, } with patch("modules.device.config", config_patch): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Create a mock device with the required attributes mock_device_data = {"name": "test-device", "serial": "ABC123"} # Call set_inventory with the config patch still active with patch("modules.device.config", config_patch): result = device.set_inventory(mock_device_data) # Check result self.assertTrue(result) self.assertEqual(device.inventory_mode, 0) # Manual mode self.assertEqual( device.inventory, {"name": "test-device", "serialno_a": "ABC123"} ) def test_iscluster_true(self): """Test isCluster when device is part of a cluster.""" # Set up virtual_chassis self.mock_nb_device.virtual_chassis = MagicMock() # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Check isCluster result self.assertTrue(device.isCluster()) def test_is_cluster_false(self): """Test isCluster when device is not part of a cluster.""" # Set virtual_chassis to None self.mock_nb_device.virtual_chassis = None # Create device with the updated mock with patch("modules.device.config", {"device_cf": "zabbix_hostid"}): device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Check isCluster result self.assertFalse(device.isCluster()) def test_promote_master_device_primary(self): """Test promoteMasterDevice when device is primary in cluster.""" # Set up virtual chassis with master device mock_vc = MagicMock() mock_vc.name = "virtual-chassis-1" mock_master = MagicMock() mock_master.id = ( self.mock_nb_device.id ) # Set master ID to match the current device mock_vc.master = mock_master self.mock_nb_device.virtual_chassis = mock_vc # Create device with the updated mock device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call promoteMasterDevice and check the result result = device.promoteMasterDevice() # Should return True for primary device self.assertTrue(result) # Device name should be updated to virtual chassis name self.assertEqual(device.name, "virtual-chassis-1") def test_promote_master_device_secondary(self): """Test promoteMasterDevice when device is secondary in cluster.""" # Set up virtual chassis with a different master device mock_vc = MagicMock() mock_vc.name = "virtual-chassis-1" mock_master = MagicMock() mock_master.id = ( self.mock_nb_device.id + 1 ) # Different ID than the current device mock_vc.master = mock_master self.mock_nb_device.virtual_chassis = mock_vc # Create device with the updated mock device = PhysicalDevice( self.mock_nb_device, self.mock_zabbix, self.mock_nb_journal, "3.0", logger=self.mock_logger, ) # Call promoteMasterDevice and check the result result = device.promoteMasterDevice() # Should return False for secondary device self.assertFalse(result) # Device name should not be modified self.assertEqual(device.name, "test-device")