| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189 |
- """
- Unit test conftest.py - Fixtures specific to unit tests.
- Provides fixtures for mocking FastAPI dependencies and isolating tests
- from real state and hardware connections.
- """
- import pytest
- from unittest.mock import MagicMock, AsyncMock, patch
- @pytest.fixture
- def mock_state_unit():
- """Mock state for unit tests with common defaults.
- This is a more comprehensive mock than the root conftest version,
- specifically designed for API endpoint testing where we need to
- control the application state precisely.
- """
- mock = MagicMock()
- # Connection mock
- mock.conn = MagicMock()
- mock.conn.is_connected.return_value = False
- mock.port = None
- mock.is_connected = False
- mock.preferred_port = None
- # 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.hyperion_ip = None
- mock.hyperion_port = 19444
- mock.dw_led_num_leds = 60
- mock.dw_led_gpio_pin = 18
- mock.dw_led_pixel_order = "GRB"
- mock.dw_led_brightness = 50
- mock.dw_led_speed = 128
- mock.dw_led_intensity = 128
- 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
- mock.dw_led_last_activity_time = 0
- # 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
- # App settings
- mock.app_name = "Dune Weaver"
- mock.custom_logo_path = None
- # MQTT settings
- mock.mqtt_enabled = False
- mock.mqtt_broker = None
- mock.mqtt_port = 1883
- mock.mqtt_username = None
- mock.mqtt_password = None
- mock.mqtt_topic_prefix = "dune_weaver"
- # Table info
- mock.table_id = None
- mock.table_name = None
- mock.known_tables = []
- # 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')
- mock.pause_condition = MagicMock()
- mock.pause_condition.__enter__ = MagicMock()
- mock.pause_condition.__exit__ = MagicMock()
- mock.pause_condition.notify_all = MagicMock()
- return mock
- @pytest.fixture
- def mock_connection_unit():
- """Mock connection for unit tests.
- Provides a connection mock that simulates a connected device
- without requiring actual hardware.
- """
- 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 app_with_mocked_state(mock_state_unit):
- """Fixture that patches state module before importing app.
- This ensures the app uses mocked state for all operations.
- Must be used before creating async_client.
- """
- with patch("modules.core.state.state", mock_state_unit):
- with patch("modules.core.pattern_manager.state", mock_state_unit):
- with patch("modules.core.playlist_manager.state", mock_state_unit):
- with patch("modules.connection.connection_manager.state", mock_state_unit):
- from main import app
- yield app, mock_state_unit
- @pytest.fixture
- async def async_client_with_mocked_state(app_with_mocked_state):
- """AsyncClient with mocked state for isolated API testing.
- This fixture combines the app patching with the async client creation.
- """
- from httpx import ASGITransport, AsyncClient
- app, mock_state = app_with_mocked_state
- async with AsyncClient(
- transport=ASGITransport(app=app),
- base_url="http://test"
- ) as client:
- yield client, mock_state
- @pytest.fixture
- def cleanup_app_overrides():
- """Fixture to ensure app.dependency_overrides is cleaned up after tests."""
- from main import app
- yield
- # Cleanup after test
- app.dependency_overrides.clear()
|