| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636 |
- """Tests for the config flow."""
- from unittest.mock import ANY, AsyncMock, MagicMock, patch
- from homeassistant.const import CONF_HOST, CONF_NAME
- import pytest
- from pytest_homeassistant_custom_component.common import MockConfigEntry
- import voluptuous as vol
- from custom_components.tuya_local import (
- config_flow,
- async_migrate_entry,
- async_setup_entry,
- )
- from custom_components.tuya_local.const import (
- CONF_DEVICE_ID,
- CONF_LOCAL_KEY,
- CONF_PROTOCOL_VERSION,
- CONF_TYPE,
- DOMAIN,
- )
- @pytest.fixture(autouse=True)
- def auto_enable_custom_integrations(enable_custom_integrations):
- yield
- @pytest.fixture(autouse=True)
- def prevent_task_creation():
- with patch(
- "custom_components.tuya_local.device.TuyaLocalDevice.register_entity",
- ):
- yield
- @pytest.fixture
- def bypass_setup():
- """Prevent actual setup of the integration after config flow."""
- with patch(
- "custom_components.tuya_local.async_setup_entry",
- return_value=True,
- ):
- yield
- @pytest.mark.asyncio
- async def test_init_entry(hass):
- """Test initialisation of the config flow."""
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- title="test",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "kogan_kahtp_heater",
- },
- options={},
- )
- entry.add_to_hass(hass)
- await hass.config_entries.async_setup(entry.entry_id)
- await hass.async_block_till_done()
- assert hass.states.get("climate.test")
- assert hass.states.get("lock.test_child_lock")
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.setup_device")
- async def test_migrate_entry(mock_setup, hass):
- """Test migration from old entry format."""
- mock_device = MagicMock()
- mock_device.async_inferred_type = AsyncMock(return_value="goldair_gpph_heater")
- mock_setup.return_value = mock_device
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=1,
- title="test",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "auto",
- "climate": True,
- "child_lock": True,
- "display_light": True,
- },
- )
- assert await async_migrate_entry(hass, entry)
- mock_device.async_inferred_type = AsyncMock(return_value=None)
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=1,
- title="test2",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "unknown",
- "climate": False,
- },
- )
- assert not await async_migrate_entry(hass, entry)
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=2,
- title="test3",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "auto",
- },
- options={
- "climate": False,
- },
- )
- assert not await async_migrate_entry(hass, entry)
- mock_device.async_inferred_type = AsyncMock(return_value="smartplugv1")
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=3,
- title="test4",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "smartplugv1",
- },
- options={
- "switch": True,
- },
- )
- assert await async_migrate_entry(hass, entry)
- mock_device.async_inferred_type = AsyncMock(return_value="smartplugv2")
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=3,
- title="test5",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "smartplugv1",
- },
- options={
- "switch": True,
- },
- )
- assert await async_migrate_entry(hass, entry)
- mock_device.async_inferred_type = AsyncMock(return_value="goldair_dehumidifier")
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=4,
- title="test6",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "goldair_dehumidifier",
- },
- options={
- "humidifier": True,
- "fan": True,
- "light": True,
- "lock": False,
- "switch": True,
- },
- )
- assert await async_migrate_entry(hass, entry)
- mock_device.async_inferred_type = AsyncMock(
- return_value="grid_connect_usb_double_power_point"
- )
- mock_device.reset_mock()
- entry = MockConfigEntry(
- domain=DOMAIN,
- version=6,
- title="test7",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_TYPE: "grid_connect_usb_double_power_point",
- },
- options={
- "switch_main_switch": True,
- "switch_left_outlet": True,
- "switch_right_outlet": True,
- },
- )
- assert await async_migrate_entry(hass, entry)
- @pytest.mark.asyncio
- async def test_flow_user_init(hass):
- """Test the initialisation of the form in the first step of the config flow."""
- result = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "user"}
- )
- expected = {
- "data_schema": ANY,
- "description_placeholders": None,
- "errors": {},
- "flow_id": ANY,
- "handler": DOMAIN,
- "step_id": "user",
- "type": "form",
- "last_step": ANY,
- }
- assert expected == result
- # Check the schema. Simple comparison does not work since they are not
- # the same object
- try:
- result["data_schema"](
- {CONF_DEVICE_ID: "test", CONF_LOCAL_KEY: "test", CONF_HOST: "test"}
- )
- except vol.MultipleInvalid:
- assert False
- try:
- result["data_schema"]({CONF_DEVICE_ID: "missing_some"})
- assert False
- except vol.MultipleInvalid:
- pass
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
- async def test_async_test_connection_valid(mock_device, hass):
- """Test that device is returned when connection is valid."""
- mock_instance = AsyncMock()
- mock_instance.has_returned_state = True
- mock_device.return_value = mock_instance
- device = await config_flow.async_test_connection(
- {
- CONF_DEVICE_ID: "deviceid",
- CONF_LOCAL_KEY: "localkey",
- CONF_HOST: "hostname",
- CONF_PROTOCOL_VERSION: "auto",
- },
- hass,
- )
- assert device == mock_instance
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.TuyaLocalDevice")
- async def test_async_test_connection_invalid(mock_device, hass):
- """Test that None is returned when connection is invalid."""
- mock_instance = AsyncMock()
- mock_instance.has_returned_state = False
- mock_device.return_value = mock_instance
- device = await config_flow.async_test_connection(
- {
- CONF_DEVICE_ID: "deviceid",
- CONF_LOCAL_KEY: "localkey",
- CONF_HOST: "hostname",
- CONF_PROTOCOL_VERSION: "auto",
- },
- hass,
- )
- assert device is None
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.async_test_connection")
- async def test_flow_user_init_invalid_config(mock_test, hass):
- """Test errors populated when config is invalid."""
- mock_test.return_value = None
- flow = await hass.config_entries.flow.async_init(DOMAIN, context={"source": "user"})
- result = await hass.config_entries.flow.async_configure(
- flow["flow_id"],
- user_input={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "badkey",
- CONF_PROTOCOL_VERSION: "auto",
- },
- )
- assert {"base": "connection"} == result["errors"]
- def setup_device_mock(mock, failure=False, type="test"):
- mock_type = MagicMock()
- mock_type.legacy_type = type
- mock_type.config_type = type
- mock_type.match_quality.return_value = 100
- mock_iter = MagicMock()
- mock_iter.__aiter__.return_value = [mock_type] if not failure else []
- mock.async_possible_types = MagicMock(return_value=mock_iter)
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.async_test_connection")
- async def test_flow_user_init_data_valid(mock_test, hass):
- """Test we advance to the next step when connection config is valid."""
- mock_device = MagicMock()
- setup_device_mock(mock_device)
- mock_test.return_value = mock_device
- flow = await hass.config_entries.flow.async_init(DOMAIN, context={"source": "user"})
- result = await hass.config_entries.flow.async_configure(
- flow["flow_id"],
- user_input={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- },
- )
- assert "form" == result["type"]
- assert "select_type" == result["step_id"]
- @pytest.mark.asyncio
- @patch.object(config_flow.ConfigFlowHandler, "device")
- async def test_flow_select_type_init(mock_device, hass):
- """Test the initialisation of the form in the 2nd step of the config flow."""
- setup_device_mock(mock_device)
- result = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "select_type"}
- )
- expected = {
- "data_schema": ANY,
- "description_placeholders": None,
- "errors": None,
- "flow_id": ANY,
- "handler": DOMAIN,
- "step_id": "select_type",
- "type": "form",
- "last_step": ANY,
- }
- assert expected == result
- # Check the schema. Simple comparison does not work since they are not
- # the same object
- try:
- result["data_schema"]({CONF_TYPE: "test"})
- except vol.MultipleInvalid:
- assert False
- try:
- result["data_schema"]({CONF_TYPE: "not_test"})
- assert False
- except vol.MultipleInvalid:
- pass
- @pytest.mark.asyncio
- @patch.object(config_flow.ConfigFlowHandler, "device")
- async def test_flow_select_type_aborts_when_no_match(mock_device, hass):
- """Test the flow aborts when an unsupported device is used."""
- setup_device_mock(mock_device, failure=True)
- result = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "select_type"}
- )
- assert result["type"] == "abort"
- assert result["reason"] == "not_supported"
- @pytest.mark.asyncio
- @patch.object(config_flow.ConfigFlowHandler, "device")
- async def test_flow_select_type_data_valid(mock_device, hass):
- """Test the flow continues when valid data is supplied."""
- setup_device_mock(mock_device, type="kogan_switch")
- flow = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "select_type"}
- )
- result = await hass.config_entries.flow.async_configure(
- flow["flow_id"],
- user_input={CONF_TYPE: "kogan_switch"},
- )
- assert "form" == result["type"]
- assert "choose_entities" == result["step_id"]
- @pytest.mark.asyncio
- async def test_flow_choose_entities_init(hass):
- """Test the initialisation of the form in the 3rd step of the config flow."""
- with patch.dict(config_flow.ConfigFlowHandler.data, {CONF_TYPE: "kogan_switch"}):
- result = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "choose_entities"}
- )
- expected = {
- "data_schema": ANY,
- "description_placeholders": None,
- "errors": None,
- "flow_id": ANY,
- "handler": DOMAIN,
- "step_id": "choose_entities",
- "type": "form",
- "last_step": ANY,
- }
- assert expected == result
- # Check the schema. Simple comparison does not work since they are not
- # the same object
- try:
- result["data_schema"]({CONF_NAME: "test"})
- except vol.MultipleInvalid:
- assert False
- try:
- result["data_schema"]({"climate": True})
- assert False
- except vol.MultipleInvalid:
- pass
- @pytest.mark.asyncio
- async def test_flow_choose_entities_creates_config_entry(hass, bypass_setup):
- """Test the flow ends when data is valid."""
- with patch.dict(
- config_flow.ConfigFlowHandler.data,
- {
- CONF_DEVICE_ID: "deviceid",
- CONF_LOCAL_KEY: "localkey",
- CONF_HOST: "hostname",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "kogan_kahtp_heater",
- },
- ):
- flow = await hass.config_entries.flow.async_init(
- DOMAIN, context={"source": "choose_entities"}
- )
- result = await hass.config_entries.flow.async_configure(
- flow["flow_id"],
- user_input={
- CONF_NAME: "test",
- },
- )
- expected = {
- "version": 10,
- "context": {"source": "choose_entities"},
- "type": "create_entry",
- "flow_id": ANY,
- "handler": DOMAIN,
- "title": "test",
- "description": None,
- "description_placeholders": None,
- "result": ANY,
- "options": {},
- "data": {
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "kogan_kahtp_heater",
- },
- }
- assert expected == result
- @pytest.mark.asyncio
- async def test_options_flow_init(hass):
- """Test config flow options."""
- config_entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- unique_id="uniqueid",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_NAME: "test",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "smartplugv1",
- },
- )
- config_entry.add_to_hass(hass)
- assert await hass.config_entries.async_setup(config_entry.entry_id)
- await hass.async_block_till_done()
- # show initial form
- result = await hass.config_entries.options.async_init(config_entry.entry_id)
- assert "form" == result["type"]
- assert "user" == result["step_id"]
- assert {} == result["errors"]
- assert result["data_schema"](
- {
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- }
- )
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.async_test_connection")
- async def test_options_flow_modifies_config(mock_test, hass):
- mock_device = MagicMock()
- mock_test.return_value = mock_device
- config_entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- unique_id="uniqueid",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_NAME: "test",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "kogan_kahtp_heater",
- },
- )
- config_entry.add_to_hass(hass)
- assert await hass.config_entries.async_setup(config_entry.entry_id)
- await hass.async_block_till_done()
- # show initial form
- form = await hass.config_entries.options.async_init(config_entry.entry_id)
- # submit updated config
- result = await hass.config_entries.options.async_configure(
- form["flow_id"],
- user_input={
- CONF_HOST: "new_hostname",
- CONF_LOCAL_KEY: "new_key",
- CONF_PROTOCOL_VERSION: 3.3,
- },
- )
- expected = {
- CONF_HOST: "new_hostname",
- CONF_LOCAL_KEY: "new_key",
- CONF_PROTOCOL_VERSION: 3.3,
- }
- assert "create_entry" == result["type"]
- assert "" == result["title"]
- assert result["result"] is True
- assert expected == result["data"]
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.async_test_connection")
- async def test_options_flow_fails_when_connection_fails(mock_test, hass):
- mock_test.return_value = None
- config_entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- unique_id="uniqueid",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_NAME: "test",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "smartplugv1",
- },
- )
- config_entry.add_to_hass(hass)
- assert await hass.config_entries.async_setup(config_entry.entry_id)
- await hass.async_block_till_done()
- # show initial form
- form = await hass.config_entries.options.async_init(config_entry.entry_id)
- # submit updated config
- result = await hass.config_entries.options.async_configure(
- form["flow_id"],
- user_input={
- CONF_HOST: "new_hostname",
- CONF_LOCAL_KEY: "new_key",
- },
- )
- assert "form" == result["type"]
- assert "user" == result["step_id"]
- assert {"base": "connection"} == result["errors"]
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.config_flow.async_test_connection")
- async def test_options_flow_fails_when_config_is_missing(mock_test, hass):
- mock_device = MagicMock()
- mock_test.return_value = mock_device
- config_entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- unique_id="uniqueid",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_NAME: "test",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "non_existing",
- },
- )
- config_entry.add_to_hass(hass)
- await hass.config_entries.async_setup(config_entry.entry_id)
- await hass.async_block_till_done()
- # show initial form
- result = await hass.config_entries.options.async_init(config_entry.entry_id)
- assert result["type"] == "abort"
- assert result["reason"] == "not_supported"
- @pytest.mark.asyncio
- @patch("custom_components.tuya_local.setup_device")
- async def test_async_setup_entry_for_switch(mock_device, hass):
- """Test setting up based on a config entry. Repeats test_init_entry."""
- config_entry = MockConfigEntry(
- domain=DOMAIN,
- version=10,
- unique_id="uniqueid",
- data={
- CONF_DEVICE_ID: "deviceid",
- CONF_HOST: "hostname",
- CONF_LOCAL_KEY: "localkey",
- CONF_NAME: "test",
- CONF_PROTOCOL_VERSION: "auto",
- CONF_TYPE: "smartplugv2",
- },
- )
- assert await async_setup_entry(hass, config_entry)
|