|
|
@@ -0,0 +1,350 @@
|
|
|
+"""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
|
|
|
+from custom_components.tuya_local.const import (
|
|
|
+ CONF_CLIMATE,
|
|
|
+ CONF_DEVICE_ID,
|
|
|
+ CONF_LOCAL_KEY,
|
|
|
+ CONF_LOCK,
|
|
|
+ CONF_SWITCH,
|
|
|
+ CONF_TYPE,
|
|
|
+ DOMAIN,
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+@pytest.fixture(autouse=True)
|
|
|
+def auto_enable_custom_integrations(enable_custom_integrations):
|
|
|
+ yield
|
|
|
+
|
|
|
+
|
|
|
+async def test_init_entry(hass):
|
|
|
+ """Test initialisation of the config flow."""
|
|
|
+ entry = MockConfigEntry(
|
|
|
+ domain=DOMAIN,
|
|
|
+ version=2,
|
|
|
+ title="test",
|
|
|
+ data={
|
|
|
+ CONF_DEVICE_ID: "deviceid",
|
|
|
+ CONF_HOST: "hostname",
|
|
|
+ CONF_LOCAL_KEY: "localkey",
|
|
|
+ CONF_TYPE: "kogan_heater",
|
|
|
+ CONF_CLIMATE: True,
|
|
|
+ CONF_LOCK: True,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ entry.add_to_hass(hass)
|
|
|
+ await hass.config_entries.async_setup(entry.entry_id)
|
|
|
+ await hass.async_block_till_done()
|
|
|
+ state = hass.states.get("climate.test")
|
|
|
+ assert state
|
|
|
+
|
|
|
+
|
|
|
+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": vol.Schema(config_flow.individual_config_schema()),
|
|
|
+ "description_placeholders": None,
|
|
|
+ "errors": {},
|
|
|
+ "flow_id": ANY,
|
|
|
+ "handler": DOMAIN,
|
|
|
+ "step_id": "user",
|
|
|
+ "type": "form",
|
|
|
+ "last_step": ANY,
|
|
|
+ }
|
|
|
+ assert expected == result
|
|
|
+
|
|
|
+
|
|
|
+@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",
|
|
|
+ },
|
|
|
+ hass,
|
|
|
+ )
|
|
|
+ assert device == mock_instance
|
|
|
+
|
|
|
+
|
|
|
+@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",
|
|
|
+ },
|
|
|
+ hass,
|
|
|
+ )
|
|
|
+ assert device is None
|
|
|
+
|
|
|
+
|
|
|
+@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",
|
|
|
+ },
|
|
|
+ )
|
|
|
+ assert {"base": "connection"} == result["errors"]
|
|
|
+
|
|
|
+
|
|
|
+def setup_device_mock(mock, failure=False, type="test"):
|
|
|
+ mock_type = MagicMock()
|
|
|
+ mock_type.legacy_type = type
|
|
|
+ mock_iter = MagicMock()
|
|
|
+ mock_iter.__aiter__.return_value = [mock_type] if not failure else []
|
|
|
+ mock.async_possible_types = MagicMock(return_value=mock_iter)
|
|
|
+
|
|
|
+
|
|
|
+@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"]
|
|
|
+
|
|
|
+
|
|
|
+@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
|
|
|
+
|
|
|
+
|
|
|
+@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"
|
|
|
+
|
|
|
+
|
|
|
+@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"]
|
|
|
+
|
|
|
+
|
|
|
+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", CONF_SWITCH: True})
|
|
|
+ except vol.MultipleInvalid:
|
|
|
+ assert False
|
|
|
+ try:
|
|
|
+ result["data_schema"]({CONF_CLIMATE: True})
|
|
|
+ assert False
|
|
|
+ except vol.MultipleInvalid:
|
|
|
+ pass
|
|
|
+
|
|
|
+
|
|
|
+async def test_flow_choose_entities_creates_config_entry(hass):
|
|
|
+ """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_TYPE: "kogan_switch",
|
|
|
+ },
|
|
|
+ ):
|
|
|
+ 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", CONF_SWITCH: True},
|
|
|
+ )
|
|
|
+ expected = {
|
|
|
+ "version": 2,
|
|
|
+ "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_SWITCH: True,
|
|
|
+ CONF_TYPE: "kogan_switch",
|
|
|
+ },
|
|
|
+ }
|
|
|
+ assert expected == result
|
|
|
+
|
|
|
+
|
|
|
+async def test_options_flow_init(hass):
|
|
|
+ """Test config flow options."""
|
|
|
+ config_entry = MockConfigEntry(
|
|
|
+ domain=DOMAIN,
|
|
|
+ unique_id="uniqueid",
|
|
|
+ data={
|
|
|
+ CONF_DEVICE_ID: "deviceid",
|
|
|
+ CONF_HOST: "hostname",
|
|
|
+ CONF_LOCAL_KEY: "localkey",
|
|
|
+ CONF_NAME: "test",
|
|
|
+ CONF_SWITCH: True,
|
|
|
+ CONF_TYPE: "kogan_switch",
|
|
|
+ },
|
|
|
+ )
|
|
|
+ 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",
|
|
|
+ CONF_SWITCH: True,
|
|
|
+ }
|
|
|
+ )
|
|
|
+
|
|
|
+
|
|
|
+@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,
|
|
|
+ unique_id="uniqueid",
|
|
|
+ data={
|
|
|
+ CONF_DEVICE_ID: "deviceid",
|
|
|
+ CONF_HOST: "hostname",
|
|
|
+ CONF_LOCAL_KEY: "localkey",
|
|
|
+ CONF_NAME: "test",
|
|
|
+ CONF_SWITCH: True,
|
|
|
+ CONF_TYPE: "kogan_switch",
|
|
|
+ },
|
|
|
+ )
|
|
|
+ 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_SWITCH: False,
|
|
|
+ },
|
|
|
+ )
|
|
|
+ expected = {
|
|
|
+ CONF_HOST: "new_hostname",
|
|
|
+ CONF_LOCAL_KEY: "new_key",
|
|
|
+ CONF_SWITCH: False,
|
|
|
+ }
|
|
|
+ assert "create_entry" == result["type"]
|
|
|
+ assert "" == result["title"]
|
|
|
+ assert result["result"] is True
|
|
|
+ assert expected == result["data"]
|