1
0

conftest.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. """
  2. Unit test conftest.py - Fixtures specific to unit tests.
  3. Provides fixtures for mocking FastAPI dependencies and isolating tests
  4. from real state and hardware connections.
  5. """
  6. import pytest
  7. from unittest.mock import MagicMock, AsyncMock, patch
  8. @pytest.fixture
  9. def mock_state_unit():
  10. """Mock state for unit tests with common defaults.
  11. This is a more comprehensive mock than the root conftest version,
  12. specifically designed for API endpoint testing where we need to
  13. control the application state precisely.
  14. """
  15. mock = MagicMock()
  16. # Connection mock
  17. mock.conn = MagicMock()
  18. mock.conn.is_connected.return_value = False
  19. mock.port = None
  20. mock.is_connected = False
  21. mock.preferred_port = None
  22. # Pattern execution state
  23. mock.current_playing_file = None
  24. mock.is_running = False
  25. mock.pause_requested = False
  26. mock.stop_requested = False
  27. mock.skip_requested = False
  28. mock.execution_progress = None
  29. mock.is_homing = False
  30. mock.is_clearing = False
  31. # Position state
  32. mock.current_theta = 0.0
  33. mock.current_rho = 0.0
  34. mock.machine_x = 0.0
  35. mock.machine_y = 0.0
  36. # Speed and settings
  37. mock.speed = 100
  38. mock.clear_pattern_speed = None
  39. mock.table_type = "dune_weaver"
  40. mock.table_type_override = None
  41. mock.homing = 0
  42. # Playlist state
  43. mock.current_playlist = None
  44. mock.current_playlist_name = None
  45. mock.current_playlist_index = None
  46. mock.playlist_mode = None
  47. mock.pause_time_remaining = 0
  48. mock.original_pause_time = None
  49. # LED state
  50. mock.led_controller = None
  51. mock.led_provider = "none"
  52. mock.wled_ip = None
  53. mock.hyperion_ip = None
  54. mock.hyperion_port = 19444
  55. mock.dw_led_num_leds = 60
  56. mock.dw_led_gpio_pin = 18
  57. mock.dw_led_pixel_order = "GRB"
  58. mock.dw_led_brightness = 50
  59. mock.dw_led_speed = 128
  60. mock.dw_led_intensity = 128
  61. mock.dw_led_idle_effect = "solid"
  62. mock.dw_led_playing_effect = "rainbow"
  63. mock.dw_led_idle_timeout_enabled = False
  64. mock.dw_led_idle_timeout_minutes = 30
  65. mock.dw_led_last_activity_time = 0
  66. # Scheduled pause
  67. mock.scheduled_pause_enabled = False
  68. mock.scheduled_pause_time_slots = []
  69. mock.scheduled_pause_control_wled = False
  70. mock.scheduled_pause_finish_pattern = False
  71. mock.scheduled_pause_timezone = None
  72. # Steps and gear ratio
  73. mock.x_steps_per_mm = 200.0
  74. mock.y_steps_per_mm = 287.0
  75. mock.gear_ratio = 10.0
  76. # Auto-home settings
  77. mock.auto_home_enabled = False
  78. mock.auto_home_after_patterns = 10
  79. mock.patterns_since_last_home = 0
  80. # Custom clear patterns
  81. mock.custom_clear_from_out = None
  82. mock.custom_clear_from_in = None
  83. # Homing offset
  84. mock.angular_homing_offset_degrees = 0.0
  85. # App settings
  86. mock.app_name = "Dune Weaver"
  87. mock.custom_logo_path = None
  88. # MQTT settings
  89. mock.mqtt_enabled = False
  90. mock.mqtt_broker = None
  91. mock.mqtt_port = 1883
  92. mock.mqtt_username = None
  93. mock.mqtt_password = None
  94. mock.mqtt_topic_prefix = "dune_weaver"
  95. # Table info
  96. mock.table_id = None
  97. mock.table_name = None
  98. mock.known_tables = []
  99. # Methods
  100. mock.save = MagicMock()
  101. mock.get_stop_event = MagicMock(return_value=None)
  102. mock.get_skip_event = MagicMock(return_value=None)
  103. mock.wait_for_interrupt = AsyncMock(return_value='timeout')
  104. mock.pause_condition = MagicMock()
  105. mock.pause_condition.__enter__ = MagicMock()
  106. mock.pause_condition.__exit__ = MagicMock()
  107. mock.pause_condition.notify_all = MagicMock()
  108. return mock
  109. @pytest.fixture
  110. def mock_connection_unit():
  111. """Mock connection for unit tests.
  112. Provides a connection mock that simulates a connected device
  113. without requiring actual hardware.
  114. """
  115. mock = MagicMock()
  116. mock.is_connected.return_value = True
  117. mock.send = MagicMock()
  118. mock.readline = MagicMock(return_value="ok")
  119. mock.in_waiting = MagicMock(return_value=0)
  120. mock.flush = MagicMock()
  121. mock.close = MagicMock()
  122. mock.reset_input_buffer = MagicMock()
  123. return mock
  124. @pytest.fixture
  125. def app_with_mocked_state(mock_state_unit):
  126. """Fixture that patches state module before importing app.
  127. This ensures the app uses mocked state for all operations.
  128. Must be used before creating async_client.
  129. """
  130. with patch("modules.core.state.state", mock_state_unit):
  131. with patch("modules.core.pattern_manager.state", mock_state_unit):
  132. with patch("modules.core.playlist_manager.state", mock_state_unit):
  133. with patch("modules.connection.connection_manager.state", mock_state_unit):
  134. from main import app
  135. yield app, mock_state_unit
  136. @pytest.fixture
  137. async def async_client_with_mocked_state(app_with_mocked_state):
  138. """AsyncClient with mocked state for isolated API testing.
  139. This fixture combines the app patching with the async client creation.
  140. """
  141. from httpx import ASGITransport, AsyncClient
  142. app, mock_state = app_with_mocked_state
  143. async with AsyncClient(
  144. transport=ASGITransport(app=app),
  145. base_url="http://test"
  146. ) as client:
  147. yield client, mock_state
  148. @pytest.fixture
  149. def cleanup_app_overrides():
  150. """Fixture to ensure app.dependency_overrides is cleaned up after tests."""
  151. from main import app
  152. yield
  153. # Cleanup after test
  154. app.dependency_overrides.clear()