Rewrite tuya config flow (#57043)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
This commit is contained in:
Joakim Sørensen 2021-10-04 17:27:24 +02:00 committed by GitHub
parent 8567aa9e13
commit 745298408a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 168 additions and 339 deletions

View File

@ -1,10 +1,12 @@
#!/usr/bin/env python3
"""Config flow for Tuya."""
from __future__ import annotations
import logging
from typing import Any
from tuya_iot import ProjectType, TuyaOpenAPI
import voluptuous as vol
from voluptuous.schema_builder import UNDEFINED
from homeassistant import config_entries
@ -16,131 +18,123 @@ from .const import (
CONF_ENDPOINT,
CONF_PASSWORD,
CONF_PROJECT_TYPE,
CONF_REGION,
CONF_USERNAME,
DOMAIN,
TUYA_APP_TYPE,
TUYA_ENDPOINT,
TUYA_PROJECT_TYPE,
SMARTLIFE_APP,
TUYA_REGIONS,
TUYA_RESPONSE_CODE,
TUYA_RESPONSE_MSG,
TUYA_RESPONSE_PLATFROM_URL,
TUYA_RESPONSE_RESULT,
TUYA_RESPONSE_SUCCESS,
TUYA_SMART_APP,
)
RESULT_SINGLE_INSTANCE = "single_instance_allowed"
RESULT_AUTH_FAILED = "invalid_auth"
TUYA_ENDPOINT_BASE = "https://openapi.tuyacn.com"
TUYA_ENDPOINT_OTHER = "https://openapi.tuyaus.com"
COUNTRY_CODE_CHINA = ["86", "+86", "China"]
_LOGGER = logging.getLogger(__name__)
# Project Type
DATA_SCHEMA_PROJECT_TYPE = vol.Schema(
{vol.Required(CONF_PROJECT_TYPE, default=0): vol.In(TUYA_PROJECT_TYPE)}
)
# INDUSTY_SOLUTIONS Schema
DATA_SCHEMA_INDUSTRY_SOLUTIONS = vol.Schema(
{
vol.Required(CONF_ENDPOINT): vol.In(TUYA_ENDPOINT),
vol.Required(CONF_ACCESS_ID): str,
vol.Required(CONF_ACCESS_SECRET): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
# SMART_HOME Schema
DATA_SCHEMA_SMART_HOME = vol.Schema(
{
vol.Required(CONF_ACCESS_ID): str,
vol.Required(CONF_ACCESS_SECRET): str,
vol.Required(CONF_APP_TYPE): vol.In(TUYA_APP_TYPE),
vol.Required(CONF_COUNTRY_CODE): str,
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
class TuyaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Tuya Config Flow."""
def __init__(self) -> None:
"""Init tuya config flow."""
super().__init__()
self.conf_project_type = None
@staticmethod
def _try_login(user_input):
project_type = ProjectType(user_input[CONF_PROJECT_TYPE])
def _try_login(user_input: dict[str, Any]) -> tuple[dict[Any, Any], dict[str, Any]]:
"""Try login."""
response = {}
data = {
CONF_ENDPOINT: TUYA_REGIONS[user_input[CONF_REGION]],
CONF_PROJECT_TYPE: ProjectType.INDUSTY_SOLUTIONS,
CONF_ACCESS_ID: user_input[CONF_ACCESS_ID],
CONF_ACCESS_SECRET: user_input[CONF_ACCESS_SECRET],
CONF_USERNAME: user_input[CONF_USERNAME],
CONF_PASSWORD: user_input[CONF_PASSWORD],
CONF_COUNTRY_CODE: user_input[CONF_REGION],
}
for app_type in ("", TUYA_SMART_APP, SMARTLIFE_APP):
data[CONF_APP_TYPE] = app_type
if data[CONF_APP_TYPE] == "":
data[CONF_PROJECT_TYPE] = ProjectType.INDUSTY_SOLUTIONS
else:
data[CONF_PROJECT_TYPE] = ProjectType.SMART_HOME
api = TuyaOpenAPI(
user_input[CONF_ENDPOINT]
if project_type == ProjectType.INDUSTY_SOLUTIONS
else "",
user_input[CONF_ACCESS_ID],
user_input[CONF_ACCESS_SECRET],
project_type,
endpoint=data[CONF_ENDPOINT],
access_id=data[CONF_ACCESS_ID],
access_secret=data[CONF_ACCESS_SECRET],
project_type=data[CONF_PROJECT_TYPE],
)
api.set_dev_channel("hass")
if project_type == ProjectType.INDUSTY_SOLUTIONS:
response = api.login(user_input[CONF_USERNAME], user_input[CONF_PASSWORD])
else:
if user_input[CONF_COUNTRY_CODE] in COUNTRY_CODE_CHINA:
api.endpoint = TUYA_ENDPOINT_BASE
else:
api.endpoint = TUYA_ENDPOINT_OTHER
response = api.login(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
user_input[CONF_COUNTRY_CODE],
user_input[CONF_APP_TYPE],
username=data[CONF_USERNAME],
password=data[CONF_PASSWORD],
country_code=data[CONF_COUNTRY_CODE],
schema=data[CONF_APP_TYPE],
)
if response.get("success", False) and isinstance(
api.token_info.platform_url, str
):
api.endpoint = api.token_info.platform_url
user_input[CONF_ENDPOINT] = api.token_info.platform_url
_LOGGER.debug("TuyaConfigFlow._try_login finish, response:, %s", response)
return response
_LOGGER.debug("Response %s", response)
if response.get(TUYA_RESPONSE_SUCCESS, False):
break
return response, data
async def async_step_user(self, user_input=None):
"""Step user."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA_PROJECT_TYPE
)
self.conf_project_type = user_input[CONF_PROJECT_TYPE]
return await self.async_step_login()
async def async_step_login(self, user_input=None):
"""Step login."""
errors = {}
if user_input is not None:
assert self.conf_project_type is not None
user_input[CONF_PROJECT_TYPE] = self.conf_project_type
placeholders = {}
response = await self.hass.async_add_executor_job(
if user_input is not None:
response, data = await self.hass.async_add_executor_job(
self._try_login, user_input
)
if response.get("success", False):
_LOGGER.debug("TuyaConfigFlow.async_step_user login success")
if response.get(TUYA_RESPONSE_SUCCESS, False):
if endpoint := response.get(TUYA_RESPONSE_RESULT, {}).get(
TUYA_RESPONSE_PLATFROM_URL
):
data[CONF_ENDPOINT] = endpoint
data[CONF_PROJECT_TYPE] = data[CONF_PROJECT_TYPE].value
return self.async_create_entry(
title=user_input[CONF_USERNAME],
data=user_input,
data=data,
)
errors["base"] = RESULT_AUTH_FAILED
errors["base"] = "login_error"
placeholders = {
TUYA_RESPONSE_CODE: response.get(TUYA_RESPONSE_CODE),
TUYA_RESPONSE_MSG: response.get(TUYA_RESPONSE_MSG),
}
if ProjectType(self.conf_project_type) == ProjectType.SMART_HOME:
return self.async_show_form(
step_id="login", data_schema=DATA_SCHEMA_SMART_HOME, errors=errors
)
def _schema_default(key: str) -> str | UNDEFINED:
if not user_input:
return UNDEFINED
return user_input[key]
return self.async_show_form(
step_id="login",
data_schema=DATA_SCHEMA_INDUSTRY_SOLUTIONS,
step_id="user",
data_schema=vol.Schema(
{
vol.Required(
CONF_REGION, default=_schema_default(CONF_REGION)
): vol.In(TUYA_REGIONS.keys()),
vol.Required(
CONF_ACCESS_ID, default=_schema_default(CONF_ACCESS_ID)
): str,
vol.Required(
CONF_ACCESS_SECRET, default=_schema_default(CONF_ACCESS_SECRET)
): str,
vol.Required(
CONF_USERNAME, default=_schema_default(CONF_USERNAME)
): str,
vol.Required(
CONF_PASSWORD, default=_schema_default(CONF_PASSWORD)
): str,
}
),
errors=errors,
description_placeholders=placeholders,
)

View File

@ -9,6 +9,7 @@ CONF_ACCESS_ID = "access_id"
CONF_ACCESS_SECRET = "access_secret"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
CONF_REGION = "region"
CONF_COUNTRY_CODE = "country_code"
CONF_APP_TYPE = "tuya_app_type"
@ -19,19 +20,24 @@ TUYA_MQTT_LISTENER = "tuya_mqtt_listener"
TUYA_HA_TUYA_MAP = "tuya_ha_tuya_map"
TUYA_HA_DEVICES = "tuya_ha_devices"
TUYA_RESPONSE_CODE = "code"
TUYA_RESPONSE_RESULT = "result"
TUYA_RESPONSE_MSG = "msg"
TUYA_RESPONSE_SUCCESS = "success"
TUYA_RESPONSE_PLATFROM_URL = "platform_url"
TUYA_HA_SIGNAL_UPDATE_ENTITY = "tuya_entry_update"
TUYA_ENDPOINT = {
"https://openapi.tuyaus.com": "America",
"https://openapi.tuyacn.com": "China",
"https://openapi.tuyaeu.com": "Europe",
"https://openapi.tuyain.com": "India",
"https://openapi-ueaz.tuyaus.com": "EasternAmerica",
"https://openapi-weaz.tuyaeu.com": "WesternEurope",
TUYA_SMART_APP = "tuyaSmart"
SMARTLIFE_APP = "smartlife"
TUYA_REGIONS = {
"America": "https://openapi.tuyaus.com",
"China": "https://openapi.tuyacn.com",
"Eastern America": "https://openapi-ueaz.tuyaus.com",
"Europe": "https://openapi.tuyaeu.com",
"India": "https://openapi.tuyain.com",
"Western Europe": "https://openapi-weaz.tuyaeu.com",
}
TUYA_PROJECT_TYPE = {1: "Custom Development", 0: "Smart Home PaaS"}
TUYA_APP_TYPE = {"tuyaSmart": "TuyaSmart", "smartlife": "Smart Life"}
PLATFORMS = ["climate", "fan", "light", "scene", "switch"]

View File

@ -1,29 +1,20 @@
{
"config": {
"flow_title": "Tuya configuration",
"step": {
"user":{
"title":"Tuya Integration",
"data":{
"tuya_project_type": "Tuya cloud project type"
}
},
"login": {
"title": "Tuya",
"description": "Enter your Tuya credential",
"user": {
"description": "Enter your Tuya credentials",
"data": {
"endpoint": "Availability Zone",
"access_id": "Access ID",
"access_secret": "Access Secret",
"tuya_app_type": "Mobile App",
"country_code": "Country Code",
"region": "Region",
"access_id": "Tuya IoT Access ID",
"access_secret": "Tuya IoT Access Secret",
"username": "Account",
"password": "Password"
"password": "[%key:common::config_flow::data::password%]"
}
}
},
"error": {
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"login_error": "Login error ({code}): {msg}"
}
}
}

View File

@ -1,78 +1,19 @@
{
"config": {
"abort": {
"cannot_connect": "Failed to connect",
"invalid_auth": "Invalid authentication",
"single_instance_allowed": "Already configured. Only a single configuration possible."
},
"error": {
"invalid_auth": "Invalid authentication"
"invalid_auth": "Invalid authentication",
"login_error": "Login error ({code}): {msg}"
},
"flow_title": "Tuya configuration",
"step": {
"login": {
"data": {
"access_id": "Access ID",
"access_secret": "Access Secret",
"country_code": "Country Code",
"endpoint": "Availability Zone",
"password": "Password",
"tuya_app_type": "Mobile App",
"username": "Account"
},
"description": "Enter your Tuya credential",
"title": "Tuya"
},
"user": {
"data": {
"country_code": "Your account country code (e.g., 1 for USA or 86 for China)",
"access_id": "Tuya IoT Access ID",
"access_secret": "Tuya IoT Access Secret",
"password": "Password",
"platform": "The app where your account is registered",
"tuya_project_type": "Tuya cloud project type",
"username": "Username"
"region": "Region",
"username": "Account"
},
"description": "Enter your Tuya credentials.",
"title": "Tuya Integration"
}
}
},
"options": {
"abort": {
"cannot_connect": "Failed to connect"
},
"error": {
"dev_multi_type": "Multiple selected devices to configure must be of the same type",
"dev_not_config": "Device type not configurable",
"dev_not_found": "Device not found"
},
"step": {
"device": {
"data": {
"brightness_range_mode": "Brightness range used by device",
"curr_temp_divider": "Current Temperature value divider (0 = use default)",
"max_kelvin": "Max color temperature supported in kelvin",
"max_temp": "Max target temperature (use min and max = 0 for default)",
"min_kelvin": "Min color temperature supported in kelvin",
"min_temp": "Min target temperature (use min and max = 0 for default)",
"set_temp_divided": "Use divided Temperature value for set temperature command",
"support_color": "Force color support",
"temp_divider": "Temperature values divider (0 = use default)",
"temp_step_override": "Target Temperature step",
"tuya_max_coltemp": "Max color temperature reported by device",
"unit_of_measurement": "Temperature unit used by device"
},
"description": "Configure options to adjust displayed information for {device_type} device `{device_name}`",
"title": "Configure Tuya Device"
},
"init": {
"data": {
"discovery_interval": "Discovery device polling interval in seconds",
"list_devices": "Select the devices to configure or leave empty to save configuration",
"query_device": "Select device that will use query method for faster status update",
"query_interval": "Query device polling interval in seconds"
},
"description": "Do not set pollings interval values too low or the calls will fail generating error message in the log",
"title": "Configure Tuya Options"
"description": "Enter your Tuya credentials"
}
}
}

View File

@ -1,93 +1,84 @@
"""Tests for the Tuya config flow."""
from unittest.mock import MagicMock, Mock, patch
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock, patch
import pytest
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.tuya.config_flow import RESULT_AUTH_FAILED
from homeassistant.components.tuya.const import (
CONF_ACCESS_ID,
CONF_ACCESS_SECRET,
CONF_APP_TYPE,
CONF_COUNTRY_CODE,
CONF_ENDPOINT,
CONF_PASSWORD,
CONF_PROJECT_TYPE,
CONF_REGION,
CONF_USERNAME,
DOMAIN,
SMARTLIFE_APP,
TUYA_REGIONS,
TUYA_SMART_APP,
)
from homeassistant.core import HomeAssistant
MOCK_SMART_HOME_PROJECT_TYPE = 0
MOCK_INDUSTRY_PROJECT_TYPE = 1
MOCK_REGION = "Europe"
MOCK_ACCESS_ID = "myAccessId"
MOCK_ACCESS_SECRET = "myAccessSecret"
MOCK_USERNAME = "myUsername"
MOCK_PASSWORD = "myPassword"
MOCK_COUNTRY_CODE_BASE = "86"
MOCK_COUNTRY_CODE_OTHER = "1"
MOCK_APP_TYPE = "smartlife"
MOCK_ENDPOINT = "https://openapi-ueaz.tuyaus.com"
TUYA_SMART_HOME_PROJECT_DATA = {
CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE,
}
TUYA_INDUSTRY_PROJECT_DATA = {
CONF_PROJECT_TYPE: MOCK_INDUSTRY_PROJECT_TYPE,
}
TUYA_INPUT_INDUSTRY_DATA = {
CONF_ENDPOINT: MOCK_ENDPOINT,
TUYA_INPUT_DATA = {
CONF_REGION: MOCK_REGION,
CONF_ACCESS_ID: MOCK_ACCESS_ID,
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
}
TUYA_IMPORT_SMART_HOME_DATA_BASE = {
CONF_ACCESS_ID: MOCK_ACCESS_ID,
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE_BASE,
CONF_APP_TYPE: MOCK_APP_TYPE,
}
TUYA_IMPORT_SMART_HOME_DATA_OTHER = {
CONF_ACCESS_ID: MOCK_ACCESS_ID,
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
CONF_COUNTRY_CODE: MOCK_COUNTRY_CODE_OTHER,
CONF_APP_TYPE: MOCK_APP_TYPE,
}
TUYA_IMPORT_INDUSTRY_DATA = {
CONF_PROJECT_TYPE: MOCK_SMART_HOME_PROJECT_TYPE,
CONF_ENDPOINT: MOCK_ENDPOINT,
CONF_ACCESS_ID: MOCK_ACCESS_ID,
CONF_ACCESS_SECRET: MOCK_ACCESS_SECRET,
CONF_USERNAME: MOCK_USERNAME,
CONF_PASSWORD: MOCK_PASSWORD,
RESPONSE_SUCCESS = {
"success": True,
"code": 1024,
"result": {"platform_url": MOCK_ENDPOINT},
}
RESPONSE_ERROR = {"success": False, "code": 123, "msg": "Error"}
@pytest.fixture(name="tuya")
def tuya_fixture() -> Mock:
def tuya_fixture() -> MagicMock:
"""Patch libraries."""
with patch("homeassistant.components.tuya.config_flow.TuyaOpenAPI") as tuya:
yield tuya
@pytest.fixture(name="tuya_setup", autouse=True)
def tuya_setup_fixture():
def tuya_setup_fixture() -> None:
"""Mock tuya entry setup."""
with patch("homeassistant.components.tuya.async_setup_entry", return_value=True):
yield
async def test_industry_user(hass, tuya):
"""Test industry user config."""
@pytest.mark.parametrize(
"app_type,side_effects, project_type",
[
("", [RESPONSE_SUCCESS], 1),
(TUYA_SMART_APP, [RESPONSE_ERROR, RESPONSE_SUCCESS], 0),
(SMARTLIFE_APP, [RESPONSE_ERROR, RESPONSE_ERROR, RESPONSE_SUCCESS], 0),
],
)
async def test_user_flow(
hass: HomeAssistant,
tuya: MagicMock,
app_type: str,
side_effects: list[dict[str, Any]],
project_type: int,
):
"""Test user flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
@ -95,17 +86,9 @@ async def test_industry_user(hass, tuya):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
tuya().login = MagicMock(side_effect=side_effects)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_INPUT_INDUSTRY_DATA
result["flow_id"], user_input=TUYA_INPUT_DATA
)
await hass.async_block_till_done()
@ -115,90 +98,10 @@ async def test_industry_user(hass, tuya):
assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
assert result["data"][CONF_USERNAME] == MOCK_USERNAME
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
assert not result["result"].unique_id
async def test_smart_home_user_base(hass, tuya):
"""Test smart home user config base."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA_BASE
)
await hass.async_block_till_done()
assert result["errors"]["base"] == RESULT_AUTH_FAILED
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA_BASE
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == MOCK_USERNAME
assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID
assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
assert result["data"][CONF_USERNAME] == MOCK_USERNAME
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
assert result["data"][CONF_COUNTRY_CODE] == MOCK_COUNTRY_CODE_BASE
assert result["data"][CONF_APP_TYPE] == MOCK_APP_TYPE
assert not result["result"].unique_id
async def test_smart_home_user_other(hass, tuya):
"""Test smart home user config other."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_SMART_HOME_PROJECT_DATA
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA_OTHER
)
await hass.async_block_till_done()
assert result["errors"]["base"] == RESULT_AUTH_FAILED
tuya().login = MagicMock(return_value={"success": True, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_IMPORT_SMART_HOME_DATA_OTHER
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result["title"] == MOCK_USERNAME
assert result["data"][CONF_ACCESS_ID] == MOCK_ACCESS_ID
assert result["data"][CONF_ACCESS_SECRET] == MOCK_ACCESS_SECRET
assert result["data"][CONF_USERNAME] == MOCK_USERNAME
assert result["data"][CONF_PASSWORD] == MOCK_PASSWORD
assert result["data"][CONF_COUNTRY_CODE] == MOCK_COUNTRY_CODE_OTHER
assert result["data"][CONF_APP_TYPE] == MOCK_APP_TYPE
assert result["data"][CONF_ENDPOINT] == MOCK_ENDPOINT
assert result["data"][CONF_ENDPOINT] != TUYA_REGIONS[TUYA_INPUT_DATA[CONF_REGION]]
assert result["data"][CONF_APP_TYPE] == app_type
assert result["data"][CONF_PROJECT_TYPE] == project_type
assert not result["result"].unique_id
@ -212,18 +115,12 @@ async def test_error_on_invalid_credentials(hass, tuya):
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
tuya().login = MagicMock(return_value=RESPONSE_ERROR)
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_INDUSTRY_PROJECT_DATA
result["flow_id"], user_input=TUYA_INPUT_DATA
)
await hass.async_block_till_done()
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "login"
tuya().login = MagicMock(return_value={"success": False, "errorCode": 1024})
result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input=TUYA_INPUT_INDUSTRY_DATA
)
await hass.async_block_till_done()
assert result["errors"]["base"] == RESULT_AUTH_FAILED
assert result["errors"]["base"] == "login_error"
assert result["description_placeholders"]["code"] == RESPONSE_ERROR["code"]
assert result["description_placeholders"]["msg"] == RESPONSE_ERROR["msg"]