test_config_flow.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. """Tests for the config flow."""
  2. from unittest.mock import ANY, AsyncMock, MagicMock, patch
  3. from homeassistant.const import CONF_HOST, CONF_NAME
  4. import pytest
  5. from pytest_homeassistant_custom_component.common import MockConfigEntry
  6. import voluptuous as vol
  7. from custom_components.tuya_local import config_flow
  8. from custom_components.tuya_local.const import (
  9. CONF_CLIMATE,
  10. CONF_DEVICE_ID,
  11. CONF_LOCAL_KEY,
  12. CONF_LOCK,
  13. CONF_SWITCH,
  14. CONF_TYPE,
  15. DOMAIN,
  16. )
  17. @pytest.fixture(autouse=True)
  18. def auto_enable_custom_integrations(enable_custom_integrations):
  19. yield
  20. async def test_init_entry(hass):
  21. """Test initialisation of the config flow."""
  22. entry = MockConfigEntry(
  23. domain=DOMAIN,
  24. version=2,
  25. title="test",
  26. data={
  27. CONF_DEVICE_ID: "deviceid",
  28. CONF_HOST: "hostname",
  29. CONF_LOCAL_KEY: "localkey",
  30. CONF_TYPE: "kogan_heater",
  31. CONF_CLIMATE: True,
  32. CONF_LOCK: True,
  33. },
  34. )
  35. entry.add_to_hass(hass)
  36. await hass.config_entries.async_setup(entry.entry_id)
  37. await hass.async_block_till_done()
  38. state = hass.states.get("climate.test")
  39. assert state
  40. async def test_flow_user_init(hass):
  41. """Test the initialisation of the form in the first step of the config flow."""
  42. result = await hass.config_entries.flow.async_init(
  43. DOMAIN, context={"source": "user"}
  44. )
  45. expected = {
  46. "data_schema": vol.Schema(config_flow.individual_config_schema()),
  47. "description_placeholders": None,
  48. "errors": {},
  49. "flow_id": ANY,
  50. "handler": DOMAIN,
  51. "step_id": "user",
  52. "type": "form",
  53. "last_step": ANY,
  54. }
  55. assert expected == result
  56. @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
  57. async def test_async_test_connection_valid(mock_device, hass):
  58. """Test that device is returned when connection is valid."""
  59. mock_instance = AsyncMock()
  60. mock_instance.has_returned_state = True
  61. mock_device.return_value = mock_instance
  62. device = await config_flow.async_test_connection(
  63. {
  64. CONF_DEVICE_ID: "deviceid",
  65. CONF_LOCAL_KEY: "localkey",
  66. CONF_HOST: "hostname",
  67. },
  68. hass,
  69. )
  70. assert device == mock_instance
  71. @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
  72. async def test_async_test_connection_invalid(mock_device, hass):
  73. """Test that None is returned when connection is invalid."""
  74. mock_instance = AsyncMock()
  75. mock_instance.has_returned_state = False
  76. mock_device.return_value = mock_instance
  77. device = await config_flow.async_test_connection(
  78. {
  79. CONF_DEVICE_ID: "deviceid",
  80. CONF_LOCAL_KEY: "localkey",
  81. CONF_HOST: "hostname",
  82. },
  83. hass,
  84. )
  85. assert device is None
  86. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  87. async def test_flow_user_init_invalid_config(mock_test, hass):
  88. """Test errors populated when config is invalid."""
  89. mock_test.return_value = None
  90. flow = await hass.config_entries.flow.async_init(DOMAIN, context={"source": "user"})
  91. result = await hass.config_entries.flow.async_configure(
  92. flow["flow_id"],
  93. user_input={
  94. CONF_DEVICE_ID: "deviceid",
  95. CONF_HOST: "hostname",
  96. CONF_LOCAL_KEY: "badkey",
  97. },
  98. )
  99. assert {"base": "connection"} == result["errors"]
  100. def setup_device_mock(mock, failure=False, type="test"):
  101. mock_type = MagicMock()
  102. mock_type.legacy_type = type
  103. mock_iter = MagicMock()
  104. mock_iter.__aiter__.return_value = [mock_type] if not failure else []
  105. mock.async_possible_types = MagicMock(return_value=mock_iter)
  106. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  107. async def test_flow_user_init_data_valid(mock_test, hass):
  108. """Test we advance to the next step when connection config is valid."""
  109. mock_device = MagicMock()
  110. setup_device_mock(mock_device)
  111. mock_test.return_value = mock_device
  112. flow = await hass.config_entries.flow.async_init(DOMAIN, context={"source": "user"})
  113. result = await hass.config_entries.flow.async_configure(
  114. flow["flow_id"],
  115. user_input={
  116. CONF_DEVICE_ID: "deviceid",
  117. CONF_HOST: "hostname",
  118. CONF_LOCAL_KEY: "localkey",
  119. },
  120. )
  121. assert "form" == result["type"]
  122. assert "select_type" == result["step_id"]
  123. @patch.object(config_flow.ConfigFlowHandler, "device")
  124. async def test_flow_select_type_init(mock_device, hass):
  125. """Test the initialisation of the form in the 2nd step of the config flow."""
  126. setup_device_mock(mock_device)
  127. result = await hass.config_entries.flow.async_init(
  128. DOMAIN, context={"source": "select_type"}
  129. )
  130. expected = {
  131. "data_schema": ANY,
  132. "description_placeholders": None,
  133. "errors": None,
  134. "flow_id": ANY,
  135. "handler": DOMAIN,
  136. "step_id": "select_type",
  137. "type": "form",
  138. "last_step": ANY,
  139. }
  140. assert expected == result
  141. # Check the schema. Simple comparison does not work since they are not
  142. # the same object
  143. try:
  144. result["data_schema"]({CONF_TYPE: "test"})
  145. except vol.MultipleInvalid:
  146. assert False
  147. try:
  148. result["data_schema"]({CONF_TYPE: "not_test"})
  149. assert False
  150. except vol.MultipleInvalid:
  151. pass
  152. @patch.object(config_flow.ConfigFlowHandler, "device")
  153. async def test_flow_select_type_aborts_when_no_match(mock_device, hass):
  154. """Test the flow aborts when an unsupported device is used."""
  155. setup_device_mock(mock_device, failure=True)
  156. result = await hass.config_entries.flow.async_init(
  157. DOMAIN, context={"source": "select_type"}
  158. )
  159. assert result["type"] == "abort"
  160. assert result["reason"] == "not_supported"
  161. @patch.object(config_flow.ConfigFlowHandler, "device")
  162. async def test_flow_select_type_data_valid(mock_device, hass):
  163. """Test the flow continues when valid data is supplied."""
  164. setup_device_mock(mock_device, type="kogan_switch")
  165. flow = await hass.config_entries.flow.async_init(
  166. DOMAIN, context={"source": "select_type"}
  167. )
  168. result = await hass.config_entries.flow.async_configure(
  169. flow["flow_id"],
  170. user_input={CONF_TYPE: "kogan_switch"},
  171. )
  172. assert "form" == result["type"]
  173. assert "choose_entities" == result["step_id"]
  174. async def test_flow_choose_entities_init(hass):
  175. """Test the initialisation of the form in the 3rd step of the config flow."""
  176. with patch.dict(config_flow.ConfigFlowHandler.data, {CONF_TYPE: "kogan_switch"}):
  177. result = await hass.config_entries.flow.async_init(
  178. DOMAIN, context={"source": "choose_entities"}
  179. )
  180. expected = {
  181. "data_schema": ANY,
  182. "description_placeholders": None,
  183. "errors": None,
  184. "flow_id": ANY,
  185. "handler": DOMAIN,
  186. "step_id": "choose_entities",
  187. "type": "form",
  188. "last_step": ANY,
  189. }
  190. assert expected == result
  191. # Check the schema. Simple comparison does not work since they are not
  192. # the same object
  193. try:
  194. result["data_schema"]({CONF_NAME: "test", CONF_SWITCH: True})
  195. except vol.MultipleInvalid:
  196. assert False
  197. try:
  198. result["data_schema"]({CONF_CLIMATE: True})
  199. assert False
  200. except vol.MultipleInvalid:
  201. pass
  202. async def test_flow_choose_entities_creates_config_entry(hass):
  203. """Test the flow ends when data is valid."""
  204. with patch.dict(
  205. config_flow.ConfigFlowHandler.data,
  206. {
  207. CONF_DEVICE_ID: "deviceid",
  208. CONF_LOCAL_KEY: "localkey",
  209. CONF_HOST: "hostname",
  210. CONF_TYPE: "kogan_switch",
  211. },
  212. ):
  213. flow = await hass.config_entries.flow.async_init(
  214. DOMAIN, context={"source": "choose_entities"}
  215. )
  216. result = await hass.config_entries.flow.async_configure(
  217. flow["flow_id"],
  218. user_input={CONF_NAME: "test", CONF_SWITCH: True},
  219. )
  220. expected = {
  221. "version": 2,
  222. "type": "create_entry",
  223. "flow_id": ANY,
  224. "handler": DOMAIN,
  225. "title": "test",
  226. "description": None,
  227. "description_placeholders": None,
  228. "result": ANY,
  229. "options": {},
  230. "data": {
  231. CONF_DEVICE_ID: "deviceid",
  232. CONF_HOST: "hostname",
  233. CONF_LOCAL_KEY: "localkey",
  234. CONF_SWITCH: True,
  235. CONF_TYPE: "kogan_switch",
  236. },
  237. }
  238. assert expected == result
  239. async def test_options_flow_init(hass):
  240. """Test config flow options."""
  241. config_entry = MockConfigEntry(
  242. domain=DOMAIN,
  243. unique_id="uniqueid",
  244. data={
  245. CONF_DEVICE_ID: "deviceid",
  246. CONF_HOST: "hostname",
  247. CONF_LOCAL_KEY: "localkey",
  248. CONF_NAME: "test",
  249. CONF_SWITCH: True,
  250. CONF_TYPE: "kogan_switch",
  251. },
  252. )
  253. config_entry.add_to_hass(hass)
  254. assert await hass.config_entries.async_setup(config_entry.entry_id)
  255. await hass.async_block_till_done()
  256. # show initial form
  257. result = await hass.config_entries.options.async_init(config_entry.entry_id)
  258. assert "form" == result["type"]
  259. assert "user" == result["step_id"]
  260. assert {} == result["errors"]
  261. assert result["data_schema"](
  262. {
  263. CONF_HOST: "hostname",
  264. CONF_LOCAL_KEY: "localkey",
  265. CONF_SWITCH: True,
  266. }
  267. )
  268. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  269. async def test_options_flow_modifies_config(mock_test, hass):
  270. mock_device = MagicMock()
  271. mock_test.return_value = mock_device
  272. config_entry = MockConfigEntry(
  273. domain=DOMAIN,
  274. unique_id="uniqueid",
  275. data={
  276. CONF_DEVICE_ID: "deviceid",
  277. CONF_HOST: "hostname",
  278. CONF_LOCAL_KEY: "localkey",
  279. CONF_NAME: "test",
  280. CONF_SWITCH: True,
  281. CONF_TYPE: "kogan_switch",
  282. },
  283. )
  284. config_entry.add_to_hass(hass)
  285. assert await hass.config_entries.async_setup(config_entry.entry_id)
  286. await hass.async_block_till_done()
  287. # show initial form
  288. form = await hass.config_entries.options.async_init(config_entry.entry_id)
  289. # submit updated config
  290. result = await hass.config_entries.options.async_configure(
  291. form["flow_id"],
  292. user_input={
  293. CONF_HOST: "new_hostname",
  294. CONF_LOCAL_KEY: "new_key",
  295. CONF_SWITCH: False,
  296. },
  297. )
  298. expected = {
  299. CONF_HOST: "new_hostname",
  300. CONF_LOCAL_KEY: "new_key",
  301. CONF_SWITCH: False,
  302. }
  303. assert "create_entry" == result["type"]
  304. assert "" == result["title"]
  305. assert result["result"] is True
  306. assert expected == result["data"]