Add reauth config flow to Whirlpool (#82532)

* Add ReauthFlow

* Update homeassistant/components/whirlpool/config_flow.py

Darn it - thought I caught all of these.

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/whirlpool/config_flow.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/whirlpool/config_flow.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/whirlpool/config_flow.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/whirlpool/config_flow.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/whirlpool/config_flow.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
This commit is contained in:
mkmer 2022-11-23 22:40:33 -05:00 committed by GitHub
parent 33d391a110
commit ae07e2a9a8
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 194 additions and 13 deletions

View file

@ -10,7 +10,7 @@ from whirlpool.backendselector import BackendSelector, Brand, Region
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform from homeassistant.const import Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .const import DOMAIN from .const import DOMAIN
@ -32,7 +32,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
if not auth.is_access_token_valid(): if not auth.is_access_token_valid():
_LOGGER.error("Authentication failed") _LOGGER.error("Authentication failed")
return False raise ConfigEntryAuthFailed("Incorrect Password")
appliances_manager = AppliancesManager(backend_selector, auth) appliances_manager = AppliancesManager(backend_selector, auth)
if not await appliances_manager.fetch_appliances(): if not await appliances_manager.fetch_appliances():

View file

@ -2,7 +2,9 @@
from __future__ import annotations from __future__ import annotations
import asyncio import asyncio
from collections.abc import Mapping
import logging import logging
from typing import Any
import aiohttp import aiohttp
import voluptuous as vol import voluptuous as vol
@ -21,6 +23,8 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
{vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str} {vol.Required(CONF_USERNAME): str, vol.Required(CONF_PASSWORD): str}
) )
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
async def validate_input( async def validate_input(
hass: core.HomeAssistant, data: dict[str, str] hass: core.HomeAssistant, data: dict[str, str]
@ -46,6 +50,50 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Whirlpool Sixth Sense.""" """Handle a config flow for Whirlpool Sixth Sense."""
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 Whirlpool Sixth Sense."""
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 Whirlpool Sixth Sense."""
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 validate_input(self.hass, data)
except InvalidAuth:
errors["base"] = "invalid_auth"
except (CannotConnect, 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:
"""Handle the initial step.""" """Handle the initial step."""

View file

@ -3,9 +3,13 @@ import asyncio
from unittest.mock import patch from unittest.mock import patch
import aiohttp import aiohttp
from aiohttp.client_exceptions import ClientConnectionError
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.components.whirlpool.const import DOMAIN from homeassistant.components.whirlpool.const import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -56,8 +60,8 @@ async def test_form_invalid_auth(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"username": "test-username", CONF_USERNAME: "test-username",
"password": "test-password", CONF_PASSWORD: "test-password",
}, },
) )
assert result2["type"] == "form" assert result2["type"] == "form"
@ -76,8 +80,8 @@ async def test_form_cannot_connect(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"username": "test-username", CONF_USERNAME: "test-username",
"password": "test-password", CONF_PASSWORD: "test-password",
}, },
) )
assert result2["type"] == "form" assert result2["type"] == "form"
@ -96,8 +100,8 @@ async def test_form_auth_timeout(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"username": "test-username", CONF_USERNAME: "test-username",
"password": "test-password", CONF_PASSWORD: "test-password",
}, },
) )
assert result2["type"] == "form" assert result2["type"] == "form"
@ -116,8 +120,8 @@ async def test_form_generic_auth_exception(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"username": "test-username", CONF_USERNAME: "test-username",
"password": "test-password", CONF_PASSWORD: "test-password",
}, },
) )
assert result2["type"] == "form" assert result2["type"] == "form"
@ -128,7 +132,7 @@ async def test_form_already_configured(hass):
"""Test we handle cannot connect error.""" """Test we handle cannot connect error."""
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
domain=DOMAIN, domain=DOMAIN,
data={"username": "test-username", "password": "test-password"}, data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username", unique_id="test-username",
) )
mock_entry.add_to_hass(hass) mock_entry.add_to_hass(hass)
@ -147,11 +151,140 @@ async def test_form_already_configured(hass):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{ {
"username": "test-username", CONF_USERNAME: "test-username",
"password": "test-password", CONF_PASSWORD: "test-password",
}, },
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == "abort" assert result2["type"] == "abort"
assert result2["reason"] == "already_configured" assert result2["reason"] == "already_configured"
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)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={"username": "test-username", "password": "new-password"},
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.whirlpool.async_setup_entry",
return_value=True,
), patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch(
"homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid",
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) -> None:
"""Test an authorization error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-username", "password": "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={"username": "test-username", "password": "new-password"},
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.whirlpool.async_setup_entry",
return_value=True,
), patch("homeassistant.components.whirlpool.config_flow.Auth.do_auth"), patch(
"homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid",
return_value=False,
):
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"}
async def test_reauth_flow_connnection_error(hass: HomeAssistant) -> None:
"""Test a connection error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-username", "password": "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "new-password"},
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] == FlowResultType.FORM
assert result["errors"] == {}
with patch(
"homeassistant.components.whirlpool.async_setup_entry",
return_value=True,
), patch(
"homeassistant.components.whirlpool.config_flow.Auth.do_auth",
side_effect=ClientConnectionError,
), patch(
"homeassistant.components.whirlpool.config_flow.Auth.is_access_token_valid",
return_value=False,
):
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"}