test_config_flow.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715
  1. """Tests for the config flow."""
  2. from unittest.mock import ANY, AsyncMock, MagicMock, patch
  3. import pytest
  4. import voluptuous as vol
  5. from homeassistant.const import CONF_HOST, CONF_NAME
  6. from homeassistant.data_entry_flow import FlowResultType
  7. from pytest_homeassistant_custom_component.common import MockConfigEntry
  8. from custom_components.tuya_local import (
  9. async_migrate_entry,
  10. config_flow,
  11. get_device_unique_id,
  12. )
  13. from custom_components.tuya_local.const import (
  14. CONF_DEVICE_CID,
  15. CONF_DEVICE_ID,
  16. CONF_LOCAL_KEY,
  17. CONF_POLL_ONLY,
  18. CONF_PROTOCOL_VERSION,
  19. CONF_TYPE,
  20. DOMAIN,
  21. )
  22. # Designed to contain "special" characters that users constantly suspect.
  23. TESTKEY = ")<jO<@)'P1|kR$Kd"
  24. @pytest.fixture(autouse=True)
  25. def auto_enable_custom_integrations(enable_custom_integrations):
  26. yield
  27. @pytest.fixture(autouse=True)
  28. def prevent_task_creation():
  29. with patch(
  30. "custom_components.tuya_local.device.TuyaLocalDevice.register_entity",
  31. ):
  32. yield
  33. @pytest.fixture
  34. def bypass_setup():
  35. """Prevent actual setup of the integration after config flow."""
  36. with patch(
  37. "custom_components.tuya_local.async_setup_entry",
  38. return_value=True,
  39. ):
  40. yield
  41. @pytest.fixture
  42. def bypass_data_fetch():
  43. """Prevent actual data fetching from the device."""
  44. with patch(
  45. "tinytuya.Device.status",
  46. return_value={"1": True},
  47. ):
  48. yield
  49. @pytest.mark.asyncio
  50. async def test_init_entry(hass, bypass_data_fetch):
  51. """Test initialisation of the config flow."""
  52. entry = MockConfigEntry(
  53. domain=DOMAIN,
  54. version=11,
  55. title="test",
  56. data={
  57. CONF_DEVICE_ID: "deviceid",
  58. CONF_HOST: "hostname",
  59. CONF_LOCAL_KEY: TESTKEY,
  60. CONF_POLL_ONLY: False,
  61. CONF_PROTOCOL_VERSION: "auto",
  62. CONF_TYPE: "kogan_kahtp_heater",
  63. CONF_DEVICE_CID: None,
  64. },
  65. options={},
  66. )
  67. entry.add_to_hass(hass)
  68. await hass.config_entries.async_setup(entry.entry_id)
  69. await hass.async_block_till_done()
  70. assert hass.states.get("climate.test")
  71. assert hass.states.get("lock.test_child_lock")
  72. @pytest.mark.asyncio
  73. @patch("custom_components.tuya_local.setup_device")
  74. async def test_migrate_entry(mock_setup, hass):
  75. """Test migration from old entry format."""
  76. mock_device = MagicMock()
  77. mock_device.async_inferred_type = AsyncMock(return_value="goldair_gpph_heater")
  78. mock_setup.return_value = mock_device
  79. entry = MockConfigEntry(
  80. domain=DOMAIN,
  81. version=1,
  82. title="test",
  83. data={
  84. CONF_DEVICE_ID: "deviceid",
  85. CONF_HOST: "hostname",
  86. CONF_LOCAL_KEY: TESTKEY,
  87. CONF_TYPE: "auto",
  88. "climate": True,
  89. "child_lock": True,
  90. "display_light": True,
  91. },
  92. )
  93. entry.add_to_hass(hass)
  94. assert await async_migrate_entry(hass, entry)
  95. mock_device.async_inferred_type = AsyncMock(return_value=None)
  96. mock_device.reset_mock()
  97. entry = MockConfigEntry(
  98. domain=DOMAIN,
  99. version=1,
  100. title="test2",
  101. data={
  102. CONF_DEVICE_ID: "deviceid",
  103. CONF_HOST: "hostname",
  104. CONF_LOCAL_KEY: TESTKEY,
  105. CONF_TYPE: "unknown",
  106. "climate": False,
  107. },
  108. )
  109. entry.add_to_hass(hass)
  110. assert not await async_migrate_entry(hass, entry)
  111. mock_device.reset_mock()
  112. entry = MockConfigEntry(
  113. domain=DOMAIN,
  114. version=2,
  115. title="test3",
  116. data={
  117. CONF_DEVICE_ID: "deviceid",
  118. CONF_HOST: "hostname",
  119. CONF_LOCAL_KEY: TESTKEY,
  120. CONF_TYPE: "auto",
  121. },
  122. options={
  123. "climate": False,
  124. },
  125. )
  126. entry.add_to_hass(hass)
  127. assert not await async_migrate_entry(hass, entry)
  128. mock_device.async_inferred_type = AsyncMock(return_value="smartplugv1")
  129. mock_device.reset_mock()
  130. entry = MockConfigEntry(
  131. domain=DOMAIN,
  132. version=3,
  133. title="test4",
  134. data={
  135. CONF_DEVICE_ID: "deviceid",
  136. CONF_HOST: "hostname",
  137. CONF_LOCAL_KEY: TESTKEY,
  138. CONF_TYPE: "smartplugv1",
  139. },
  140. options={
  141. "switch": True,
  142. },
  143. )
  144. entry.add_to_hass(hass)
  145. assert await async_migrate_entry(hass, entry)
  146. mock_device.async_inferred_type = AsyncMock(return_value="smartplugv2")
  147. mock_device.reset_mock()
  148. entry = MockConfigEntry(
  149. domain=DOMAIN,
  150. version=3,
  151. title="test5",
  152. data={
  153. CONF_DEVICE_ID: "deviceid",
  154. CONF_HOST: "hostname",
  155. CONF_LOCAL_KEY: TESTKEY,
  156. CONF_TYPE: "smartplugv1",
  157. },
  158. options={
  159. "switch": True,
  160. },
  161. )
  162. entry.add_to_hass(hass)
  163. assert await async_migrate_entry(hass, entry)
  164. mock_device.async_inferred_type = AsyncMock(return_value="goldair_dehumidifier")
  165. mock_device.reset_mock()
  166. entry = MockConfigEntry(
  167. domain=DOMAIN,
  168. version=4,
  169. title="test6",
  170. data={
  171. CONF_DEVICE_ID: "deviceid",
  172. CONF_HOST: "hostname",
  173. CONF_LOCAL_KEY: TESTKEY,
  174. CONF_TYPE: "goldair_dehumidifier",
  175. },
  176. options={
  177. "humidifier": True,
  178. "fan": True,
  179. "light": True,
  180. "lock": False,
  181. "switch": True,
  182. },
  183. )
  184. entry.add_to_hass(hass)
  185. assert await async_migrate_entry(hass, entry)
  186. mock_device.async_inferred_type = AsyncMock(
  187. return_value="grid_connect_usb_double_power_point"
  188. )
  189. mock_device.reset_mock()
  190. entry = MockConfigEntry(
  191. domain=DOMAIN,
  192. version=6,
  193. title="test7",
  194. data={
  195. CONF_DEVICE_ID: "deviceid",
  196. CONF_HOST: "hostname",
  197. CONF_LOCAL_KEY: TESTKEY,
  198. CONF_TYPE: "grid_connect_usb_double_power_point",
  199. },
  200. options={
  201. "switch_main_switch": True,
  202. "switch_left_outlet": True,
  203. "switch_right_outlet": True,
  204. },
  205. )
  206. entry.add_to_hass(hass)
  207. assert await async_migrate_entry(hass, entry)
  208. @pytest.mark.asyncio
  209. async def test_flow_user_init(hass):
  210. """Test the initialisation of the form in the first page of the manual config flow path."""
  211. result = await hass.config_entries.flow.async_init(
  212. DOMAIN, context={"source": "local"}
  213. )
  214. expected = {
  215. "data_schema": ANY,
  216. "description_placeholders": ANY,
  217. "errors": {},
  218. "flow_id": ANY,
  219. "handler": DOMAIN,
  220. "step_id": "local",
  221. "type": "form",
  222. "last_step": ANY,
  223. "preview": ANY,
  224. }
  225. assert expected == result
  226. # Check the schema. Simple comparison does not work since they are not
  227. # the same object
  228. try:
  229. result["data_schema"](
  230. {CONF_DEVICE_ID: "test", CONF_LOCAL_KEY: TESTKEY, CONF_HOST: "test"}
  231. )
  232. except vol.MultipleInvalid:
  233. assert False
  234. try:
  235. result["data_schema"]({CONF_DEVICE_ID: "missing_some"})
  236. assert False
  237. except vol.MultipleInvalid:
  238. pass
  239. @pytest.mark.asyncio
  240. @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
  241. async def test_async_test_connection_valid(mock_device, hass):
  242. """Test that device is returned when connection is valid."""
  243. mock_instance = AsyncMock()
  244. mock_instance.has_returned_state = True
  245. mock_instance.pause = MagicMock()
  246. mock_instance.resume = MagicMock()
  247. mock_device.return_value = mock_instance
  248. hass.data[DOMAIN] = {"deviceid": {"device": mock_instance}}
  249. device = await config_flow.async_test_connection(
  250. {
  251. CONF_DEVICE_ID: "deviceid",
  252. CONF_LOCAL_KEY: TESTKEY,
  253. CONF_HOST: "hostname",
  254. CONF_PROTOCOL_VERSION: "auto",
  255. },
  256. hass,
  257. )
  258. assert device == mock_instance
  259. mock_instance.pause.assert_called_once()
  260. mock_instance.resume.assert_called_once()
  261. @pytest.mark.asyncio
  262. @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
  263. async def test_async_test_connection_for_subdevice_valid(mock_device, hass):
  264. """Test that subdevice is returned when connection is valid."""
  265. mock_instance = AsyncMock()
  266. mock_instance.has_returned_state = True
  267. mock_instance.pause = MagicMock()
  268. mock_instance.resume = MagicMock()
  269. mock_device.return_value = mock_instance
  270. hass.data[DOMAIN] = {"subdeviceid": {"device": mock_instance}}
  271. device = await config_flow.async_test_connection(
  272. {
  273. CONF_DEVICE_ID: "deviceid",
  274. CONF_LOCAL_KEY: TESTKEY,
  275. CONF_HOST: "hostname",
  276. CONF_PROTOCOL_VERSION: "auto",
  277. CONF_DEVICE_CID: "subdeviceid",
  278. },
  279. hass,
  280. )
  281. assert device == mock_instance
  282. mock_instance.pause.assert_called_once()
  283. mock_instance.resume.assert_called_once()
  284. @pytest.mark.asyncio
  285. @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
  286. async def test_async_test_connection_invalid(mock_device, hass):
  287. """Test that None is returned when connection is invalid."""
  288. mock_instance = AsyncMock()
  289. mock_instance.has_returned_state = False
  290. mock_device.return_value = mock_instance
  291. device = await config_flow.async_test_connection(
  292. {
  293. CONF_DEVICE_ID: "deviceid",
  294. CONF_LOCAL_KEY: TESTKEY,
  295. CONF_HOST: "hostname",
  296. CONF_PROTOCOL_VERSION: "auto",
  297. },
  298. hass,
  299. )
  300. assert device is None
  301. @pytest.mark.asyncio
  302. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  303. async def test_flow_user_init_invalid_config(mock_test, hass):
  304. """Test errors populated when config is invalid."""
  305. mock_test.return_value = None
  306. flow = await hass.config_entries.flow.async_init(
  307. DOMAIN, context={"source": "local"}
  308. )
  309. result = await hass.config_entries.flow.async_configure(
  310. flow["flow_id"],
  311. user_input={
  312. CONF_DEVICE_ID: "deviceid",
  313. CONF_HOST: "hostname",
  314. CONF_LOCAL_KEY: "badkey",
  315. CONF_PROTOCOL_VERSION: "auto",
  316. CONF_POLL_ONLY: False,
  317. },
  318. )
  319. assert {"base": "connection"} == result["errors"]
  320. def setup_device_mock(mock, failure=False, type="test"):
  321. mock_type = MagicMock()
  322. mock_type.legacy_type = type
  323. mock_type.config_type = type
  324. mock_type.match_quality.return_value = 100
  325. mock.async_possible_types = AsyncMock(
  326. return_value=[mock_type] if not failure else []
  327. )
  328. @pytest.mark.asyncio
  329. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  330. async def test_flow_user_init_data_valid(mock_test, hass):
  331. """Test we advance to the next step when connection config is valid."""
  332. mock_device = MagicMock()
  333. setup_device_mock(mock_device)
  334. mock_test.return_value = mock_device
  335. flow = await hass.config_entries.flow.async_init(
  336. DOMAIN, context={"source": "local"}
  337. )
  338. result = await hass.config_entries.flow.async_configure(
  339. flow["flow_id"],
  340. user_input={
  341. CONF_DEVICE_ID: "deviceid",
  342. CONF_HOST: "hostname",
  343. CONF_LOCAL_KEY: TESTKEY,
  344. },
  345. )
  346. assert "form" == result["type"]
  347. assert "select_type" == result["step_id"]
  348. @pytest.mark.asyncio
  349. @patch.object(config_flow.ConfigFlowHandler, "device")
  350. async def test_flow_select_type_init(mock_device, hass):
  351. """Test the initialisation of the form in the 2nd step of the config flow."""
  352. setup_device_mock(mock_device)
  353. result = await hass.config_entries.flow.async_init(
  354. DOMAIN, context={"source": "select_type"}
  355. )
  356. expected = {
  357. "data_schema": ANY,
  358. "description_placeholders": None,
  359. "errors": None,
  360. "flow_id": ANY,
  361. "handler": DOMAIN,
  362. "step_id": "select_type",
  363. "type": "form",
  364. "last_step": ANY,
  365. "preview": ANY,
  366. }
  367. assert expected == result
  368. # Check the schema. Simple comparison does not work since they are not
  369. # the same object
  370. try:
  371. result["data_schema"]({CONF_TYPE: "test"})
  372. except vol.MultipleInvalid:
  373. assert False
  374. try:
  375. result["data_schema"]({CONF_TYPE: "not_test"})
  376. assert False
  377. except vol.MultipleInvalid:
  378. pass
  379. @pytest.mark.asyncio
  380. @patch.object(config_flow.ConfigFlowHandler, "device")
  381. async def test_flow_select_type_aborts_when_no_match(mock_device, hass):
  382. """Test the flow aborts when an unsupported device is used."""
  383. setup_device_mock(mock_device, failure=True)
  384. result = await hass.config_entries.flow.async_init(
  385. DOMAIN, context={"source": "select_type"}
  386. )
  387. assert result["type"] == "abort"
  388. assert result["reason"] == "not_supported"
  389. @pytest.mark.asyncio
  390. @patch.object(config_flow.ConfigFlowHandler, "device")
  391. async def test_flow_select_type_data_valid(mock_device, hass):
  392. """Test the flow continues when valid data is supplied."""
  393. setup_device_mock(mock_device, type="smartplugv1")
  394. flow = await hass.config_entries.flow.async_init(
  395. DOMAIN, context={"source": "select_type"}
  396. )
  397. result = await hass.config_entries.flow.async_configure(
  398. flow["flow_id"],
  399. user_input={CONF_TYPE: "smartplugv1"},
  400. )
  401. assert "form" == result["type"]
  402. assert "choose_entities" == result["step_id"]
  403. @pytest.mark.asyncio
  404. async def test_flow_choose_entities_init(hass):
  405. """Test the initialisation of the form in the 3rd step of the config flow."""
  406. with patch.dict(config_flow.ConfigFlowHandler.data, {CONF_TYPE: "smartplugv1"}):
  407. result = await hass.config_entries.flow.async_init(
  408. DOMAIN, context={"source": "choose_entities"}
  409. )
  410. expected = {
  411. "data_schema": ANY,
  412. "description_placeholders": None,
  413. "errors": None,
  414. "flow_id": ANY,
  415. "handler": DOMAIN,
  416. "step_id": "choose_entities",
  417. "type": "form",
  418. "last_step": ANY,
  419. "preview": ANY,
  420. }
  421. assert expected == result
  422. # Check the schema. Simple comparison does not work since they are not
  423. # the same object
  424. try:
  425. result["data_schema"]({CONF_NAME: "test"})
  426. except vol.MultipleInvalid:
  427. assert False
  428. try:
  429. result["data_schema"]({"climate": True})
  430. assert False
  431. except vol.MultipleInvalid:
  432. pass
  433. @pytest.mark.asyncio
  434. async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
  435. """Test the flow ends when data is valid."""
  436. with patch.dict(
  437. config_flow.ConfigFlowHandler.data,
  438. {
  439. CONF_DEVICE_ID: "deviceid",
  440. CONF_LOCAL_KEY: TESTKEY,
  441. CONF_HOST: "hostname",
  442. CONF_POLL_ONLY: False,
  443. CONF_PROTOCOL_VERSION: "auto",
  444. CONF_TYPE: "kogan_kahtp_heater",
  445. CONF_DEVICE_CID: None,
  446. },
  447. ):
  448. flow = await hass.config_entries.flow.async_init(
  449. DOMAIN, context={"source": "choose_entities"}
  450. )
  451. result = await hass.config_entries.flow.async_configure(
  452. flow["flow_id"],
  453. user_input={
  454. CONF_NAME: "test",
  455. },
  456. )
  457. expected = {
  458. "version": 13,
  459. "minor_version": ANY,
  460. "context": {"source": "choose_entities"},
  461. "type": FlowResultType.CREATE_ENTRY,
  462. "flow_id": ANY,
  463. "handler": DOMAIN,
  464. "title": "test",
  465. "description": None,
  466. "description_placeholders": None,
  467. "result": ANY,
  468. "subentries": (),
  469. "options": {},
  470. "data": {
  471. CONF_DEVICE_ID: "deviceid",
  472. CONF_HOST: "hostname",
  473. CONF_LOCAL_KEY: TESTKEY,
  474. CONF_POLL_ONLY: False,
  475. CONF_PROTOCOL_VERSION: "auto",
  476. CONF_TYPE: "kogan_kahtp_heater",
  477. CONF_DEVICE_CID: None,
  478. },
  479. }
  480. assert expected == result
  481. @pytest.mark.asyncio
  482. async def test_options_flow_init(hass, bypass_data_fetch):
  483. """Test config flow options."""
  484. config_entry = MockConfigEntry(
  485. domain=DOMAIN,
  486. version=13,
  487. unique_id="uniqueid",
  488. data={
  489. CONF_DEVICE_ID: "deviceid",
  490. CONF_HOST: "hostname",
  491. CONF_LOCAL_KEY: TESTKEY,
  492. CONF_NAME: "test",
  493. CONF_POLL_ONLY: False,
  494. CONF_PROTOCOL_VERSION: "auto",
  495. CONF_TYPE: "smartplugv1",
  496. CONF_DEVICE_CID: "",
  497. },
  498. )
  499. config_entry.add_to_hass(hass)
  500. assert await hass.config_entries.async_setup(config_entry.entry_id)
  501. await hass.async_block_till_done()
  502. # show initial form
  503. result = await hass.config_entries.options.async_init(config_entry.entry_id)
  504. assert "form" == result["type"]
  505. assert "user" == result["step_id"]
  506. assert {} == result["errors"]
  507. assert result["data_schema"](
  508. {
  509. CONF_HOST: "hostname",
  510. CONF_LOCAL_KEY: TESTKEY,
  511. }
  512. )
  513. @pytest.mark.asyncio
  514. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  515. async def test_options_flow_modifies_config(mock_test, hass, bypass_setup):
  516. mock_device = MagicMock()
  517. mock_test.return_value = mock_device
  518. config_entry = MockConfigEntry(
  519. domain=DOMAIN,
  520. version=13,
  521. unique_id="uniqueid",
  522. data={
  523. CONF_DEVICE_ID: "deviceid",
  524. CONF_HOST: "hostname",
  525. CONF_LOCAL_KEY: TESTKEY,
  526. CONF_NAME: "test",
  527. CONF_POLL_ONLY: False,
  528. CONF_PROTOCOL_VERSION: "auto",
  529. CONF_TYPE: "ble_pt216_temp_humidity",
  530. CONF_DEVICE_CID: "subdeviceid",
  531. },
  532. )
  533. config_entry.add_to_hass(hass)
  534. assert await hass.config_entries.async_setup(config_entry.entry_id)
  535. await hass.async_block_till_done()
  536. # show initial form
  537. form = await hass.config_entries.options.async_init(config_entry.entry_id)
  538. # submit updated config
  539. result = await hass.config_entries.options.async_configure(
  540. form["flow_id"],
  541. user_input={
  542. CONF_HOST: "new_hostname",
  543. CONF_LOCAL_KEY: "new_key",
  544. CONF_POLL_ONLY: False,
  545. CONF_PROTOCOL_VERSION: 3.3,
  546. },
  547. )
  548. expected = {
  549. CONF_HOST: "new_hostname",
  550. CONF_LOCAL_KEY: "new_key",
  551. CONF_POLL_ONLY: False,
  552. CONF_PROTOCOL_VERSION: 3.3,
  553. }
  554. assert "create_entry" == result["type"]
  555. assert "" == result["title"]
  556. assert result["result"] is True
  557. assert expected == result["data"]
  558. @pytest.mark.asyncio
  559. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  560. async def test_options_flow_fails_when_connection_fails(
  561. mock_test, hass, bypass_data_fetch
  562. ):
  563. mock_test.return_value = None
  564. config_entry = MockConfigEntry(
  565. domain=DOMAIN,
  566. version=13,
  567. unique_id="uniqueid",
  568. data={
  569. CONF_DEVICE_ID: "deviceid",
  570. CONF_HOST: "hostname",
  571. CONF_LOCAL_KEY: TESTKEY,
  572. CONF_NAME: "test",
  573. CONF_POLL_ONLY: False,
  574. CONF_PROTOCOL_VERSION: "auto",
  575. CONF_TYPE: "smartplugv1",
  576. CONF_DEVICE_CID: "",
  577. },
  578. )
  579. config_entry.add_to_hass(hass)
  580. assert await hass.config_entries.async_setup(config_entry.entry_id)
  581. await hass.async_block_till_done()
  582. # show initial form
  583. form = await hass.config_entries.options.async_init(config_entry.entry_id)
  584. # submit updated config
  585. result = await hass.config_entries.options.async_configure(
  586. form["flow_id"],
  587. user_input={
  588. CONF_HOST: "new_hostname",
  589. CONF_LOCAL_KEY: "new_key",
  590. },
  591. )
  592. assert "form" == result["type"]
  593. assert "user" == result["step_id"]
  594. assert {"base": "connection"} == result["errors"]
  595. @pytest.mark.asyncio
  596. @patch("custom_components.tuya_local.config_flow.async_test_connection")
  597. async def test_options_flow_fails_when_config_is_missing(mock_test, hass):
  598. mock_device = MagicMock()
  599. mock_test.return_value = mock_device
  600. config_entry = MockConfigEntry(
  601. domain=DOMAIN,
  602. version=13,
  603. unique_id="uniqueid",
  604. data={
  605. CONF_DEVICE_ID: "deviceid",
  606. CONF_HOST: "hostname",
  607. CONF_LOCAL_KEY: TESTKEY,
  608. CONF_NAME: "test",
  609. CONF_POLL_ONLY: False,
  610. CONF_PROTOCOL_VERSION: "auto",
  611. CONF_TYPE: "non_existing",
  612. },
  613. )
  614. config_entry.add_to_hass(hass)
  615. await hass.config_entries.async_setup(config_entry.entry_id)
  616. await hass.async_block_till_done()
  617. # show initial form
  618. result = await hass.config_entries.options.async_init(config_entry.entry_id)
  619. assert result["type"] == "abort"
  620. assert result["reason"] == "not_supported"
  621. def test_migration_gets_correct_device_id():
  622. """Test that migration gets the correct device id."""
  623. # Normal device
  624. entry = MockConfigEntry(
  625. domain=DOMAIN,
  626. version=1,
  627. title="test",
  628. data={
  629. CONF_DEVICE_ID: "deviceid",
  630. CONF_HOST: "hostname",
  631. CONF_LOCAL_KEY: TESTKEY,
  632. CONF_TYPE: "auto",
  633. },
  634. )
  635. assert get_device_unique_id(entry) == "deviceid"