mirror of
https://github.com/home-assistant/core.git
synced 2025-05-02 21:19:16 +00:00
Add Totalconnect config flow (#32126)
* Bump skybellpy to 0.4.0 * Bump skybellpy to 0.4.0 in requirements_all.txt * Added extra states for STATE_ALARM_TRIGGERED to allow users to know if it is a burglar or fire or carbon monoxide so automations can take appropriate actions. Updated TotalConnect component to handle these new states. * Fix const import * Fix const import * Fix const imports * Bump total-connect-client to 0.26. * Catch details of alarm trigger in state attributes. Also bumps total_connect_client to 0.27. * Change state_attributes() to device_state_attributes() * Move totalconnect component toward being a multi-platform integration. Bump total_connect_client to 0.28. * add missing total-connect alarm state mappings * Made recommended changes of MartinHjelmare at https://github.com/home-assistant/home-assistant/pull/24427 * Update __init__.py * Updates per MartinHjelmare comments * flake8/pydocstyle fixes * removed . at end of log message * added blank line between logging and voluptuous * more fixes * Adding totalconnect zones as HA binary_sensors * fix manifest.json * flake8/pydocstyle fixes. Added codeowner. * Update formatting per @springstan guidance. * Fixed pylint * Add zone ID to log message for easier troubleshooting * Account for bypassed zones in update() * More status handling fixes. * Fixed flake8 error * Another attempt at black/isort fixes. * Bump total-connect-client to 0.50. Simplify code using new functions in total-connect-client package instead of importing constants. Run black and isort. * Fix manifest file * Another manifest fix * one more manifest fix * more manifest changes. * sync up * fix indent * one more pylint fix * Hopefully the last pylint fix * make variable names understandable * create and fill dict in one step * Fix name and attributes * rename to logical variable in alarm_control_panel * Remove location_name from alarm_control_panel attributes since it is already the name of the alarm. * Multiple fixes to improve code per @springstan suggestions * Update homeassistant/components/totalconnect/binary_sensor.py Co-Authored-By: springstan <46536646+springstan@users.noreply.github.com> * Multiple changes per @MartinHjelmare review * simplify alarm adding * Fix binary_sensor.py is_on * Move DOMAIN to .const in line with examples. * Move to async_setup * Simplify code using new features of total-connect-client 0.51 * First crack at config flow for totalconnect * bump totalconnect to 0.52 * use client.is_logged_in() to avoid total-connect-client details. * updated generated/config_flow.py * use is_logged_in() * Hopefully final touches for config flow * Add tests for config flow * Updated requirements for test * Fixes to test_config_flow * Removed leftover comments and code * fix const.py flake8 error * Simplify text per @Kane610 https://github.com/home-assistant/home-assistant/pull/32126#pullrequestreview-364652949 * Remove .get() to speed things up since the required items should always be available. * Move CONF_USERNAME and CONF_PASSWORD into .const to eliminate extra I/O to import from homeassistant.const * Fix I/O async issues * Fix flake8 and black errors * Mock the I/O in tests. * Fix isort error * Empty commit to re-start azure pipelines (per discord) * bump total-connect-client to 0.53 * Update homeassistant/components/totalconnect/__init__.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Update homeassistant/components/totalconnect/config_flow.py Co-Authored-By: Paulus Schoutsen <paulus@home-assistant.io> * Fixes per @balloob comments * Fix imports * fix isort error * Fix async_unload_entry It still referenced CONF_USERNAME instead of entry.entity_id * Added async_setup so not breaking change. Fixed imports. * Update tests/components/totalconnect/test_config_flow.py Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * Remove TotalConnectSystem() per @MartinHjelmare suggestion * Moved from is_logged_in() to is_valid_credentials() The second is more accurate for what we are checking for, because is_logged_in() could return False due to connection error. * Fix import in test * remove commented code and decorator * Update tests/components/totalconnect/test_config_flow.py Co-Authored-By: Martin Hjelmare <marhje52@gmail.com> * fix test_config_flow.py * bump total-connect-client to 0.54 * remove un-needed import of mock_coro * bump to total-connect-client 0.54.1 * re-add CONFIG_SCHEMA * disable pylint on line 10 to avoid pylint bug Co-authored-by: springstan <46536646+springstan@users.noreply.github.com> Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io> Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
This commit is contained in:
parent
ab1eda7412
commit
c7ab5de07c
homeassistant
components/totalconnect
.translations
__init__.pyalarm_control_panel.pybinary_sensor.pyconfig_flow.pymanifest.jsonstrings.jsongenerated
tests/components/totalconnect
20
homeassistant/components/totalconnect/.translations/en.json
Normal file
20
homeassistant/components/totalconnect/.translations/en.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Account already configured"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"login": "Login error: please check your username & password"
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"data": {
|
||||||
|
"password": "Password",
|
||||||
|
"username": "Username"
|
||||||
|
},
|
||||||
|
"title": "Total Connect"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Total Connect"
|
||||||
|
}
|
||||||
|
}
|
@ -1,16 +1,20 @@
|
|||||||
"""The totalconnect component."""
|
"""The totalconnect component."""
|
||||||
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from total_connect_client import TotalConnectClient
|
from total_connect_client import TotalConnectClient
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
from homeassistant.helpers import discovery
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
|
||||||
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
DOMAIN = "totalconnect"
|
PLATFORMS = ["alarm_control_panel", "binary_sensor"]
|
||||||
|
|
||||||
CONFIG_SCHEMA = vol.Schema(
|
CONFIG_SCHEMA = vol.Schema(
|
||||||
{
|
{
|
||||||
@ -20,39 +24,61 @@ CONFIG_SCHEMA = vol.Schema(
|
|||||||
vol.Required(CONF_PASSWORD): cv.string,
|
vol.Required(CONF_PASSWORD): cv.string,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
}
|
||||||
extra=vol.ALLOW_EXTRA,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
TOTALCONNECT_PLATFORMS = ["alarm_control_panel", "binary_sensor"]
|
|
||||||
|
|
||||||
|
async def async_setup(hass: HomeAssistant, config: dict):
|
||||||
|
"""Set up by configuration file."""
|
||||||
|
if DOMAIN not in config:
|
||||||
|
return True
|
||||||
|
|
||||||
def setup(hass, config):
|
hass.async_create_task(
|
||||||
"""Set up TotalConnect component."""
|
hass.config_entries.flow.async_init(
|
||||||
conf = config[DOMAIN]
|
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN],
|
||||||
|
)
|
||||||
username = conf[CONF_USERNAME]
|
)
|
||||||
password = conf[CONF_PASSWORD]
|
|
||||||
|
|
||||||
client = TotalConnectClient.TotalConnectClient(username, password)
|
|
||||||
|
|
||||||
if client.token is False:
|
|
||||||
_LOGGER.error("TotalConnect authentication failed")
|
|
||||||
return False
|
|
||||||
|
|
||||||
hass.data[DOMAIN] = TotalConnectSystem(username, password, client)
|
|
||||||
|
|
||||||
for platform in TOTALCONNECT_PLATFORMS:
|
|
||||||
discovery.load_platform(hass, platform, DOMAIN, {}, config)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
class TotalConnectSystem:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""TotalConnect System class."""
|
"""Set up upon config entry in user interface."""
|
||||||
|
hass.data.setdefault(DOMAIN, {})
|
||||||
|
|
||||||
def __init__(self, username, password, client):
|
conf = entry.data
|
||||||
"""Initialize the TotalConnect system."""
|
username = conf[CONF_USERNAME]
|
||||||
self._username = username
|
password = conf[CONF_PASSWORD]
|
||||||
self._password = password
|
|
||||||
self.client = client
|
client = await hass.async_add_executor_job(
|
||||||
|
TotalConnectClient.TotalConnectClient, username, password
|
||||||
|
)
|
||||||
|
|
||||||
|
if not client.is_valid_credentials():
|
||||||
|
_LOGGER.error("TotalConnect authentication failed")
|
||||||
|
return False
|
||||||
|
|
||||||
|
hass.data[DOMAIN][entry.entry_id] = client
|
||||||
|
|
||||||
|
for component in PLATFORMS:
|
||||||
|
hass.async_create_task(
|
||||||
|
hass.config_entries.async_forward_entry_setup(entry, component)
|
||||||
|
)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass, entry: ConfigEntry):
|
||||||
|
"""Unload a config entry."""
|
||||||
|
unload_ok = all(
|
||||||
|
await asyncio.gather(
|
||||||
|
*[
|
||||||
|
hass.config_entries.async_forward_entry_unload(entry, platform)
|
||||||
|
for platform in PLATFORMS
|
||||||
|
]
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if unload_ok:
|
||||||
|
hass.data[DOMAIN].pop(entry.entry_id)
|
||||||
|
|
||||||
|
return unload_ok
|
||||||
|
@ -23,19 +23,17 @@ from .const import DOMAIN
|
|||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, entry, async_add_entities) -> None:
|
||||||
"""Set up an alarm control panel for a TotalConnect device."""
|
"""Set up TotalConnect alarm panels based on a config entry."""
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
alarms = []
|
alarms = []
|
||||||
|
|
||||||
client = hass.data[DOMAIN].client
|
client = hass.data[DOMAIN][entry.entry_id]
|
||||||
|
|
||||||
for location_id, location in client.locations.items():
|
for location_id, location in client.locations.items():
|
||||||
location_name = location.location_name
|
location_name = location.location_name
|
||||||
alarms.append(TotalConnectAlarm(location_name, location_id, client))
|
alarms.append(TotalConnectAlarm(location_name, location_id, client))
|
||||||
add_entities(alarms)
|
|
||||||
|
async_add_entities(alarms, True)
|
||||||
|
|
||||||
|
|
||||||
class TotalConnectAlarm(alarm.AlarmControlPanel):
|
class TotalConnectAlarm(alarm.AlarmControlPanel):
|
||||||
|
@ -8,24 +8,22 @@ from homeassistant.components.binary_sensor import (
|
|||||||
BinarySensorDevice,
|
BinarySensorDevice,
|
||||||
)
|
)
|
||||||
|
|
||||||
from . import DOMAIN as TOTALCONNECT_DOMAIN
|
from .const import DOMAIN
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
async def async_setup_entry(hass, entry, async_add_entities) -> None:
|
||||||
"""Set up a sensor for a TotalConnect device."""
|
"""Set up TotalConnect device sensors based on a config entry."""
|
||||||
if discovery_info is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
sensors = []
|
sensors = []
|
||||||
|
|
||||||
client_locations = hass.data[TOTALCONNECT_DOMAIN].client.locations
|
client_locations = hass.data[DOMAIN][entry.entry_id].locations
|
||||||
|
|
||||||
for location_id, location in client_locations.items():
|
for location_id, location in client_locations.items():
|
||||||
for zone_id, zone in location.zones.items():
|
for zone_id, zone in location.zones.items():
|
||||||
sensors.append(TotalConnectBinarySensor(zone_id, location_id, zone))
|
sensors.append(TotalConnectBinarySensor(zone_id, location_id, zone))
|
||||||
add_entities(sensors, True)
|
|
||||||
|
async_add_entities(sensors, True)
|
||||||
|
|
||||||
|
|
||||||
class TotalConnectBinarySensor(BinarySensorDevice):
|
class TotalConnectBinarySensor(BinarySensorDevice):
|
||||||
|
60
homeassistant/components/totalconnect/config_flow.py
Normal file
60
homeassistant/components/totalconnect/config_flow.py
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
"""Config flow for the Total Connect component."""
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from total_connect_client import TotalConnectClient
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant import config_entries
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from .const import DOMAIN # pylint: disable=unused-import
|
||||||
|
|
||||||
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class TotalConnectConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Total Connect config flow."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(self, user_input=None):
|
||||||
|
"""Handle a flow initiated by the user."""
|
||||||
|
errors = {}
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
# Validate user input
|
||||||
|
username = user_input[CONF_USERNAME]
|
||||||
|
password = user_input[CONF_PASSWORD]
|
||||||
|
|
||||||
|
await self.async_set_unique_id(username)
|
||||||
|
self._abort_if_unique_id_configured()
|
||||||
|
|
||||||
|
valid = await self.is_valid(username, password)
|
||||||
|
|
||||||
|
if valid:
|
||||||
|
# authentication success / valid
|
||||||
|
return self.async_create_entry(
|
||||||
|
title="Total Connect",
|
||||||
|
data={CONF_USERNAME: username, CONF_PASSWORD: password},
|
||||||
|
)
|
||||||
|
# authentication failed / invalid
|
||||||
|
errors["base"] = "login"
|
||||||
|
|
||||||
|
data_schema = vol.Schema(
|
||||||
|
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(
|
||||||
|
step_id="user", data_schema=data_schema, errors=errors
|
||||||
|
)
|
||||||
|
|
||||||
|
async def async_step_import(self, user_input):
|
||||||
|
"""Import a config entry."""
|
||||||
|
return await self.async_step_user(user_input)
|
||||||
|
|
||||||
|
async def is_valid(self, username="", password=""):
|
||||||
|
"""Return true if the given username and password are valid."""
|
||||||
|
client = await self.hass.async_add_executor_job(
|
||||||
|
TotalConnectClient.TotalConnectClient, username, password
|
||||||
|
)
|
||||||
|
return client.is_valid_credentials()
|
@ -3,5 +3,7 @@
|
|||||||
"name": "Honeywell Total Connect Alarm",
|
"name": "Honeywell Total Connect Alarm",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
|
"documentation": "https://www.home-assistant.io/integrations/totalconnect",
|
||||||
"requirements": ["total_connect_client==0.54.1"],
|
"requirements": ["total_connect_client==0.54.1"],
|
||||||
"codeowners": ["@austinmroczek"]
|
"dependencies": [],
|
||||||
|
"codeowners": ["@austinmroczek"],
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
20
homeassistant/components/totalconnect/strings.json
Normal file
20
homeassistant/components/totalconnect/strings.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"title": "Total Connect",
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"title": "Total Connect",
|
||||||
|
"data": {
|
||||||
|
"username": "Username",
|
||||||
|
"password": "Password"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"login": "Login error: please check your username & password"
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"already_configured": "Account already configured"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -117,6 +117,7 @@ FLOWS = [
|
|||||||
"tellduslive",
|
"tellduslive",
|
||||||
"tesla",
|
"tesla",
|
||||||
"toon",
|
"toon",
|
||||||
|
"totalconnect",
|
||||||
"tplink",
|
"tplink",
|
||||||
"traccar",
|
"traccar",
|
||||||
"tradfri",
|
"tradfri",
|
||||||
|
@ -761,6 +761,9 @@ teslajsonpy==0.6.0
|
|||||||
# homeassistant.components.toon
|
# homeassistant.components.toon
|
||||||
toonapilib==3.2.4
|
toonapilib==3.2.4
|
||||||
|
|
||||||
|
# homeassistant.components.totalconnect
|
||||||
|
total_connect_client==0.54.1
|
||||||
|
|
||||||
# homeassistant.components.transmission
|
# homeassistant.components.transmission
|
||||||
transmissionrpc==0.11
|
transmissionrpc==0.11
|
||||||
|
|
||||||
|
1
tests/components/totalconnect/__init__.py
Normal file
1
tests/components/totalconnect/__init__.py
Normal file
@ -0,0 +1 @@
|
|||||||
|
"""Tests for the totalconnect component."""
|
104
tests/components/totalconnect/test_config_flow.py
Normal file
104
tests/components/totalconnect/test_config_flow.py
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"""Tests for the iCloud config flow."""
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
from homeassistant import data_entry_flow
|
||||||
|
from homeassistant.components.totalconnect.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||||
|
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
USERNAME = "username@me.com"
|
||||||
|
PASSWORD = "password"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_user(hass):
|
||||||
|
"""Test user config."""
|
||||||
|
# no data provided so show the form
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["step_id"] == "user"
|
||||||
|
|
||||||
|
# now data is provided, so check if login is correct and create the entry
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
|
||||||
|
) as client_mock:
|
||||||
|
client_mock.return_value.is_valid_credentials.return_value = True
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import(hass):
|
||||||
|
"""Test import step with good username and password."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
|
||||||
|
) as client_mock:
|
||||||
|
client_mock.return_value.is_valid_credentials.return_value = True
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_IMPORT},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_abort_if_already_setup(hass):
|
||||||
|
"""Test abort if the account is already setup."""
|
||||||
|
MockConfigEntry(
|
||||||
|
domain=DOMAIN,
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
unique_id=USERNAME,
|
||||||
|
).add_to_hass(hass)
|
||||||
|
|
||||||
|
# Should fail, same USERNAME (import)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
|
||||||
|
) as client_mock:
|
||||||
|
client_mock.return_value.is_valid_credentials.return_value = True
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_IMPORT},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
# Should fail, same USERNAME (flow)
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
|
||||||
|
) as client_mock:
|
||||||
|
client_mock.return_value.is_valid_credentials.return_value = True
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_ABORT
|
||||||
|
assert result["reason"] == "already_configured"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_login_failed(hass):
|
||||||
|
"""Test when we have errors during login."""
|
||||||
|
with patch(
|
||||||
|
"homeassistant.components.totalconnect.config_flow.TotalConnectClient.TotalConnectClient"
|
||||||
|
) as client_mock:
|
||||||
|
client_mock.return_value.is_valid_credentials.return_value = False
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_USER},
|
||||||
|
data={CONF_USERNAME: USERNAME, CONF_PASSWORD: PASSWORD},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
|
||||||
|
assert result["errors"] == {"base": "login"}
|
Loading…
x
Reference in New Issue
Block a user