test_config_flow.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. from unittest.mock import AsyncMock, patch
  2. import pytest
  3. import caldav.lib.error as caldav_error
  4. from homeassistant.config_entries import SOURCE_USER
  5. from homeassistant.core import HomeAssistant
  6. from custom_components.room_cal_ha.const import (
  7. CONF_API_BASE_URL,
  8. CONF_API_TOKEN,
  9. CONF_CALDAV_URL,
  10. CONF_CALENDAR_NAME,
  11. CONF_PASSWORD,
  12. CONF_REFRESH_INTERVAL,
  13. CONF_RESERVATION_DEFAULTS,
  14. CONF_ROOM_NAME,
  15. CONF_SYNC_MODE,
  16. CONF_USERNAME,
  17. DEFAULT_REFRESH_INTERVAL,
  18. DOMAIN,
  19. SYNC_MODE_API,
  20. SYNC_MODE_CALDAV,
  21. SYNC_MODE_MANUAL,
  22. )
  23. _EMPTY_DEFAULTS = {
  24. "guest_name": "",
  25. "check_in_time": "",
  26. "check_out_time": "",
  27. "wifi_ssid": "",
  28. "wifi_key": "",
  29. "door_code": "",
  30. }
  31. # ---------------------------------------------------------------------------
  32. # Config flow — user step
  33. # ---------------------------------------------------------------------------
  34. async def test_user_step_shows_form(hass: HomeAssistant):
  35. result = await hass.config_entries.flow.async_init(
  36. DOMAIN, context={"source": SOURCE_USER}
  37. )
  38. assert result["type"] == "form"
  39. assert result["step_id"] == "user"
  40. async def test_user_step_creates_entry_with_room_name(hass: HomeAssistant):
  41. result = await hass.config_entries.flow.async_init(
  42. DOMAIN, context={"source": SOURCE_USER}
  43. )
  44. result = await hass.config_entries.flow.async_configure(
  45. result["flow_id"],
  46. user_input={CONF_ROOM_NAME: "Beach House"},
  47. )
  48. assert result["type"] == "create_entry"
  49. assert result["title"] == "Beach House"
  50. assert result["data"][CONF_ROOM_NAME] == "Beach House"
  51. # ---------------------------------------------------------------------------
  52. # Options flow — sync_mode step
  53. # ---------------------------------------------------------------------------
  54. async def test_options_sync_mode_manual(hass: HomeAssistant, mock_entry):
  55. mock_entry.add_to_hass(hass)
  56. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  57. assert result["type"] == "form"
  58. assert result["step_id"] == "sync_mode"
  59. result = await hass.config_entries.options.async_configure(
  60. result["flow_id"],
  61. user_input={CONF_SYNC_MODE: SYNC_MODE_MANUAL},
  62. )
  63. assert result["type"] == "form"
  64. assert result["step_id"] == "defaults"
  65. result = await hass.config_entries.options.async_configure(
  66. result["flow_id"],
  67. user_input=_EMPTY_DEFAULTS,
  68. )
  69. assert result["type"] == "create_entry"
  70. assert result["data"][CONF_SYNC_MODE] == SYNC_MODE_MANUAL
  71. async def test_options_sync_mode_caldav_proceeds_to_caldav_step(hass: HomeAssistant, mock_entry):
  72. mock_entry.add_to_hass(hass)
  73. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  74. result = await hass.config_entries.options.async_configure(
  75. result["flow_id"],
  76. user_input={CONF_SYNC_MODE: SYNC_MODE_CALDAV},
  77. )
  78. assert result["type"] == "form"
  79. assert result["step_id"] == "caldav"
  80. async def test_options_sync_mode_api_proceeds_to_api_step(hass: HomeAssistant, mock_entry):
  81. mock_entry.add_to_hass(hass)
  82. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  83. result = await hass.config_entries.options.async_configure(
  84. result["flow_id"],
  85. user_input={CONF_SYNC_MODE: SYNC_MODE_API},
  86. )
  87. assert result["type"] == "form"
  88. assert result["step_id"] == "api"
  89. # ---------------------------------------------------------------------------
  90. # Options flow — CalDAV step
  91. # ---------------------------------------------------------------------------
  92. async def test_options_caldav_saves_on_success(hass: HomeAssistant, mock_entry):
  93. mock_entry.add_to_hass(hass)
  94. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  95. await hass.config_entries.options.async_configure(
  96. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_CALDAV}
  97. )
  98. with patch("custom_components.room_cal_ha.config_flow._test_caldav_auth", return_value=None):
  99. result = await hass.config_entries.options.async_configure(
  100. result["flow_id"],
  101. user_input={
  102. CONF_CALDAV_URL: "https://cal.example.com",
  103. CONF_CALENDAR_NAME: "Room",
  104. CONF_USERNAME: "user",
  105. CONF_PASSWORD: "pass",
  106. CONF_REFRESH_INTERVAL: 30,
  107. },
  108. )
  109. assert result["type"] == "form"
  110. assert result["step_id"] == "defaults"
  111. result = await hass.config_entries.options.async_configure(
  112. result["flow_id"],
  113. user_input=_EMPTY_DEFAULTS,
  114. )
  115. assert result["type"] == "create_entry"
  116. assert result["data"][CONF_SYNC_MODE] == SYNC_MODE_CALDAV
  117. assert result["data"][CONF_CALDAV_URL] == "https://cal.example.com"
  118. assert result["data"][CONF_REFRESH_INTERVAL] == 30
  119. async def test_options_caldav_bad_credentials_shows_error(hass: HomeAssistant, mock_entry):
  120. mock_entry.add_to_hass(hass)
  121. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  122. await hass.config_entries.options.async_configure(
  123. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_CALDAV}
  124. )
  125. with patch(
  126. "custom_components.room_cal_ha.config_flow._test_caldav_auth",
  127. side_effect=caldav_error.AuthorizationError,
  128. ):
  129. result = await hass.config_entries.options.async_configure(
  130. result["flow_id"],
  131. user_input={
  132. CONF_CALDAV_URL: "https://cal.example.com",
  133. CONF_CALENDAR_NAME: "Room",
  134. CONF_USERNAME: "user",
  135. CONF_PASSWORD: "bad",
  136. CONF_REFRESH_INTERVAL: 15,
  137. },
  138. )
  139. assert result["type"] == "form"
  140. assert result["errors"]["base"] == "invalid_auth"
  141. async def test_options_caldav_connect_error_shows_error(hass: HomeAssistant, mock_entry):
  142. mock_entry.add_to_hass(hass)
  143. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  144. await hass.config_entries.options.async_configure(
  145. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_CALDAV}
  146. )
  147. with patch(
  148. "custom_components.room_cal_ha.config_flow._test_caldav_auth",
  149. side_effect=Exception("timeout"),
  150. ):
  151. result = await hass.config_entries.options.async_configure(
  152. result["flow_id"],
  153. user_input={
  154. CONF_CALDAV_URL: "https://cal.example.com",
  155. CONF_CALENDAR_NAME: "Room",
  156. CONF_USERNAME: "user",
  157. CONF_PASSWORD: "pass",
  158. CONF_REFRESH_INTERVAL: 15,
  159. },
  160. )
  161. assert result["type"] == "form"
  162. assert result["errors"]["base"] == "cannot_connect"
  163. # ---------------------------------------------------------------------------
  164. # Options flow — API step
  165. # ---------------------------------------------------------------------------
  166. async def test_options_api_saves_on_success(hass: HomeAssistant, mock_entry):
  167. mock_entry.add_to_hass(hass)
  168. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  169. await hass.config_entries.options.async_configure(
  170. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_API}
  171. )
  172. with patch(
  173. "custom_components.room_cal_ha.config_flow._test_api_connection",
  174. new=AsyncMock(return_value=None),
  175. ):
  176. result = await hass.config_entries.options.async_configure(
  177. result["flow_id"],
  178. user_input={
  179. CONF_API_BASE_URL: "https://api.example.com",
  180. CONF_API_TOKEN: "tok-secret",
  181. CONF_REFRESH_INTERVAL: 10,
  182. },
  183. )
  184. assert result["type"] == "form"
  185. assert result["step_id"] == "defaults"
  186. result = await hass.config_entries.options.async_configure(
  187. result["flow_id"],
  188. user_input=_EMPTY_DEFAULTS,
  189. )
  190. assert result["type"] == "create_entry"
  191. assert result["data"][CONF_SYNC_MODE] == SYNC_MODE_API
  192. assert result["data"][CONF_API_BASE_URL] == "https://api.example.com"
  193. assert result["data"][CONF_REFRESH_INTERVAL] == 10
  194. async def test_options_api_bad_token_shows_error(hass: HomeAssistant, mock_entry):
  195. from custom_components.room_cal_ha.config_flow import _ApiAuthError
  196. mock_entry.add_to_hass(hass)
  197. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  198. await hass.config_entries.options.async_configure(
  199. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_API}
  200. )
  201. with patch(
  202. "custom_components.room_cal_ha.config_flow._test_api_connection",
  203. new=AsyncMock(side_effect=_ApiAuthError),
  204. ):
  205. result = await hass.config_entries.options.async_configure(
  206. result["flow_id"],
  207. user_input={
  208. CONF_API_BASE_URL: "https://api.example.com",
  209. CONF_API_TOKEN: "bad-token",
  210. CONF_REFRESH_INTERVAL: 15,
  211. },
  212. )
  213. assert result["type"] == "form"
  214. assert result["errors"]["base"] == "invalid_auth"
  215. async def test_options_api_connect_error_shows_error(hass: HomeAssistant, mock_entry):
  216. mock_entry.add_to_hass(hass)
  217. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  218. await hass.config_entries.options.async_configure(
  219. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_API}
  220. )
  221. with patch(
  222. "custom_components.room_cal_ha.config_flow._test_api_connection",
  223. new=AsyncMock(side_effect=Exception("timeout")),
  224. ):
  225. result = await hass.config_entries.options.async_configure(
  226. result["flow_id"],
  227. user_input={
  228. CONF_API_BASE_URL: "https://api.example.com",
  229. CONF_API_TOKEN: "tok",
  230. CONF_REFRESH_INTERVAL: 15,
  231. },
  232. )
  233. assert result["type"] == "form"
  234. assert result["errors"]["base"] == "cannot_connect"
  235. # ---------------------------------------------------------------------------
  236. # Options flow — defaults step
  237. # ---------------------------------------------------------------------------
  238. async def _reach_defaults_step(hass: HomeAssistant, mock_entry):
  239. """Navigate to the defaults step via manual sync mode."""
  240. mock_entry.add_to_hass(hass)
  241. result = await hass.config_entries.options.async_init(mock_entry.entry_id)
  242. result = await hass.config_entries.options.async_configure(
  243. result["flow_id"], user_input={CONF_SYNC_MODE: SYNC_MODE_MANUAL}
  244. )
  245. assert result["step_id"] == "defaults"
  246. return result
  247. async def test_options_defaults_saves_values(hass: HomeAssistant, mock_entry):
  248. result = await _reach_defaults_step(hass, mock_entry)
  249. result = await hass.config_entries.options.async_configure(
  250. result["flow_id"],
  251. user_input={
  252. "guest_name": "Default Guest",
  253. "check_in_time": "15:00",
  254. "check_out_time": "11:00",
  255. "wifi_ssid": "HomeNet",
  256. "wifi_key": "secret",
  257. "door_code": "1234",
  258. },
  259. )
  260. assert result["type"] == "create_entry"
  261. defaults = result["data"][CONF_RESERVATION_DEFAULTS]
  262. assert defaults["guest_name"] == "Default Guest"
  263. assert defaults["check_in_time"] == "15:00"
  264. assert defaults["check_out_time"] == "11:00"
  265. assert defaults["wifi_ssid"] == "HomeNet"
  266. assert defaults["wifi_key"] == "secret"
  267. assert defaults["door_code"] == "1234"
  268. async def test_options_defaults_empty_strings_become_none(hass: HomeAssistant, mock_entry):
  269. result = await _reach_defaults_step(hass, mock_entry)
  270. result = await hass.config_entries.options.async_configure(
  271. result["flow_id"],
  272. user_input=_EMPTY_DEFAULTS,
  273. )
  274. assert result["type"] == "create_entry"
  275. defaults = result["data"][CONF_RESERVATION_DEFAULTS]
  276. assert defaults["guest_name"] is None
  277. assert defaults["check_in_time"] is None
  278. assert defaults["wifi_ssid"] is None
  279. async def test_options_defaults_invalid_time_shows_error(hass: HomeAssistant, mock_entry):
  280. result = await _reach_defaults_step(hass, mock_entry)
  281. result = await hass.config_entries.options.async_configure(
  282. result["flow_id"],
  283. user_input={**_EMPTY_DEFAULTS, "check_in_time": "not-a-time"},
  284. )
  285. assert result["type"] == "form"
  286. assert "check_in_time" in result["errors"]