mcmqtt/tests/conftest.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

226 lines
5.8 KiB
Python

"""Pytest configuration and fixtures for mcmqtt tests."""
import asyncio
import os
import tempfile
from typing import AsyncGenerator, Dict, Any
from unittest.mock import AsyncMock, MagicMock
import pytest
import pytest_asyncio
# Test configuration
@pytest.fixture(scope="session")
def event_loop():
"""Create an instance of the default event loop for the test session."""
loop = asyncio.get_event_loop_policy().new_event_loop()
yield loop
loop.close()
# MQTT fixtures removed to avoid heavy imports during test discovery
# Individual test files can import and create their own configs as needed
@pytest.fixture
def mock_paho_client():
"""Create a mock paho MQTT client."""
mock_client = MagicMock()
mock_client.connect.return_value = 0 # MQTT_ERR_SUCCESS
mock_client.disconnect.return_value = 0
mock_client.publish.return_value = MagicMock(rc=0)
mock_client.subscribe.return_value = (0, 1)
mock_client.unsubscribe.return_value = (0, None)
mock_client.loop_start.return_value = None
mock_client.loop_stop.return_value = None
return mock_client
# MQTT client fixtures removed to avoid heavy imports during test discovery
# Test data fixtures
@pytest.fixture
def sample_mqtt_message() -> Dict[str, Any]:
"""Sample MQTT message data."""
return {
"topic": "test/topic",
"payload": "test message",
"qos": 1,
"retain": False
}
@pytest.fixture
def sample_json_message() -> Dict[str, Any]:
"""Sample JSON MQTT message data."""
return {
"topic": "test/json",
"payload": {
"temperature": 25.5,
"humidity": 60,
"timestamp": "2025-09-16T01:48:00Z"
},
"qos": 1,
"retain": False
}
@pytest.fixture
def batch_messages() -> list:
"""Sample batch of MQTT messages."""
return [
{
"topic": "sensor/temp/1",
"payload": {"value": 20.1, "unit": "C"},
"qos": 1
},
{
"topic": "sensor/temp/2",
"payload": {"value": 22.3, "unit": "C"},
"qos": 1
},
{
"topic": "sensor/humidity/1",
"payload": {"value": 45.0, "unit": "%"},
"qos": 0
}
]
# Mock external dependencies
@pytest.fixture
def mock_mosquitto_broker():
"""Mock mosquitto broker for integration tests."""
mock_broker = MagicMock()
mock_broker.start.return_value = True
mock_broker.stop.return_value = True
mock_broker.is_running = True
return mock_broker
@pytest.fixture
def temporary_directory():
"""Create a temporary directory for test files."""
with tempfile.TemporaryDirectory() as temp_dir:
yield temp_dir
# Environment setup
@pytest.fixture(autouse=True)
def setup_test_environment():
"""Set up test environment variables."""
original_env = dict(os.environ)
# Set test environment variables
test_env = {
"MQTT_BROKER_HOST": "localhost",
"MQTT_BROKER_PORT": "1883",
"MQTT_CLIENT_ID": "test-client",
"LOG_LEVEL": "DEBUG"
}
os.environ.update(test_env)
yield
# Restore original environment
os.environ.clear()
os.environ.update(original_env)
# JSON Schema fixtures for validation tests
@pytest.fixture
def sensor_data_schema() -> Dict[str, Any]:
"""JSON schema for sensor data validation."""
return {
"type": "object",
"required": ["value", "unit", "timestamp"],
"properties": {
"value": {"type": "number"},
"unit": {"type": "string"},
"timestamp": {"type": "string"},
"sensor_id": {"type": "string"}
}
}
@pytest.fixture
def valid_sensor_data() -> Dict[str, Any]:
"""Valid sensor data matching the schema."""
return {
"value": 25.5,
"unit": "C",
"timestamp": "2025-09-16T01:48:00Z",
"sensor_id": "temp_01"
}
@pytest.fixture
def invalid_sensor_data() -> Dict[str, Any]:
"""Invalid sensor data not matching the schema."""
return {
"value": "invalid", # Should be number
"unit": 123, # Should be string
# Missing required timestamp
}
# Performance test fixtures
@pytest.fixture
def performance_test_config():
"""Configuration for performance tests."""
return {
"message_count": 1000,
"concurrent_connections": 10,
"message_size_bytes": 1024,
"test_duration_seconds": 30
}
# Error simulation fixtures
@pytest.fixture
def connection_error_scenarios():
"""Different connection error scenarios for testing."""
return [
{"error_type": "timeout", "description": "Connection timeout"},
{"error_type": "refused", "description": "Connection refused"},
{"error_type": "auth_failed", "description": "Authentication failed"},
{"error_type": "network_error", "description": "Network unreachable"}
]
# Cleanup utilities
@pytest.fixture
def cleanup_subscriptions():
"""Utility to cleanup test subscriptions."""
subscriptions_to_clean = []
def add_subscription(topic: str):
subscriptions_to_clean.append(topic)
yield add_subscription
# Cleanup logic would go here in a real implementation
# For now, just track what needs cleaning
if subscriptions_to_clean:
print(f"Cleaning up {len(subscriptions_to_clean)} test subscriptions")
# Integration test utilities
@pytest.fixture
def integration_test_broker():
"""Integration test broker setup."""
# In a real scenario, this would start a test MQTT broker
# For now, return configuration for testing
return {
"host": "localhost",
"port": 1883,
"test_topics": [
"test/integration/basic",
"test/integration/json",
"test/integration/wildcard/+",
"test/integration/multilevel/#"
]
}