Update mazda to use ConfigEntryAuthFailed (#49333)

This commit is contained in:
J. Nick Koston 2021-04-17 09:25:13 -10:00 committed by GitHub
parent 18cbf3cdb2
commit 46c28f349a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 198 additions and 139 deletions

View File

@ -13,10 +13,10 @@ from pymazda import (
MazdaTokenExpiredException, MazdaTokenExpiredException,
) )
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_REGION
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import aiohttp_client from homeassistant.helpers import aiohttp_client
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
CoordinatorEntity, CoordinatorEntity,
@ -49,15 +49,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
try: try:
await mazda_client.validate_credentials() await mazda_client.validate_credentials()
except MazdaAuthenticationException: except MazdaAuthenticationException as ex:
hass.async_create_task( raise ConfigEntryAuthFailed from ex
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
return False
except ( except (
MazdaException, MazdaException,
MazdaAccountLockedException, MazdaAccountLockedException,
@ -83,14 +76,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
return vehicles return vehicles
except MazdaAuthenticationException as ex: except MazdaAuthenticationException as ex:
hass.async_create_task( raise ConfigEntryAuthFailed("Not authenticated with Mazda API") from ex
hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_REAUTH},
data=entry.data,
)
)
raise UpdateFailed("Not authenticated with Mazda API") from ex
except Exception as ex: except Exception as ex:
_LOGGER.exception( _LOGGER.exception(
"Unknown error occurred during Mazda update request: %s", ex "Unknown error occurred during Mazda update request: %s", ex

View File

@ -32,12 +32,23 @@ class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1 VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL CONNECTION_CLASS = config_entries.CONN_CLASS_CLOUD_POLL
def __init__(self):
"""Start the mazda config flow."""
self._reauth_entry = None
self._email = None
self._region = None
async def async_step_user(self, user_input=None): async def async_step_user(self, user_input=None):
"""Handle the initial step.""" """Handle the initial step."""
errors = {} errors = {}
if user_input is not None: if user_input is not None:
await self.async_set_unique_id(user_input[CONF_EMAIL].lower()) self._email = user_input[CONF_EMAIL]
self._region = user_input[CONF_REGION]
unique_id = user_input[CONF_EMAIL].lower()
await self.async_set_unique_id(unique_id)
if not self._reauth_entry:
self._abort_if_unique_id_configured()
websession = aiohttp_client.async_get_clientsession(self.hass) websession = aiohttp_client.async_get_clientsession(self.hass)
mazda_client = MazdaAPI( mazda_client = MazdaAPI(
user_input[CONF_EMAIL], user_input[CONF_EMAIL],
@ -60,56 +71,38 @@ class MazdaConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"Unknown error occurred during Mazda login request: %s", ex "Unknown error occurred during Mazda login request: %s", ex
) )
else: else:
return self.async_create_entry( if not self._reauth_entry:
title=user_input[CONF_EMAIL], data=user_input return self.async_create_entry(
title=user_input[CONF_EMAIL], data=user_input
)
self.hass.config_entries.async_update_entry(
self._reauth_entry, data=user_input, unique_id=unique_id
) )
# Reload the config entry otherwise devices will remain unavailable
self.hass.async_create_task(
self.hass.config_entries.async_reload(self._reauth_entry.entry_id)
)
return self.async_abort(reason="reauth_successful")
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=DATA_SCHEMA, errors=errors step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_EMAIL, default=self._email): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(CONF_REGION, default=self._region): vol.In(
MAZDA_REGIONS
),
}
),
errors=errors,
) )
async def async_step_reauth(self, user_input=None): async def async_step_reauth(self, user_input=None):
"""Perform reauth if the user credentials have changed.""" """Perform reauth if the user credentials have changed."""
errors = {} self._reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
if user_input is not None:
try:
websession = aiohttp_client.async_get_clientsession(self.hass)
mazda_client = MazdaAPI(
user_input[CONF_EMAIL],
user_input[CONF_PASSWORD],
user_input[CONF_REGION],
websession,
)
await mazda_client.validate_credentials()
except MazdaAuthenticationException:
errors["base"] = "invalid_auth"
except MazdaAccountLockedException:
errors["base"] = "account_locked"
except aiohttp.ClientError:
errors["base"] = "cannot_connect"
except Exception as ex: # pylint: disable=broad-except
errors["base"] = "unknown"
_LOGGER.exception(
"Unknown error occurred during Mazda login request: %s", ex
)
else:
await self.async_set_unique_id(user_input[CONF_EMAIL].lower())
for entry in self._async_current_entries():
if entry.unique_id == self.unique_id:
self.hass.config_entries.async_update_entry(
entry, data=user_input
)
# Reload the config entry otherwise devices will remain unavailable
self.hass.async_create_task(
self.hass.config_entries.async_reload(entry.entry_id)
)
return self.async_abort(reason="reauth_successful")
errors["base"] = "unknown"
return self.async_show_form(
step_id="reauth", data_schema=DATA_SCHEMA, errors=errors
) )
self._email = user_input[CONF_EMAIL]
self._region = user_input[CONF_REGION]
return await self.async_step_user()

View File

@ -11,15 +11,6 @@
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"step": { "step": {
"reauth": {
"data": {
"email": "[%key:common::config_flow::data::email%]",
"password": "[%key:common::config_flow::data::password%]",
"region": "Region"
},
"description": "Authentication failed for Mazda Connected Services. Please enter your current credentials.",
"title": "Mazda Connected Services - Authentication Failed"
},
"user": { "user": {
"data": { "data": {
"email": "[%key:common::config_flow::data::email%]", "email": "[%key:common::config_flow::data::email%]",

View File

@ -11,15 +11,6 @@
"unknown": "Unexpected error" "unknown": "Unexpected error"
}, },
"step": { "step": {
"reauth": {
"data": {
"email": "Email",
"password": "Password",
"region": "Region"
},
"description": "Authentication failed for Mazda Connected Services. Please enter your current credentials.",
"title": "Mazda Connected Services - Authentication Failed"
},
"user": { "user": {
"data": { "data": {
"email": "Email", "email": "Email",

View File

@ -24,6 +24,11 @@ FIXTURE_USER_INPUT_REAUTH = {
CONF_PASSWORD: "password_fixed", CONF_PASSWORD: "password_fixed",
CONF_REGION: "MNAO", CONF_REGION: "MNAO",
} }
FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL = {
CONF_EMAIL: "example2@example.com",
CONF_PASSWORD: "password_fixed",
CONF_REGION: "MNAO",
}
async def test_form(hass): async def test_form(hass):
@ -54,6 +59,36 @@ async def test_form(hass):
assert len(mock_setup_entry.mock_calls) == 1 assert len(mock_setup_entry.mock_calls) == 1
async def test_account_already_exists(hass):
"""Test account already exists."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
assert result["errors"] == {}
with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
return_value=True,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
FIXTURE_USER_INPUT,
)
await hass.async_block_till_done()
assert result2["type"] == "abort"
assert result2["reason"] == "already_configured"
async def test_form_invalid_auth(hass: HomeAssistant) -> None: async def test_form_invalid_auth(hass: HomeAssistant) -> None:
"""Test we handle invalid auth.""" """Test we handle invalid auth."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -145,37 +180,40 @@ async def test_form_unknown_error(hass):
async def test_reauth_flow(hass: HomeAssistant) -> None: async def test_reauth_flow(hass: HomeAssistant) -> None:
"""Test reauth works.""" """Test reauth works."""
await setup.async_setup_component(hass, "persistent_notification", {}) await setup.async_setup_component(hass, "persistent_notification", {})
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
side_effect=MazdaAuthenticationException("Failed to authenticate"), side_effect=MazdaAuthenticationException("Failed to authenticate"),
), patch(
"homeassistant.components.mazda.async_setup_entry",
return_value=True,
): ):
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config.entry_id) await hass.config_entries.async_setup(mock_config.entry_id)
await hass.async_block_till_done() await hass.async_block_till_done()
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth" assert result["step_id"] == "user"
assert result["errors"] == {"base": "invalid_auth"} assert result["errors"] == {}
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
return_value=True, return_value=True,
): ), patch("homeassistant.components.mazda.async_setup_entry", return_value=True):
result2 = await hass.config_entries.flow.async_init( result2 = await hass.config_entries.flow.async_configure(
DOMAIN, result["flow_id"],
context={"source": "reauth", "unique_id": FIXTURE_USER_INPUT[CONF_EMAIL]}, FIXTURE_USER_INPUT_REAUTH,
data=FIXTURE_USER_INPUT_REAUTH,
) )
await hass.async_block_till_done() await hass.async_block_till_done()
@ -185,16 +223,28 @@ async def test_reauth_flow(hass: HomeAssistant) -> None:
async def test_reauth_authorization_error(hass: HomeAssistant) -> None: async def test_reauth_authorization_error(hass: HomeAssistant) -> None:
"""Test we show user form on authorization error.""" """Test we show user form on authorization error."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
side_effect=MazdaAuthenticationException("Failed to authenticate"), side_effect=MazdaAuthenticationException("Failed to authenticate"),
), patch(
"homeassistant.components.mazda.async_setup_entry",
return_value=True,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth" assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -203,22 +253,34 @@ async def test_reauth_authorization_error(hass: HomeAssistant) -> None:
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "reauth" assert result2["step_id"] == "user"
assert result2["errors"] == {"base": "invalid_auth"} assert result2["errors"] == {"base": "invalid_auth"}
async def test_reauth_account_locked(hass: HomeAssistant) -> None: async def test_reauth_account_locked(hass: HomeAssistant) -> None:
"""Test we show user form on account_locked error.""" """Test we show user form on account_locked error."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
side_effect=MazdaAccountLockedException("Account locked"), side_effect=MazdaAccountLockedException("Account locked"),
), patch(
"homeassistant.components.mazda.async_setup_entry",
return_value=True,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth" assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -227,22 +289,34 @@ async def test_reauth_account_locked(hass: HomeAssistant) -> None:
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "reauth" assert result2["step_id"] == "user"
assert result2["errors"] == {"base": "account_locked"} assert result2["errors"] == {"base": "account_locked"}
async def test_reauth_connection_error(hass: HomeAssistant) -> None: async def test_reauth_connection_error(hass: HomeAssistant) -> None:
"""Test we show user form on connection error.""" """Test we show user form on connection error."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
side_effect=aiohttp.ClientError, side_effect=aiohttp.ClientError,
), patch(
"homeassistant.components.mazda.async_setup_entry",
return_value=True,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth" assert result["step_id"] == "user"
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -251,50 +325,34 @@ async def test_reauth_connection_error(hass: HomeAssistant) -> None:
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "reauth" assert result2["step_id"] == "user"
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
async def test_reauth_unknown_error(hass: HomeAssistant) -> None: async def test_reauth_unknown_error(hass: HomeAssistant) -> None:
"""Test we show user form on unknown error.""" """Test we show user form on unknown error."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch( with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials", "homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
side_effect=Exception, side_effect=Exception,
): ), patch(
result = await hass.config_entries.flow.async_init( "homeassistant.components.mazda.async_setup_entry",
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth"
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
FIXTURE_USER_INPUT_REAUTH,
)
await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "reauth"
assert result2["errors"] == {"base": "unknown"}
async def test_reauth_unique_id_not_found(hass: HomeAssistant) -> None:
"""Test we show user form when unique id not found during reauth."""
with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
return_value=True, return_value=True,
): ):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": "reauth"}, data=FIXTURE_USER_INPUT DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
) )
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "reauth" assert result["step_id"] == "user"
# Change the unique_id of the flow in order to cause a mismatch
flows = hass.config_entries.flow.async_progress()
flows[0]["context"]["unique_id"] = "example2@example.com"
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -303,5 +361,45 @@ async def test_reauth_unique_id_not_found(hass: HomeAssistant) -> None:
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM assert result2["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result2["step_id"] == "reauth" assert result2["step_id"] == "user"
assert result2["errors"] == {"base": "unknown"} assert result2["errors"] == {"base": "unknown"}
async def test_reauth_user_has_new_email_address(hass: HomeAssistant) -> None:
"""Test reauth with a new email address but same account."""
mock_config = MockConfigEntry(
domain=DOMAIN,
unique_id=FIXTURE_USER_INPUT[CONF_EMAIL],
data=FIXTURE_USER_INPUT,
)
mock_config.add_to_hass(hass)
with patch(
"homeassistant.components.mazda.config_flow.MazdaAPI.validate_credentials",
return_value=True,
), patch(
"homeassistant.components.mazda.async_setup_entry",
return_value=True,
):
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": "reauth", "entry_id": mock_config.entry_id},
data=FIXTURE_USER_INPUT,
)
assert result["type"] == data_entry_flow.RESULT_TYPE_FORM
assert result["step_id"] == "user"
# Change the email and ensure the entry and its unique id gets
# updated in the event the user has changed their email with mazda
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL,
)
await hass.async_block_till_done()
assert (
mock_config.unique_id == FIXTURE_USER_INPUT_REAUTH_CHANGED_EMAIL[CONF_EMAIL]
)
assert result2["type"] == data_entry_flow.RESULT_TYPE_ABORT
assert result2["reason"] == "reauth_successful"

View File

@ -60,7 +60,7 @@ async def test_init_auth_failure(hass: HomeAssistant):
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1 assert len(flows) == 1
assert flows[0]["step_id"] == "reauth" assert flows[0]["step_id"] == "user"
async def test_update_auth_failure(hass: HomeAssistant): async def test_update_auth_failure(hass: HomeAssistant):
@ -99,7 +99,7 @@ async def test_update_auth_failure(hass: HomeAssistant):
flows = hass.config_entries.flow.async_progress() flows = hass.config_entries.flow.async_progress()
assert len(flows) == 1 assert len(flows) == 1
assert flows[0]["step_id"] == "reauth" assert flows[0]["step_id"] == "user"
async def test_unload_config_entry(hass: HomeAssistant) -> None: async def test_unload_config_entry(hass: HomeAssistant) -> None: