mcmqtt/tests/unit/test_config_comprehensive.py
Ryan Malloy 8ab61eb1df 🚀 Initial release: mcmqtt FastMCP MQTT Server v2025.09.17
Complete FastMCP MQTT integration server featuring:

 Core Features:
- FastMCP native Model Context Protocol server with MQTT tools
- Embedded MQTT broker support with zero-configuration spawning
- Modular architecture: CLI, config, logging, server, MQTT, MCP, broker
- Comprehensive testing: 70+ tests with 96%+ coverage
- Cross-platform support: Linux, macOS, Windows

🏗️ Architecture:
- Clean separation of concerns across 7 modules
- Async/await patterns throughout for maximum performance
- Pydantic models with validation and configuration management
- AMQTT pure Python embedded brokers
- Typer CLI framework with rich output formatting

🧪 Quality Assurance:
- pytest-cov with HTML reporting
- AsyncMock comprehensive unit testing
- Edge case coverage for production reliability
- Pre-commit hooks with black, ruff, mypy

📦 Production Ready:
- PyPI package with proper metadata
- MIT License
- Professional documentation
- uvx installation support
- MCP client integration examples

Perfect for AI agent coordination, IoT data collection, and
microservice communication with MQTT messaging patterns.
2025-09-17 05:46:08 -06:00

250 lines
8.9 KiB
Python

"""
Comprehensive unit tests for configuration modules.
Tests environment variable and command-line argument configuration handling.
"""
import pytest
import os
from unittest.mock import patch, Mock
from argparse import Namespace
from mcmqtt.config.env_config import create_mqtt_config_from_env, create_mqtt_config_from_args
from mcmqtt.mqtt.types import MQTTConfig, MQTTQoS
class TestCreateMqttConfigFromEnv:
"""Test MQTT configuration from environment variables."""
def setUp(self):
"""Clear environment variables before each test."""
env_vars = [
'MQTT_BROKER_HOST', 'MQTT_BROKER_PORT', 'MQTT_CLIENT_ID',
'MQTT_USERNAME', 'MQTT_PASSWORD', 'MQTT_KEEPALIVE',
'MQTT_QOS', 'MQTT_USE_TLS', 'MQTT_CLEAN_SESSION',
'MQTT_RECONNECT_INTERVAL', 'MQTT_MAX_RECONNECT_ATTEMPTS'
]
for var in env_vars:
os.environ.pop(var, None)
def test_create_mqtt_config_no_host(self):
"""Test config creation with no broker host."""
self.setUp()
config = create_mqtt_config_from_env()
assert config is None
def test_create_mqtt_config_minimal(self):
"""Test config creation with minimal environment variables."""
self.setUp()
os.environ['MQTT_BROKER_HOST'] = 'localhost'
config = create_mqtt_config_from_env()
assert config is not None
assert config.broker_host == 'localhost'
assert config.broker_port == 1883 # default
assert config.client_id.startswith('mcmqtt-')
assert config.username is None
assert config.password is None
assert config.keepalive == 60
assert config.qos == MQTTQoS.AT_LEAST_ONCE
assert config.use_tls is False
assert config.clean_session is True
assert config.reconnect_interval == 5
assert config.max_reconnect_attempts == 10
def test_create_mqtt_config_complete(self):
"""Test config creation with all environment variables."""
self.setUp()
os.environ.update({
'MQTT_BROKER_HOST': 'mqtt.example.com',
'MQTT_BROKER_PORT': '8883',
'MQTT_CLIENT_ID': 'test-client',
'MQTT_USERNAME': 'testuser',
'MQTT_PASSWORD': 'testpass',
'MQTT_KEEPALIVE': '120',
'MQTT_QOS': '2',
'MQTT_USE_TLS': 'true',
'MQTT_CLEAN_SESSION': 'false',
'MQTT_RECONNECT_INTERVAL': '10',
'MQTT_MAX_RECONNECT_ATTEMPTS': '5'
})
config = create_mqtt_config_from_env()
assert config is not None
assert config.broker_host == 'mqtt.example.com'
assert config.broker_port == 8883
assert config.client_id == 'test-client'
assert config.username == 'testuser'
assert config.password == 'testpass'
assert config.keepalive == 120
assert config.qos == MQTTQoS.EXACTLY_ONCE
assert config.use_tls is True
assert config.clean_session is False
assert config.reconnect_interval == 10
assert config.max_reconnect_attempts == 5
def test_create_mqtt_config_boolean_variations(self):
"""Test config creation with boolean variations."""
self.setUp()
# Test TLS true variations
for tls_value in ['true', 'True', 'TRUE', '1']:
os.environ['MQTT_BROKER_HOST'] = 'localhost'
os.environ['MQTT_USE_TLS'] = tls_value
config = create_mqtt_config_from_env()
assert config.use_tls is True
os.environ.pop('MQTT_USE_TLS', None)
# Test TLS false variations
for tls_value in ['false', 'False', 'FALSE', '0', 'no']:
os.environ['MQTT_BROKER_HOST'] = 'localhost'
os.environ['MQTT_USE_TLS'] = tls_value
config = create_mqtt_config_from_env()
assert config.use_tls is False
os.environ.pop('MQTT_USE_TLS', None)
def test_create_mqtt_config_invalid_port(self):
"""Test config creation with invalid port."""
self.setUp()
os.environ['MQTT_BROKER_HOST'] = 'localhost'
os.environ['MQTT_BROKER_PORT'] = 'invalid'
with patch('logging.error') as mock_error:
config = create_mqtt_config_from_env()
assert config is None
mock_error.assert_called_once()
def test_create_mqtt_config_invalid_qos(self):
"""Test config creation with invalid QoS."""
self.setUp()
os.environ['MQTT_BROKER_HOST'] = 'localhost'
os.environ['MQTT_QOS'] = 'invalid'
with patch('logging.error') as mock_error:
config = create_mqtt_config_from_env()
assert config is None
mock_error.assert_called_once()
def test_create_mqtt_config_invalid_keepalive(self):
"""Test config creation with invalid keepalive."""
self.setUp()
os.environ['MQTT_BROKER_HOST'] = 'localhost'
os.environ['MQTT_KEEPALIVE'] = 'invalid'
with patch('logging.error') as mock_error:
config = create_mqtt_config_from_env()
assert config is None
mock_error.assert_called_once()
def test_create_mqtt_config_default_client_id_varies(self):
"""Test that default client ID includes PID."""
self.setUp()
os.environ['MQTT_BROKER_HOST'] = 'localhost'
config = create_mqtt_config_from_env()
assert config is not None
assert config.client_id.startswith('mcmqtt-')
assert str(os.getpid()) in config.client_id
class TestCreateMqttConfigFromArgs:
"""Test MQTT configuration from command-line arguments."""
def test_create_mqtt_config_no_host(self):
"""Test config creation with no broker host."""
args = Namespace(mqtt_host=None)
config = create_mqtt_config_from_args(args)
assert config is None
def test_create_mqtt_config_minimal(self):
"""Test config creation with minimal arguments."""
args = Namespace(
mqtt_host='localhost',
mqtt_port=1883,
mqtt_client_id=None,
mqtt_username=None,
mqtt_password=None
)
config = create_mqtt_config_from_args(args)
assert config is not None
assert config.broker_host == 'localhost'
assert config.broker_port == 1883
assert config.client_id.startswith('mcmqtt-')
assert config.username is None
assert config.password is None
def test_create_mqtt_config_complete(self):
"""Test config creation with all arguments."""
args = Namespace(
mqtt_host='mqtt.example.com',
mqtt_port=8883,
mqtt_client_id='test-client',
mqtt_username='testuser',
mqtt_password='testpass'
)
config = create_mqtt_config_from_args(args)
assert config is not None
assert config.broker_host == 'mqtt.example.com'
assert config.broker_port == 8883
assert config.client_id == 'test-client'
assert config.username == 'testuser'
assert config.password == 'testpass'
def test_create_mqtt_config_default_client_id(self):
"""Test config creation with default client ID generation."""
args = Namespace(
mqtt_host='localhost',
mqtt_port=1883,
mqtt_client_id=None,
mqtt_username=None,
mqtt_password=None
)
config = create_mqtt_config_from_args(args)
assert config is not None
assert config.client_id.startswith('mcmqtt-')
assert str(os.getpid()) in config.client_id
def test_create_mqtt_config_exception_handling(self):
"""Test config creation with exception handling."""
# Mock args object that raises exception when accessed
args = Mock()
args.mqtt_host = 'localhost'
args.mqtt_port = Mock(side_effect=Exception("Port error"))
with patch('logging.error') as mock_error:
config = create_mqtt_config_from_args(args)
assert config is None
mock_error.assert_called_once()
def test_create_mqtt_config_custom_port(self):
"""Test config creation with custom port."""
args = Namespace(
mqtt_host='broker.local',
mqtt_port=9883,
mqtt_client_id='custom-client',
mqtt_username='user123',
mqtt_password='pass456'
)
config = create_mqtt_config_from_args(args)
assert config is not None
assert config.broker_host == 'broker.local'
assert config.broker_port == 9883
assert config.client_id == 'custom-client'
assert config.username == 'user123'
assert config.password == 'pass456'