Add command-line interface, test suite, and unit tests for hardware manager and recipe models
This commit is contained in:
227
python_rewrite/tests/conftest.py
Normal file
227
python_rewrite/tests/conftest.py
Normal file
@@ -0,0 +1,227 @@
|
||||
"""
|
||||
pytest configuration and shared fixtures.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from typing import AsyncGenerator, Generator
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
|
||||
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from src.tempering_machine.shared.database import Base, get_db
|
||||
from src.tempering_machine.shared.config import settings
|
||||
from src.tempering_machine.services.web.main import app
|
||||
from src.tempering_machine.services.hardware.hardware_manager import hardware_manager
|
||||
from src.tempering_machine.services.safety.safety_monitor import safety_monitor
|
||||
from src.tempering_machine.services.recipe.recipe_controller import recipe_controller
|
||||
|
||||
|
||||
# Test database URL
|
||||
TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def event_loop():
|
||||
"""Create event loop for the test session."""
|
||||
loop = asyncio.get_event_loop_policy().new_event_loop()
|
||||
yield loop
|
||||
loop.close()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_db_engine():
|
||||
"""Create test database engine."""
|
||||
engine = create_async_engine(
|
||||
TEST_DATABASE_URL,
|
||||
echo=False,
|
||||
future=True
|
||||
)
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.create_all)
|
||||
|
||||
yield engine
|
||||
|
||||
async with engine.begin() as conn:
|
||||
await conn.run_sync(Base.metadata.drop_all)
|
||||
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
@pytest_asyncio.fixture
|
||||
async def test_db_session(test_db_engine) -> AsyncGenerator[AsyncSession, None]:
|
||||
"""Create test database session."""
|
||||
async_session = sessionmaker(
|
||||
test_db_engine, class_=AsyncSession, expire_on_commit=False
|
||||
)
|
||||
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def test_client(test_db_session):
|
||||
"""Create test client with database override."""
|
||||
def override_get_db():
|
||||
return test_db_session
|
||||
|
||||
app.dependency_overrides[get_db] = override_get_db
|
||||
|
||||
with TestClient(app) as client:
|
||||
yield client
|
||||
|
||||
app.dependency_overrides.clear()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_hardware_manager():
|
||||
"""Mock hardware manager for testing."""
|
||||
mock = MagicMock()
|
||||
mock.initialize = AsyncMock(return_value=True)
|
||||
mock.shutdown = AsyncMock(return_value=True)
|
||||
mock.get_hardware_status = MagicMock()
|
||||
mock.get_all_temperatures = AsyncMock(return_value={})
|
||||
mock.get_temperature = AsyncMock(return_value=None)
|
||||
mock.is_safe_to_operate = AsyncMock(return_value=(True, []))
|
||||
mock.emergency_stop = AsyncMock(return_value=True)
|
||||
mock.set_motor_state = AsyncMock(return_value=True)
|
||||
mock.set_heater_state = AsyncMock(return_value=True)
|
||||
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_safety_monitor():
|
||||
"""Mock safety monitor for testing."""
|
||||
mock = MagicMock()
|
||||
mock.initialize = AsyncMock(return_value=True)
|
||||
mock.shutdown = AsyncMock(return_value=True)
|
||||
mock.get_active_alarms = MagicMock(return_value=[])
|
||||
mock.get_alarm_summary = MagicMock(return_value={
|
||||
"total_active_alarms": 0,
|
||||
"active_by_priority": {"critical": 0, "high": 0, "medium": 0, "low": 0},
|
||||
"emergency_stop_active": False,
|
||||
"last_safety_check": "2025-01-08T12:00:00",
|
||||
})
|
||||
mock.acknowledge_alarm = AsyncMock(return_value=True)
|
||||
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_recipe_controller():
|
||||
"""Mock recipe controller for testing."""
|
||||
mock = MagicMock()
|
||||
mock.start_recipe = AsyncMock(return_value="test-session-id")
|
||||
mock.stop_recipe = AsyncMock(return_value=True)
|
||||
mock.pause_recipe = AsyncMock(return_value=True)
|
||||
mock.resume_recipe = AsyncMock(return_value=True)
|
||||
mock.emergency_stop = AsyncMock(return_value=True)
|
||||
mock.get_process_status = AsyncMock(return_value=None)
|
||||
mock.get_active_sessions = AsyncMock(return_value=[])
|
||||
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_recipe_data():
|
||||
"""Sample recipe data for testing."""
|
||||
return {
|
||||
"name": "Test Recipe",
|
||||
"description": "A test recipe for unit testing",
|
||||
"heating_goal": 46.0,
|
||||
"cooling_goal": 27.0,
|
||||
"pouring_goal": 30.0,
|
||||
"tank_temp": 45.0,
|
||||
"fountain_temp": 32.0,
|
||||
"mixer_enabled": True,
|
||||
"fountain_enabled": True,
|
||||
"mold_heater_enabled": False,
|
||||
"vibration_enabled": False,
|
||||
"vib_heater_enabled": False,
|
||||
"pedal_control_enabled": True,
|
||||
"pedal_on_time": 2.0,
|
||||
"pedal_off_time": 3.0,
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_temperature_reading():
|
||||
"""Sample temperature reading for testing."""
|
||||
from datetime import datetime
|
||||
from src.tempering_machine.services.hardware.hardware_manager import TemperatureReading
|
||||
|
||||
return TemperatureReading(
|
||||
value=25.5,
|
||||
timestamp=datetime.now(),
|
||||
sensor_name="tank_bottom",
|
||||
units="°C",
|
||||
is_valid=True,
|
||||
error_message=None
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def sample_hardware_status():
|
||||
"""Sample hardware status for testing."""
|
||||
from datetime import datetime
|
||||
from src.tempering_machine.services.hardware.hardware_manager import (
|
||||
HardwareStatus, TemperatureReading, MotorStatus, MotorState, SafetyStatus, ComponentStatus
|
||||
)
|
||||
|
||||
return HardwareStatus(
|
||||
temperatures={
|
||||
"tank_bottom": TemperatureReading(
|
||||
value=25.5,
|
||||
timestamp=datetime.now(),
|
||||
sensor_name="tank_bottom",
|
||||
units="°C",
|
||||
is_valid=True
|
||||
),
|
||||
"fountain": TemperatureReading(
|
||||
value=30.2,
|
||||
timestamp=datetime.now(),
|
||||
sensor_name="fountain",
|
||||
units="°C",
|
||||
is_valid=True
|
||||
)
|
||||
},
|
||||
motors={
|
||||
"mixer_motor": MotorStatus(
|
||||
name="mixer_motor",
|
||||
state=MotorState.STOPPED,
|
||||
is_enabled=False
|
||||
)
|
||||
},
|
||||
safety=SafetyStatus(
|
||||
emergency_stop_active=False,
|
||||
cover_sensor_closed=True,
|
||||
temperature_alarms=[],
|
||||
current_alarms=[],
|
||||
last_safety_check=datetime.now()
|
||||
),
|
||||
communication_health=95.0,
|
||||
system_status=ComponentStatus.ONLINE,
|
||||
last_update=datetime.now()
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def setup_test_environment(monkeypatch):
|
||||
"""Set up test environment with mocked services."""
|
||||
# Override settings for testing
|
||||
monkeypatch.setattr(settings, "environment", "testing")
|
||||
monkeypatch.setattr(settings, "debug", True)
|
||||
monkeypatch.setattr(settings.database, "url", TEST_DATABASE_URL)
|
||||
|
||||
# Prevent actual hardware initialization during tests
|
||||
monkeypatch.setattr(hardware_manager, "initialize", AsyncMock(return_value=True))
|
||||
monkeypatch.setattr(safety_monitor, "initialize", AsyncMock(return_value=True))
|
||||
|
||||
|
||||
# Pytest markers
|
||||
pytest_plugins = ["pytest_asyncio"]
|
||||
Reference in New Issue
Block a user