| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- """
- Root conftest.py - Shared fixtures for all tests.
- This file provides:
- - CI environment detection for auto-skipping hardware tests
- - AsyncClient fixture for API testing
- - Mock state fixture for isolated testing
- """
- import os
- import pytest
- from unittest.mock import MagicMock, AsyncMock
- def pytest_configure(config):
- """Configure pytest with custom markers and CI detection."""
- # Register custom markers
- config.addinivalue_line(
- "markers", "hardware: marks tests requiring real hardware (skip in CI)"
- )
- config.addinivalue_line(
- "markers", "slow: marks slow tests"
- )
- def pytest_collection_modifyitems(config, items):
- """Auto-skip hardware tests when CI=true environment variable is set."""
- if os.environ.get("CI"):
- skip_hardware = pytest.mark.skip(reason="Hardware not available in CI")
- for item in items:
- if "hardware" in item.keywords:
- item.add_marker(skip_hardware)
- @pytest.fixture
- async def async_client():
- """Async HTTP client for testing API endpoints.
- Uses httpx AsyncClient with ASGITransport to test FastAPI app directly
- without starting a server.
- """
- from httpx import ASGITransport, AsyncClient
- from main import app
- async with AsyncClient(
- transport=ASGITransport(app=app),
- base_url="http://test"
- ) as client:
- yield client
- @pytest.fixture
- def mock_state():
- """Mock global state object for isolated testing.
- Returns a MagicMock configured with common defaults to simulate
- the application state without affecting real state.
- """
- mock = MagicMock()
- # Connection mock
- mock.conn = MagicMock()
- mock.conn.is_connected.return_value = False
- mock.port = None
- mock.is_connected = False
- # Pattern execution state
- mock.current_playing_file = None
- mock.is_running = False
- mock.pause_requested = False
- mock.stop_requested = False
- mock.skip_requested = False
- mock.execution_progress = None
- mock.is_homing = False
- mock.is_clearing = False
- # Position state
- mock.current_theta = 0.0
- mock.current_rho = 0.0
- mock.machine_x = 0.0
- mock.machine_y = 0.0
- # Speed and settings
- mock.speed = 100
- mock.clear_pattern_speed = None
- mock.table_type = "dune_weaver"
- mock.table_type_override = None
- mock.homing = 0
- # Playlist state
- mock.current_playlist = None
- mock.current_playlist_name = None
- mock.current_playlist_index = None
- mock.playlist_mode = None
- mock.pause_time_remaining = 0
- mock.original_pause_time = None
- # LED state
- mock.led_controller = None
- mock.led_provider = "none"
- mock.wled_ip = None
- mock.dw_led_idle_effect = "solid"
- mock.dw_led_playing_effect = "rainbow"
- mock.dw_led_idle_timeout_enabled = False
- mock.dw_led_idle_timeout_minutes = 30
- # Scheduled pause
- mock.scheduled_pause_enabled = False
- mock.scheduled_pause_time_slots = []
- mock.scheduled_pause_control_wled = False
- mock.scheduled_pause_finish_pattern = False
- mock.scheduled_pause_timezone = None
- # Steps and gear ratio
- mock.x_steps_per_mm = 200.0
- mock.y_steps_per_mm = 287.0
- mock.gear_ratio = 10.0
- # Auto-home settings
- mock.auto_home_enabled = False
- mock.auto_home_after_patterns = 10
- mock.patterns_since_last_home = 0
- # Custom clear patterns
- mock.custom_clear_from_out = None
- mock.custom_clear_from_in = None
- # Homing offset
- mock.angular_homing_offset_degrees = 0.0
- # Methods
- mock.save = MagicMock()
- mock.get_stop_event = MagicMock(return_value=None)
- mock.get_skip_event = MagicMock(return_value=None)
- mock.wait_for_interrupt = AsyncMock(return_value='timeout')
- return mock
- @pytest.fixture
- def mock_connection():
- """Mock connection object for testing hardware communication.
- Returns a MagicMock configured to simulate serial/websocket connection.
- """
- mock = MagicMock()
- mock.is_connected.return_value = True
- mock.send = MagicMock()
- mock.readline = MagicMock(return_value="ok")
- mock.in_waiting = MagicMock(return_value=0)
- mock.flush = MagicMock()
- mock.close = MagicMock()
- mock.reset_input_buffer = MagicMock()
- return mock
- @pytest.fixture
- def patterns_dir(tmp_path):
- """Create a temporary patterns directory for testing.
- Returns the path to a temporary directory that can be used
- for pattern file operations during tests.
- """
- patterns = tmp_path / "patterns"
- patterns.mkdir()
- return patterns
|