Add Reauth config flow to honeywell (#86170)

This commit is contained in:
mkmer 2023-01-30 07:57:14 -05:00 committed by GitHub
parent 7e7a27f8b9
commit 7368c86ecb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 204 additions and 13 deletions

View File

@ -7,7 +7,7 @@ import AIOSomecomfort
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import ( from .const import (
@ -57,15 +57,12 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await client.login() await client.login()
await client.discover() await client.discover()
except AIOSomecomfort.AuthError as ex: except AIOSomecomfort.device.AuthError as ex:
raise ConfigEntryNotReady( raise ConfigEntryAuthFailed("Incorrect Password") from ex
"Failed to initialize the Honeywell client: "
"Check your configuration (username, password), "
) from ex
except ( except (
AIOSomecomfort.ConnectionError, AIOSomecomfort.device.ConnectionError,
AIOSomecomfort.ConnectionTimeout, AIOSomecomfort.device.ConnectionTimeout,
asyncio.TimeoutError, asyncio.TimeoutError,
) as ex: ) as ex:
raise ConfigEntryNotReady( raise ConfigEntryNotReady(

View File

@ -336,7 +336,6 @@ class HoneywellUSThermostat(ClimateEntity):
await self._device.set_setpoint_heat(self._heat_away_temp) await self._device.set_setpoint_heat(self._heat_away_temp)
except AIOSomecomfort.SomeComfortError: except AIOSomecomfort.SomeComfortError:
_LOGGER.error( _LOGGER.error(
"Temperature out of range. Mode: %s, Heat Temperature: %.1f, Cool Temperature: %.1f", "Temperature out of range. Mode: %s, Heat Temperature: %.1f, Cool Temperature: %.1f",
mode, mode,

View File

@ -2,6 +2,8 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Mapping
from typing import Any
import AIOSomecomfort import AIOSomecomfort
import voluptuous as vol import voluptuous as vol
@ -20,11 +22,67 @@ from .const import (
DOMAIN, DOMAIN,
) )
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class HoneywellConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a honeywell config flow.""" """Handle a honeywell config flow."""
VERSION = 1 VERSION = 1
entry: config_entries.ConfigEntry | None
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
"""Handle re-authentication with Honeywell."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
"""Confirm re-authentication with Honeywell."""
errors: dict[str, str] = {}
if user_input:
assert self.entry is not None
password = user_input[CONF_PASSWORD]
data = {
CONF_USERNAME: self.entry.data[CONF_USERNAME],
CONF_PASSWORD: password,
}
try:
await self.is_valid(
username=data[CONF_USERNAME], password=data[CONF_PASSWORD]
)
except AIOSomecomfort.AuthError:
errors["base"] = "invalid_auth"
except (
AIOSomecomfort.ConnectionError,
AIOSomecomfort.ConnectionTimeout,
asyncio.TimeoutError,
):
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
data={
**self.entry.data,
CONF_PASSWORD: password,
},
)
await self.hass.config_entries.async_reload(self.entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors,
)
async def async_step_user(self, user_input=None) -> FlowResult: async def async_step_user(self, user_input=None) -> FlowResult:
"""Create config entry. Show the setup form to the user.""" """Create config entry. Show the setup form to the user."""

View File

@ -1,7 +1,9 @@
"""Tests for honeywell config flow.""" """Tests for honeywell config flow."""
import asyncio
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import AIOSomecomfort import AIOSomecomfort
import pytest
from homeassistant import data_entry_flow from homeassistant import data_entry_flow
from homeassistant.components.honeywell.const import ( from homeassistant.components.honeywell.const import (
@ -9,8 +11,10 @@ from homeassistant.components.honeywell.const import (
CONF_HEAT_AWAY_TEMPERATURE, CONF_HEAT_AWAY_TEMPERATURE,
DOMAIN, DOMAIN,
) )
from homeassistant.config_entries import SOURCE_USER, ConfigEntryState from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER, ConfigEntryState
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -35,8 +39,7 @@ async def test_show_authenticate_form(hass: HomeAssistant) -> None:
async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None: async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None:
"""Test that an error message is shown on connection fail.""" """Test that an error message is shown on connection fail."""
client.login.side_effect = AIOSomecomfort.ConnectionError client.login.side_effect = AIOSomecomfort.device.ConnectionError
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
) )
@ -45,7 +48,7 @@ async def test_connection_error(hass: HomeAssistant, client: MagicMock) -> None:
async def test_auth_error(hass: HomeAssistant, client: MagicMock) -> None: async def test_auth_error(hass: HomeAssistant, client: MagicMock) -> None:
"""Test that an error message is shown on login fail.""" """Test that an error message is shown on login fail."""
client.login.side_effect = AIOSomecomfort.AuthError client.login.side_effect = AIOSomecomfort.device.AuthError
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG DOMAIN, context={"source": SOURCE_USER}, data=FAKE_CONFIG
@ -116,3 +119,137 @@ async def test_create_option_entry(
CONF_COOL_AWAY_TEMPERATURE: 1, CONF_COOL_AWAY_TEMPERATURE: 1,
CONF_HEAT_AWAY_TEMPERATURE: 2, CONF_HEAT_AWAY_TEMPERATURE: 2,
} }
async def test_reauth_flow(hass: HomeAssistant) -> None:
"""Test a successful reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"
assert mock_entry.data == {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "new-password",
}
async def test_reauth_flow_auth_error(hass: HomeAssistant, client: MagicMock) -> None:
"""Test an authorization error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
client.login.side_effect = AIOSomecomfort.device.AuthError
with patch(
"homeassistant.components.honeywell.async_setup_entry",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
@pytest.mark.parametrize(
"error",
[
AIOSomecomfort.device.ConnectionError,
AIOSomecomfort.device.ConnectionTimeout,
asyncio.TimeoutError,
],
)
async def test_reauth_flow_connnection_error(
hass: HomeAssistant, client: MagicMock, error
) -> None:
"""Test a connection error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
client.login.side_effect = error
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] == FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}