Add connectivity checks to renault config flow (#131251)

* Add connectivity checks to renault config flow

* Parametrize

* Sort

* merge
This commit is contained in:
epenet 2024-11-22 12:33:04 +01:00 committed by GitHub
parent 849ebd1435
commit 2da73ea068
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 27 deletions

View File

@ -5,7 +5,9 @@ from __future__ import annotations
from collections.abc import Mapping
from typing import Any
import aiohttp
from renault_api.const import AVAILABLE_LOCALES
from renault_api.gigya.exceptions import GigyaException
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
@ -27,12 +29,11 @@ REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
class RenaultFlowHandler(ConfigFlow, domain=DOMAIN):
"""Handle a Renault config flow."""
VERSION = 1
renault_hub: RenaultHub
def __init__(self) -> None:
"""Initialize the Renault config flow."""
self.renault_config: dict[str, Any] = {}
self.renault_hub: RenaultHub | None = None
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@ -41,24 +42,28 @@ class RenaultFlowHandler(ConfigFlow, domain=DOMAIN):
Ask the user for API keys.
"""
errors: dict[str, str] = {}
if user_input:
locale = user_input[CONF_LOCALE]
self.renault_config.update(user_input)
self.renault_config.update(AVAILABLE_LOCALES[locale])
self.renault_hub = RenaultHub(self.hass, locale)
if not await self.renault_hub.attempt_login(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
):
return self._show_user_form({"base": "invalid_credentials"})
return await self.async_step_kamereon()
return self._show_user_form()
def _show_user_form(self, errors: dict[str, Any] | None = None) -> ConfigFlowResult:
"""Show the API keys form."""
try:
login_success = await self.renault_hub.attempt_login(
user_input[CONF_USERNAME], user_input[CONF_PASSWORD]
)
except (aiohttp.ClientConnectionError, GigyaException):
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
errors["base"] = "unknown"
else:
if login_success:
return await self.async_step_kamereon()
errors["base"] = "invalid_credentials"
return self.async_show_form(
step_id="user",
data_schema=USER_SCHEMA,
errors=errors or {},
errors=errors,
)
async def async_step_kamereon(
@ -74,18 +79,12 @@ class RenaultFlowHandler(ConfigFlow, domain=DOMAIN):
title=user_input[CONF_KAMEREON_ACCOUNT_ID], data=self.renault_config
)
assert self.renault_hub
accounts = await self.renault_hub.get_account_ids()
if len(accounts) == 0:
return self.async_abort(reason="kamereon_no_account")
if len(accounts) == 1:
await self.async_set_unique_id(accounts[0])
self._abort_if_unique_id_configured()
self.renault_config[CONF_KAMEREON_ACCOUNT_ID] = accounts[0]
return self.async_create_entry(
title=self.renault_config[CONF_KAMEREON_ACCOUNT_ID],
data=self.renault_config,
return await self.async_step_kamereon(
user_input={CONF_KAMEREON_ACCOUNT_ID: accounts[0]}
)
return self.async_show_form(
@ -122,6 +121,6 @@ class RenaultFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors or {},
errors=errors,
description_placeholders={CONF_USERNAME: reauth_entry.data[CONF_USERNAME]},
)

View File

@ -6,7 +6,9 @@
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {
"invalid_credentials": "[%key:common::config_flow::error::invalid_auth%]"
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_credentials": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"kamereon": {

View File

@ -2,6 +2,7 @@
from unittest.mock import AsyncMock, PropertyMock, patch
import aiohttp
import pytest
from renault_api.gigya.exceptions import InvalidCredentialsException
from renault_api.kamereon import schemas
@ -23,20 +24,35 @@ from tests.common import MockConfigEntry, load_fixture
pytestmark = pytest.mark.usefixtures("mock_setup_entry")
@pytest.mark.parametrize(
("exception", "error"),
[
(Exception, "unknown"),
(aiohttp.ClientConnectionError, "cannot_connect"),
(
InvalidCredentialsException(403042, "invalid loginID or password"),
"invalid_credentials",
),
],
)
async def test_config_flow_single_account(
hass: HomeAssistant, mock_setup_entry: AsyncMock
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
exception: Exception | type[Exception],
error: str,
) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
assert result["step_id"] == "user"
assert not result["errors"]
# Failed credentials
# Raise error
with patch(
"renault_api.renault_session.RenaultSession.login",
side_effect=InvalidCredentialsException(403042, "invalid loginID or password"),
side_effect=exception,
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
@ -48,7 +64,8 @@ async def test_config_flow_single_account(
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": "invalid_credentials"}
assert result["step_id"] == "user"
assert result["errors"] == {"base": error}
renault_account = AsyncMock()
type(renault_account).account_id = PropertyMock(return_value="account_id_1")