test_api_status.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. """
  2. Unit tests for status and info API endpoints.
  3. Tests the following endpoints:
  4. - GET /serial_status
  5. - GET /list_serial_ports
  6. - GET /api/settings
  7. - GET /api/table-info
  8. """
  9. import pytest
  10. from unittest.mock import patch, MagicMock, AsyncMock
  11. class TestSerialStatus:
  12. """Tests for /serial_status endpoint."""
  13. @pytest.mark.asyncio
  14. async def test_serial_status_when_connected(self, async_client, mock_state):
  15. """Test serial_status returns connected state."""
  16. mock_state.conn = MagicMock()
  17. mock_state.conn.is_connected.return_value = True
  18. mock_state.port = "/dev/ttyUSB0"
  19. mock_state.preferred_port = "__auto__"
  20. with patch("main.state", mock_state):
  21. response = await async_client.get("/serial_status")
  22. assert response.status_code == 200
  23. data = response.json()
  24. assert data["connected"] is True
  25. assert data["port"] == "/dev/ttyUSB0"
  26. assert data["preferred_port"] == "__auto__"
  27. @pytest.mark.asyncio
  28. async def test_serial_status_when_disconnected(self, async_client, mock_state):
  29. """Test serial_status returns disconnected state."""
  30. mock_state.conn = None
  31. mock_state.port = None
  32. mock_state.preferred_port = "__none__"
  33. with patch("main.state", mock_state):
  34. response = await async_client.get("/serial_status")
  35. assert response.status_code == 200
  36. data = response.json()
  37. assert data["connected"] is False
  38. assert data["port"] is None
  39. assert data["preferred_port"] == "__none__"
  40. @pytest.mark.asyncio
  41. async def test_serial_status_with_disconnected_conn(self, async_client, mock_state):
  42. """Test serial_status when conn exists but is disconnected."""
  43. mock_state.conn = MagicMock()
  44. mock_state.conn.is_connected.return_value = False
  45. mock_state.port = "/dev/ttyUSB0"
  46. mock_state.preferred_port = "/dev/ttyUSB0"
  47. with patch("main.state", mock_state):
  48. response = await async_client.get("/serial_status")
  49. assert response.status_code == 200
  50. data = response.json()
  51. assert data["connected"] is False
  52. class TestListSerialPorts:
  53. """Tests for /list_serial_ports endpoint."""
  54. @pytest.mark.asyncio
  55. async def test_list_serial_ports_returns_list(self, async_client):
  56. """Test list_serial_ports returns a list of available ports."""
  57. mock_ports = ["/dev/ttyUSB0", "/dev/ttyACM0"]
  58. with patch("main.connection_manager.list_serial_ports", return_value=mock_ports):
  59. response = await async_client.get("/list_serial_ports")
  60. assert response.status_code == 200
  61. data = response.json()
  62. assert isinstance(data, list)
  63. assert "/dev/ttyUSB0" in data
  64. assert "/dev/ttyACM0" in data
  65. @pytest.mark.asyncio
  66. async def test_list_serial_ports_empty(self, async_client):
  67. """Test list_serial_ports returns empty list when no ports."""
  68. with patch("main.connection_manager.list_serial_ports", return_value=[]):
  69. response = await async_client.get("/list_serial_ports")
  70. assert response.status_code == 200
  71. data = response.json()
  72. assert data == []
  73. class TestGetAllSettings:
  74. """Tests for /api/settings endpoint."""
  75. @pytest.mark.asyncio
  76. async def test_get_all_settings_returns_expected_structure(self, async_client, mock_state):
  77. """Test get_all_settings returns complete settings structure."""
  78. mock_state.app_name = "Test Table"
  79. mock_state.custom_logo = None
  80. mock_state.preferred_port = "__auto__"
  81. mock_state.clear_pattern_speed = 150
  82. mock_state.custom_clear_from_in = None
  83. mock_state.custom_clear_from_out = None
  84. mock_state.auto_play_enabled = False
  85. mock_state.auto_play_playlist = None
  86. mock_state.auto_play_run_mode = "single"
  87. mock_state.auto_play_pause_time = 0
  88. mock_state.auto_play_clear_pattern = None
  89. mock_state.auto_play_shuffle = False
  90. mock_state.scheduled_pause_enabled = False
  91. mock_state.scheduled_pause_control_wled = False
  92. mock_state.scheduled_pause_finish_pattern = False
  93. mock_state.scheduled_pause_timezone = None
  94. mock_state.scheduled_pause_time_slots = []
  95. mock_state.homing = 0
  96. mock_state.homing_user_override = False
  97. mock_state.angular_homing_offset_degrees = 0.0
  98. mock_state.auto_home_enabled = False
  99. mock_state.auto_home_after_patterns = 10
  100. mock_state.led_provider = "none"
  101. mock_state.wled_ip = None
  102. mock_state.dw_led_num_leds = 60
  103. mock_state.dw_led_gpio_pin = 18
  104. mock_state.dw_led_pixel_order = "GRB"
  105. mock_state.dw_led_brightness = 50
  106. mock_state.dw_led_speed = 128
  107. mock_state.dw_led_intensity = 128
  108. mock_state.dw_led_idle_effect = "solid"
  109. mock_state.dw_led_playing_effect = "rainbow"
  110. mock_state.dw_led_idle_timeout_enabled = False
  111. mock_state.dw_led_idle_timeout_minutes = 30
  112. mock_state.mqtt_enabled = False
  113. mock_state.mqtt_broker = None
  114. mock_state.mqtt_port = 1883
  115. mock_state.mqtt_username = None
  116. mock_state.mqtt_password = None
  117. mock_state.mqtt_client_id = "dune_weaver"
  118. mock_state.mqtt_discovery_prefix = "homeassistant"
  119. mock_state.mqtt_device_id = "dune_weaver_01"
  120. mock_state.mqtt_device_name = "Dune Weaver"
  121. mock_state.table_type = "dune_weaver"
  122. mock_state.table_type_override = None
  123. mock_state.gear_ratio = 10.0
  124. mock_state.x_steps_per_mm = 200.0
  125. mock_state.y_steps_per_mm = 287.0
  126. mock_state.timezone = "UTC"
  127. with patch("main.state", mock_state):
  128. response = await async_client.get("/api/settings")
  129. assert response.status_code == 200
  130. data = response.json()
  131. # Check top-level structure
  132. assert "app" in data
  133. assert "connection" in data
  134. assert "patterns" in data
  135. assert "auto_play" in data
  136. assert "scheduled_pause" in data
  137. assert "homing" in data
  138. assert "led" in data
  139. assert "mqtt" in data
  140. assert "machine" in data
  141. # Verify specific values
  142. assert data["app"]["name"] == "Test Table"
  143. assert data["connection"]["preferred_port"] == "__auto__"
  144. assert data["patterns"]["clear_pattern_speed"] == 150
  145. assert data["machine"]["detected_table_type"] == "dune_weaver"
  146. @pytest.mark.asyncio
  147. async def test_get_all_settings_effective_table_type(self, async_client, mock_state):
  148. """Test that effective_table_type uses override when set."""
  149. mock_state.app_name = "Test"
  150. mock_state.custom_logo = None
  151. mock_state.preferred_port = None
  152. mock_state.clear_pattern_speed = None
  153. mock_state.custom_clear_from_in = None
  154. mock_state.custom_clear_from_out = None
  155. mock_state.auto_play_enabled = False
  156. mock_state.auto_play_playlist = None
  157. mock_state.auto_play_run_mode = "single"
  158. mock_state.auto_play_pause_time = 0
  159. mock_state.auto_play_clear_pattern = None
  160. mock_state.auto_play_shuffle = False
  161. mock_state.scheduled_pause_enabled = False
  162. mock_state.scheduled_pause_control_wled = False
  163. mock_state.scheduled_pause_finish_pattern = False
  164. mock_state.scheduled_pause_timezone = None
  165. mock_state.scheduled_pause_time_slots = []
  166. mock_state.homing = 0
  167. mock_state.homing_user_override = False
  168. mock_state.angular_homing_offset_degrees = 0.0
  169. mock_state.auto_home_enabled = False
  170. mock_state.auto_home_after_patterns = 10
  171. mock_state.led_provider = "none"
  172. mock_state.wled_ip = None
  173. mock_state.dw_led_num_leds = 60
  174. mock_state.dw_led_gpio_pin = 18
  175. mock_state.dw_led_pixel_order = "GRB"
  176. mock_state.dw_led_brightness = 50
  177. mock_state.dw_led_speed = 128
  178. mock_state.dw_led_intensity = 128
  179. mock_state.dw_led_idle_effect = "solid"
  180. mock_state.dw_led_playing_effect = "rainbow"
  181. mock_state.dw_led_idle_timeout_enabled = False
  182. mock_state.dw_led_idle_timeout_minutes = 30
  183. mock_state.mqtt_enabled = False
  184. mock_state.mqtt_broker = None
  185. mock_state.mqtt_port = 1883
  186. mock_state.mqtt_username = None
  187. mock_state.mqtt_password = None
  188. mock_state.mqtt_client_id = "dune_weaver"
  189. mock_state.mqtt_discovery_prefix = "homeassistant"
  190. mock_state.mqtt_device_id = "dune_weaver_01"
  191. mock_state.mqtt_device_name = "Dune Weaver"
  192. mock_state.table_type = "dune_weaver"
  193. mock_state.table_type_override = "dune_weaver_mini" # Override set
  194. mock_state.gear_ratio = 6.25
  195. mock_state.x_steps_per_mm = 256.0
  196. mock_state.y_steps_per_mm = 180.0
  197. mock_state.timezone = "UTC"
  198. with patch("main.state", mock_state):
  199. response = await async_client.get("/api/settings")
  200. assert response.status_code == 200
  201. data = response.json()
  202. assert data["machine"]["detected_table_type"] == "dune_weaver"
  203. assert data["machine"]["table_type_override"] == "dune_weaver_mini"
  204. assert data["machine"]["effective_table_type"] == "dune_weaver_mini"
  205. class TestGetTableInfo:
  206. """Tests for /api/table-info endpoint."""
  207. @pytest.mark.asyncio
  208. async def test_get_table_info(self, async_client, mock_state):
  209. """Test get_table_info returns table identification info."""
  210. mock_state.table_id = "table-123"
  211. mock_state.table_name = "Living Room Table"
  212. with patch("main.state", mock_state):
  213. with patch("main.version_manager.get_current_version", return_value="1.0.0"):
  214. response = await async_client.get("/api/table-info")
  215. assert response.status_code == 200
  216. data = response.json()
  217. # API returns "id" and "name", not "table_id" and "table_name"
  218. assert data["id"] == "table-123"
  219. assert data["name"] == "Living Room Table"
  220. assert data["version"] == "1.0.0"
  221. @pytest.mark.asyncio
  222. async def test_get_table_info_not_set(self, async_client, mock_state):
  223. """Test get_table_info when not configured."""
  224. mock_state.table_id = None
  225. mock_state.table_name = None
  226. with patch("main.state", mock_state):
  227. with patch("main.version_manager.get_current_version", return_value="1.0.0"):
  228. response = await async_client.get("/api/table-info")
  229. assert response.status_code == 200
  230. data = response.json()
  231. assert data["id"] is None
  232. assert data["name"] is None