"""
Pytest fixtures for the Unimus MCP Server test suite.
Provides shared fixtures for mocking, configuration, and test utilities.
"""
import pytest
from unittest.mock import Mock, patch, MagicMock
from typing import Dict, Any, List, Optional
import tempfile
import os
from pathlib import Path
from config import UnimusConfig, ConfigurationManager
from unimus_client import UnimusRestClient
from tests.fixtures.mock_data import MockDataFactory
@pytest.fixture
def mock_config() -> UnimusConfig:
"""Create a mock configuration for testing."""
return UnimusConfig(
url="https://test.unimus.com",
token="test-token-123",
timeout=30,
verify_ssl=True,
log_level="INFO",
health_check_port=8080
)
@pytest.fixture
def mock_unimus_client(mock_config) -> Mock:
"""Create a mock Unimus client with realistic responses."""
client = Mock(spec=UnimusRestClient)
# Mock successful API responses
client.get_health.return_value = MockDataFactory.create_health_status()
client.get_devices.return_value = MockDataFactory.create_device_list()
client.get_device_by_id.return_value = MockDataFactory.create_device()
client.get_device_backups.return_value = MockDataFactory.create_backup_list()
client.get_backup_by_id.return_value = MockDataFactory.create_backup()
client.get_schedules.return_value = MockDataFactory.create_schedule_list()
client.get_schedule_by_id.return_value = MockDataFactory.create_schedule()
client.search_backup_content.return_value = []
# Mock configuration
client.config = mock_config
return client
@pytest.fixture
def mock_server_globals(mock_config, mock_unimus_client):
"""Mock the global variables in server.py."""
with patch('server.config', mock_config), \
patch('server.unimus_client', mock_unimus_client):
yield {
'config': mock_config,
'unimus_client': mock_unimus_client
}
@pytest.fixture
def temp_config_file():
"""Create a temporary configuration file for testing."""
config_content = """
url: "https://temp.unimus.com"
token: "temp-token"
timeout: 45
log_level: "DEBUG"
"""
with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f:
f.write(config_content)
temp_path = f.name
yield temp_path
# Cleanup
os.unlink(temp_path)
@pytest.fixture
def clean_environment():
"""Clean environment variables for testing."""
env_vars = [
'UNIMUS_URL', 'UNIMUS_TOKEN', 'UNIMUS_TIMEOUT',
'UNIMUS_VERIFY_SSL', 'UNIMUS_LOG_LEVEL', 'UNIMUS_HEALTH_PORT'
]
# Store original values
original_values = {}
for var in env_vars:
original_values[var] = os.environ.get(var)
if var in os.environ:
del os.environ[var]
yield
# Restore original values
for var, value in original_values.items():
if value is not None:
os.environ[var] = value
else:
os.environ.pop(var, None)
@pytest.fixture
def mock_device_data():
"""Provide mock device data for testing."""
return {
'single_device': MockDataFactory.create_device(),
'device_list': MockDataFactory.create_device_list(),
'device_with_connections': MockDataFactory.create_device_with_connections(),
'device_with_schedule': MockDataFactory.create_device_with_schedule(),
'enriched_metadata': MockDataFactory.create_enriched_metadata()
}
@pytest.fixture
def mock_backup_data():
"""Provide mock backup data for testing."""
return {
'single_backup': MockDataFactory.create_backup(),
'backup_list': MockDataFactory.create_backup_list(),
'cisco_config': MockDataFactory.create_backup(
content="hostname CISCO-ROUTER\nversion 15.1\ninterface GigabitEthernet0/0\n ip address 10.0.0.1 255.255.255.0\n!"
),
'mikrotik_config': MockDataFactory.create_backup(
content="/interface bridge\nadd name=bridge1\n/ip address\nadd address=192.168.1.1/24 interface=bridge1"
)
}
@pytest.fixture
def mock_schedule_data():
"""Provide mock schedule data for testing."""
return {
'single_schedule': MockDataFactory.create_schedule(),
'schedule_list': MockDataFactory.create_schedule_list(),
'daily_schedule': MockDataFactory.create_schedule(name="Daily Backup", cronExpression="0 2 * * *"),
'weekly_schedule': MockDataFactory.create_schedule(name="Weekly Check", cronExpression="0 6 * * 0")
}
@pytest.fixture
def mock_api_responses():
"""Provide mock API responses for different scenarios."""
return {
'success': {
'status_code': 200,
'json': lambda: MockDataFactory.create_device_list()
},
'not_found': {
'status_code': 404,
'json': lambda: {'error': 'Device not found'}
},
'server_error': {
'status_code': 500,
'json': lambda: {'error': 'Internal server error'}
},
'auth_error': {
'status_code': 401,
'json': lambda: {'error': 'Unauthorized'}
}
}
@pytest.fixture
def mock_network_topology():
"""Provide mock network topology data for testing."""
return MockDataFactory.create_topology_analysis()
@pytest.fixture
def mock_device_relationships():
"""Provide mock device relationships for testing."""
return MockDataFactory.create_device_relationships()
@pytest.fixture
def error_scenarios():
"""Provide different error scenarios for testing."""
return {
'connection_error': Exception("Connection failed"),
'timeout_error': Exception("Request timeout"),
'auth_error': Exception("Authentication failed"),
'not_found_error': Exception("Resource not found"),
'validation_error': ValueError("Invalid input data")
}
@pytest.fixture(autouse=True)
def setup_test_logging(caplog):
"""Set up logging for tests."""
import logging
logging.getLogger().setLevel(logging.DEBUG)
yield caplog
@pytest.fixture
def mock_fastmcp():
"""Mock FastMCP server for testing MCP tools."""
with patch('server.mcp') as mock_mcp:
mock_mcp.tool = Mock()
# Make the decorator return the function unchanged
mock_mcp.tool.return_value = lambda func: func
yield mock_mcp
@pytest.fixture
def sample_search_content():
"""Provide sample configuration content for search testing."""
return {
'cisco_ios': """
hostname CORE_SWITCH_01
!
version 15.2
!
interface GigabitEthernet1/0/1
description Uplink to Core Router
switchport mode trunk
switchport trunk allowed vlan 10,20,30
!
interface GigabitEthernet1/0/24
description Management Interface
ip address 192.168.1.10 255.255.255.0
!
ip route 0.0.0.0 0.0.0.0 192.168.1.1
!
end
""",
'mikrotik_ros': """
/interface bridge
add name=bridge1
/interface ethernet
set [ find default-name=ether1 ] comment="WAN Interface"
set [ find default-name=ether2 ] comment="LAN Interface"
/interface bridge port
add bridge=bridge1 interface=ether2
/ip address
add address=192.168.88.1/24 interface=bridge1
add address=10.0.0.1/30 interface=ether1
/ip route
add distance=1 gateway=10.0.0.2
""",
'hp_aruba': """
hostname "HP-Switch-01"
!
vlan 10
name "Management"
!
vlan 20
name "Users"
!
interface 1/0/1
description "Uplink"
untagged vlan 10
!
interface 1/0/24
description "Management Port"
ip address 192.168.10.5/24
!
ip route 0.0.0.0/0 192.168.10.1
!
"""
}
@pytest.fixture
def mock_http_responses():
"""Mock HTTP responses for different API scenarios."""
responses = {}
# Successful responses
responses['devices'] = Mock()
responses['devices'].status_code = 200
responses['devices'].json.return_value = MockDataFactory.create_device_list()
responses['device'] = Mock()
responses['device'].status_code = 200
responses['device'].json.return_value = MockDataFactory.create_device()
responses['backups'] = Mock()
responses['backups'].status_code = 200
responses['backups'].json.return_value = MockDataFactory.create_backup_list()
# Error responses
responses['not_found'] = Mock()
responses['not_found'].status_code = 404
responses['not_found'].json.return_value = {'error': 'Not found'}
responses['server_error'] = Mock()
responses['server_error'].status_code = 500
responses['server_error'].json.return_value = {'error': 'Internal server error'}
return responses
@pytest.fixture
def performance_test_data():
"""Provide data for performance testing."""
large_backup_content = []
for i in range(50):
content = f"!\ninterface GigabitEthernet1/0/{i}\n ip address 10.0.{i}.1 255.255.255.0\n!\n" * 20
large_backup_content.append(content)
return {
'large_device_list': MockDataFactory.create_device_list(100),
'large_backup_content': large_backup_content,
'complex_search_patterns': [
r'interface\s+\w+',
r'ip\s+address\s+\d+\.\d+\.\d+\.\d+',
r'hostname\s+\w+',
r'vlan\s+\d+',
r'description\s+.+'
]
}
# Utility fixtures for common test patterns
@pytest.fixture
def assert_called_with_timeout():
"""Helper to assert function calls with timeout."""
def _assert_timeout(mock_func, expected_timeout=30):
# Check if timeout was passed correctly
call_args = mock_func.call_args
if call_args and len(call_args) > 1:
kwargs = call_args[1]
assert kwargs.get('timeout') == expected_timeout
return _assert_timeout
@pytest.fixture
def validate_api_response():
"""Helper to validate API response structure."""
def _validate_response(response, expected_keys):
assert isinstance(response, (dict, list))
if isinstance(response, dict):
for key in expected_keys:
assert key in response
elif isinstance(response, list) and response:
# Check first item if list is not empty
for key in expected_keys:
assert key in response[0]
return _validate_response