Melcloud add reconfigure flow (#115999)

This commit is contained in:
Erwin Douna
2024-06-22 11:25:42 +02:00
committed by GitHub
parent d9e26077c6
commit 6e15c06aa9
5 changed files with 213 additions and 4 deletions

View File

@ -841,6 +841,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/media_source/ @hunterjm
/tests/components/media_source/ @hunterjm
/homeassistant/components/mediaroom/ @dgomes
/homeassistant/components/melcloud/ @erwindouna
/tests/components/melcloud/ @erwindouna
/homeassistant/components/melissa/ @kennedyshead
/tests/components/melissa/ @kennedyshead
/homeassistant/components/melnor/ @vanstinator

View File

@ -25,7 +25,6 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
VERSION = 1
entry: ConfigEntry | None = None
async def _create_entry(self, username: str, token: str) -> ConfigFlowResult:
@ -148,3 +147,66 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
errors["base"] = "cannot_connect"
return acquired_token, errors
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
return await self.async_step_reconfigure_confirm()
async def async_step_reconfigure_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a reconfiguration flow initialized by the user."""
errors: dict[str, str] = {}
acquired_token = None
assert self.entry
if user_input is not None:
user_input[CONF_USERNAME] = self.entry.data[CONF_USERNAME]
try:
async with asyncio.timeout(10):
acquired_token = await pymelcloud.login(
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
async_get_clientsession(self.hass),
)
except (ClientResponseError, AttributeError) as err:
if (
isinstance(err, ClientResponseError)
and err.status
in (
HTTPStatus.UNAUTHORIZED,
HTTPStatus.FORBIDDEN,
)
or isinstance(err, AttributeError)
and err.name == "get"
):
errors["base"] = "invalid_auth"
else:
errors["base"] = "cannot_connect"
except (
TimeoutError,
ClientError,
):
errors["base"] = "cannot_connect"
if not errors:
user_input[CONF_TOKEN] = acquired_token
return self.async_update_reload_and_abort(
self.entry,
data={**self.entry.data, **user_input},
reason="reconfigure_successful",
)
return self.async_show_form(
step_id="reconfigure_confirm",
data_schema=vol.Schema(
{
vol.Required(CONF_PASSWORD): str,
}
),
errors=errors,
description_placeholders={CONF_USERNAME: self.entry.data[CONF_USERNAME]},
)

View File

@ -1,7 +1,7 @@
{
"domain": "melcloud",
"name": "MELCloud",
"codeowners": [],
"codeowners": ["@erwindouna"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/melcloud",
"iot_class": "cloud_polling",

View File

@ -16,6 +16,16 @@
"username": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]"
}
},
"reconfigure_confirm": {
"title": "Reconfigure your MelCloud",
"description": "Reconfigure the entry to obtain a new token, for your account: `{username}`.",
"data": {
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"password": "Enter the (new) password for MelCloud."
}
}
},
"error": {
@ -25,7 +35,8 @@
},
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"already_configured": "MELCloud integration already configured for this email. Access token has been refreshed."
"already_configured": "MELCloud integration already configured for this email. Access token has been refreshed.",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
}
},
"services": {

View File

@ -9,7 +9,8 @@ import pytest
from homeassistant import config_entries
from homeassistant.components.melcloud.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_RECONFIGURE
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
@ -305,3 +306,136 @@ async def test_client_errors_reauthentication(
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reauth_successful"
@pytest.mark.parametrize(
("error", "reason"),
[
(HTTPStatus.UNAUTHORIZED, "invalid_auth"),
(HTTPStatus.FORBIDDEN, "invalid_auth"),
(HTTPStatus.INTERNAL_SERVER_ERROR, "cannot_connect"),
],
)
async def test_reconfigure_flow(
hass: HomeAssistant, mock_login, mock_request_info, error, reason
) -> None:
"""Test re-configuration flow."""
mock_login.side_effect = ClientResponseError(mock_request_info(), (), status=error)
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-email@test-domain.com", "token": "test-original-token"},
unique_id="test-email@test-domain.com",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_RECONFIGURE,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data=mock_entry.data,
)
assert result["type"] is FlowResultType.FORM
with patch(
"homeassistant.components.melcloud.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "test-password"},
)
await hass.async_block_till_done()
assert result["errors"]["base"] == reason
assert result["type"] is FlowResultType.FORM
mock_login.side_effect = None
with patch(
"homeassistant.components.melcloud.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "test-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
assert entry
assert entry.title == "Mock Title"
assert entry.data == {
"username": "test-email@test-domain.com",
"token": "test-token",
"password": "test-password",
}
@pytest.mark.parametrize(
("error", "reason"),
[
(TimeoutError(), "cannot_connect"),
(AttributeError(name="get"), "invalid_auth"),
],
)
async def test_form_errors_reconfigure(
hass: HomeAssistant, mock_login, error, reason
) -> None:
"""Test we handle cannot connect error."""
mock_login.side_effect = error
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-email@test-domain.com", "token": "test-original-token"},
unique_id="test-email@test-domain.com",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": SOURCE_RECONFIGURE,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data=mock_entry.data,
)
with patch(
"homeassistant.components.melcloud.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "test-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.FORM
assert result["errors"]["base"] == reason
mock_login.side_effect = None
with patch(
"homeassistant.components.melcloud.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "test-password"},
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
entry = hass.config_entries.async_get_entry(mock_entry.entry_id)
assert entry
assert entry.title == "Mock Title"
assert entry.data == {
"username": "test-email@test-domain.com",
"token": "test-token",
"password": "test-password",
}