Skip to main content
Glama

NetBox Read/Write MCP Server

test_bulk_cable_creation.py24.9 kB
""" Comprehensive tests for bulk cable creation functionality. This module tests the netbox_bulk_create_cable_connections function that enables enterprise-grade bulk cable operations with rollback support. """ import pytest from unittest.mock import Mock, patch, MagicMock from typing import Dict, Any, List from netbox_mcp.tools.dcim.cables import netbox_bulk_create_cable_connections from netbox_mcp.client import NetBoxClient from netbox_mcp.exceptions import NetBoxValidationError, NetBoxNotFoundError class TestBulkCableCreation: """Test bulk cable creation functionality.""" def setup_method(self): """Setup test client and mock data.""" self.mock_client = Mock(spec=NetBoxClient) # Mock successful cable creation response self.mock_cable_response = { 'id': 123, 'label': 'Test Cable', 'type': {'value': 'cat6'}, 'color': 'pink', 'status': {'value': 'connected'}, 'a_terminations': [{'object_id': 101, 'object_type': 'dcim.interface'}], 'b_terminations': [{'object_id': 201, 'object_type': 'dcim.interface'}] } # Sample cable connections for testing self.sample_connections = [ { "device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1" }, { "device_a_name": "server-02", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/2" }, { "device_a_name": "server-03", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/3" } ] @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_success(self, mock_create_cable): """Test successful bulk cable creation.""" # Mock individual cable creation success mock_create_cable.return_value = { "success": True, "action": "created", "cable": self.mock_cable_response } # Execute bulk creation result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", cable_color="pink", batch_size=2, confirm=True ) # Verify success assert result["success"] is True assert result["action"] == "bulk_created" assert result["dry_run"] is False # Verify batch processing batch_results = result["batch_results"] assert len(batch_results) == 2 # 3 cables in batches of 2 = 2 batches # First batch (2 cables) assert batch_results[0]["batch_number"] == 1 assert batch_results[0]["cables_in_batch"] == 2 assert batch_results[0]["batch_success"] is True # Second batch (1 cable) assert batch_results[1]["batch_number"] == 2 assert batch_results[1]["cables_in_batch"] == 1 assert batch_results[1]["batch_success"] is True # Verify summary summary = result["summary"] assert summary["total_requested"] == 3 assert summary["total_successful"] == 3 assert summary["total_failed"] == 0 assert summary["success_rate"] == "100.0%" # Verify individual cable creation was called 3 times assert mock_create_cable.call_count == 3 # Verify cable creation parameters calls = mock_create_cable.call_args_list assert calls[0][1]["cable_type"] == "cat6" assert calls[0][1]["cable_color"] == "pink" assert calls[0][1]["confirm"] is True @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_dry_run(self, mock_create_cable): """Test bulk cable creation in dry-run mode.""" # Mock dry-run response mock_create_cable.return_value = { "success": True, "action": "dry_run", "cable_preview": { "device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1" } } # Execute dry run result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", cable_color="pink", batch_size=10, confirm=False ) # Verify dry run assert result["success"] is True assert result["action"] == "dry_run" assert result["dry_run"] is True # Verify preview preview = result["bulk_preview"] assert preview["total_cables"] == 3 assert preview["cable_type"] == "cat6" assert preview["cable_color"] == "pink" assert preview["batch_size"] == 10 assert preview["estimated_batches"] == 1 # Verify individual previews cable_previews = preview["cable_previews"] assert len(cable_previews) == 3 # Verify all calls were made with confirm=False calls = mock_create_cable.call_args_list for call in calls: assert call[1]["confirm"] is False @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_partial_failure(self, mock_create_cable): """Test bulk cable creation with partial failures.""" # Mock mixed success/failure responses def mock_cable_side_effect(*args, **kwargs): device_a = kwargs.get('device_a_name', '') if device_a == 'server-02': return { "success": False, "error": "Interface not found", "error_type": "NotFoundError" } return { "success": True, "action": "created", "cable": self.mock_cable_response } mock_create_cable.side_effect = mock_cable_side_effect # Execute bulk creation result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", cable_color="pink", batch_size=2, rollback_on_error=False, # Don't rollback on individual failures confirm=True ) # Verify partial success assert result["success"] is True # Overall success despite individual failures assert result["action"] == "bulk_created" # Verify summary summary = result["summary"] assert summary["total_requested"] == 3 assert summary["total_successful"] == 2 assert summary["total_failed"] == 1 assert summary["success_rate"] == "66.7%" # Verify failed cables are tracked failed_cables = result["failed_cables"] assert len(failed_cables) == 1 assert failed_cables[0]["device_a_name"] == "server-02" assert failed_cables[0]["error"] == "Interface not found" assert failed_cables[0]["error_type"] == "NotFoundError" @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_rollback_on_error(self, mock_create_cable): """Test bulk cable creation with rollback on error.""" # Mock responses: first succeeds, second fails responses = [ { "success": True, "action": "created", "cable": {'id': 100, 'label': 'Cable 1'} }, { "success": False, "error": "Critical error", "error_type": "ValidationError" } ] mock_create_cable.side_effect = responses # Mock rollback function with patch('netbox_mcp.tools.dcim.cables.netbox_disconnect_cable') as mock_disconnect: mock_disconnect.return_value = {"success": True} # Execute bulk creation with rollback enabled result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections[:2], # Only 2 cables cable_type="cat6", rollback_on_error=True, confirm=True ) # Verify rollback was triggered assert result["success"] is False # Overall failure due to rollback assert result["action"] == "rolled_back" assert "rollback_performed" in result assert result["rollback_performed"] is True # Verify rollback details rollback_info = result["rollback_info"] assert rollback_info["cables_rolled_back"] == 1 assert rollback_info["rollback_successful"] is True # Verify disconnect was called for the successful cable mock_disconnect.assert_called_once_with( client=self.mock_client, cable_id=100, confirm=True ) def test_bulk_cable_creation_validation_errors(self): """Test validation errors in bulk cable creation.""" # Test empty cable connections result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[], cable_type="cat6", confirm=True ) assert result["success"] is False assert result["error_type"] == "ValidationError" assert "No cable connections provided" in result["error"] # Test invalid cable type result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="invalid_type", confirm=True ) assert result["success"] is False assert result["error_type"] == "ValidationError" assert "Invalid cable_type" in result["error"] # Test invalid cable color result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", cable_color="invalid_color", confirm=True ) assert result["success"] is False assert result["error_type"] == "ValidationError" assert "Invalid cable_color" in result["error"] # Test invalid batch size result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", batch_size=0, confirm=True ) assert result["success"] is False assert result["error_type"] == "ValidationError" assert "batch_size must be between 1 and 50" in result["error"] def test_bulk_cable_creation_connection_validation(self): """Test individual cable connection validation.""" # Test missing required fields invalid_connections = [ { "device_a_name": "server-01", # Missing interface_a_name "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1" } ] result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=invalid_connections, cable_type="cat6", confirm=True ) assert result["success"] is False assert result["error_type"] == "ValidationError" assert "Missing required field" in result["error"] @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_progress_tracking(self, mock_create_cable): """Test progress tracking during bulk creation.""" # Mock successful responses mock_create_cable.return_value = { "success": True, "action": "created", "cable": self.mock_cable_response } # Execute with small batch size to test multiple batches result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", batch_size=1, # One cable per batch confirm=True ) # Verify batch tracking batch_results = result["batch_results"] assert len(batch_results) == 3 # 3 cables, 1 per batch # Verify each batch has proper tracking for i, batch in enumerate(batch_results): assert batch["batch_number"] == i + 1 assert batch["cables_in_batch"] == 1 assert batch["batch_success"] is True assert "execution_time_ms" in batch assert batch["execution_time_ms"] >= 0 @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_performance_metrics(self, mock_create_cable): """Test performance metrics collection.""" # Mock responses with some delay simulation mock_create_cable.return_value = { "success": True, "action": "created", "cable": self.mock_cable_response } # Execute bulk creation result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", batch_size=5, confirm=True ) # Verify performance metrics performance = result["performance_metrics"] assert "total_execution_time_ms" in performance assert "average_time_per_cable_ms" in performance assert "cables_per_second" in performance assert performance["total_execution_time_ms"] >= 0 assert performance["average_time_per_cable_ms"] >= 0 assert performance["cables_per_second"] > 0 def test_bulk_cable_creation_edge_cases(self): """Test edge cases and boundary conditions.""" # Test maximum batch size large_connections = [ { "device_a_name": f"server-{i:02d}", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": f"Te1/1/{i}" } for i in range(1, 101) # 100 connections ] with patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') as mock_create: mock_create.return_value = { "success": True, "action": "created", "cable": self.mock_cable_response } result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=large_connections, cable_type="cat6", batch_size=50, # Max batch size confirm=True ) assert result["success"] is True assert result["summary"]["total_requested"] == 100 assert len(result["batch_results"]) == 2 # 100 cables / 50 batch size @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_bulk_cable_creation_with_labels(self, mock_create_cable): """Test bulk cable creation with automatic labeling.""" mock_create_cable.return_value = { "success": True, "action": "created", "cable": self.mock_cable_response } # Execute with label prefix result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=self.sample_connections, cable_type="cat6", cable_color="pink", label_prefix="K3-SW1", confirm=True ) # Verify label generation assert result["success"] is True # Check that labels were passed to individual cable creation calls = mock_create_cable.call_args_list for i, call in enumerate(calls): expected_label = f"K3-SW1-{i+1:03d}" assert call[1]["label"] == expected_label class TestBulkCableRollback: """Test rollback functionality for bulk cable creation.""" def setup_method(self): """Setup test client.""" self.mock_client = Mock(spec=NetBoxClient) @patch('netbox_mcp.tools.dcim.cables.netbox_disconnect_cable') @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_rollback_success(self, mock_create_cable, mock_disconnect): """Test successful rollback of created cables.""" # Setup: first cable succeeds, second fails created_cables = [ {"success": True, "action": "created", "cable": {"id": 100}}, {"success": False, "error": "Test error", "error_type": "TestError"} ] mock_create_cable.side_effect = created_cables mock_disconnect.return_value = {"success": True} # Execute with rollback enabled result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"}, {"device_a_name": "server-02", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/2"} ], cable_type="cat6", rollback_on_error=True, confirm=True ) # Verify rollback was performed assert result["success"] is False assert result["action"] == "rolled_back" assert result["rollback_performed"] is True # Verify disconnect was called for the successful cable mock_disconnect.assert_called_once_with( client=self.mock_client, cable_id=100, confirm=True ) @patch('netbox_mcp.tools.dcim.cables.netbox_disconnect_cable') @patch('netbox_mcp.tools.dcim.cables.netbox_create_cable_connection') def test_rollback_failure(self, mock_create_cable, mock_disconnect): """Test handling of rollback failures.""" # Setup: cable creation succeeds, rollback fails mock_create_cable.side_effect = [ {"success": True, "action": "created", "cable": {"id": 100}}, {"success": False, "error": "Test error", "error_type": "TestError"} ] mock_disconnect.return_value = { "success": False, "error": "Rollback failed", "error_type": "RollbackError" } # Execute with rollback enabled result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"}, {"device_a_name": "server-02", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/2"} ], cable_type="cat6", rollback_on_error=True, confirm=True ) # Verify rollback failure is reported assert result["success"] is False assert result["rollback_performed"] is True assert result["rollback_info"]["rollback_successful"] is False assert "rollback_errors" in result["rollback_info"] class TestBulkCableValidation: """Test validation functionality for bulk cable operations.""" def setup_method(self): """Setup test client.""" self.mock_client = Mock(spec=NetBoxClient) def test_cable_type_validation(self): """Test cable type validation.""" valid_types = [ "cat3", "cat5", "cat5e", "cat6", "cat6a", "cat7", "cat8", "dac-active", "dac-passive", "mmf", "smf", "power" ] for cable_type in valid_types: result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type=cable_type, confirm=False # Dry run ) # Should not fail validation assert result["success"] is True or result["error_type"] != "ValidationError" def test_cable_color_validation(self): """Test cable color validation.""" valid_colors = [ "pink", "red", "blue", "green", "yellow", "orange", "purple", "grey", "black", "white", "brown", "cyan", "magenta", "lime", "silver", "gold" ] for color in valid_colors: result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type="cat6", cable_color=color, confirm=False # Dry run ) # Should not fail validation assert result["success"] is True or result["error_type"] != "ValidationError" def test_batch_size_validation(self): """Test batch size validation.""" # Test minimum batch size result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type="cat6", batch_size=1, # Minimum confirm=False ) assert result["success"] is True # Test maximum batch size result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type="cat6", batch_size=50, # Maximum confirm=False ) assert result["success"] is True # Test invalid batch size (too small) result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type="cat6", batch_size=0, confirm=False ) assert result["success"] is False assert result["error_type"] == "ValidationError" # Test invalid batch size (too large) result = netbox_bulk_create_cable_connections( client=self.mock_client, cable_connections=[ {"device_a_name": "server-01", "interface_a_name": "lom1", "device_b_name": "switch1.k3", "interface_b_name": "Te1/1/1"} ], cable_type="cat6", batch_size=100, confirm=False ) assert result["success"] is False assert result["error_type"] == "ValidationError" if __name__ == "__main__": pytest.main([__file__, "-v"])

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Deployment-Team/netbox-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server