Add reauth to Blue Current integration (#106658)
* Add reauth to Blue Current integration. * Apply feedback * Fix failing codecov check * Fix patches * Add wrong_account to strings.jsonpull/106719/head
parent
f17470cb29
commit
ee1b0b46ce
|
@ -16,7 +16,7 @@ from bluecurrent_api.exceptions import (
|
|||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import ATTR_NAME, CONF_API_TOKEN, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_send
|
||||
from homeassistant.helpers.event import async_call_later
|
||||
|
||||
|
@ -42,9 +42,8 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
|
|||
|
||||
try:
|
||||
await connector.connect(api_token)
|
||||
except InvalidApiToken:
|
||||
LOGGER.error("Invalid Api token")
|
||||
return False
|
||||
except InvalidApiToken as err:
|
||||
raise ConfigEntryAuthFailed("Invalid API token.") from err
|
||||
except BlueCurrentException as err:
|
||||
raise ConfigEntryNotReady from err
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
"""Config flow for Blue Current integration."""
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Mapping
|
||||
from typing import Any
|
||||
|
||||
from bluecurrent_api import Client
|
||||
|
@ -25,6 +26,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
"""Handle the config flow for Blue Current."""
|
||||
|
||||
VERSION = 1
|
||||
_reauth_entry: config_entries.ConfigEntry | None = None
|
||||
|
||||
async def async_step_user(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
|
@ -51,11 +53,31 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||
errors["base"] = "unknown"
|
||||
|
||||
else:
|
||||
await self.async_set_unique_id(customer_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
if not self._reauth_entry:
|
||||
await self.async_set_unique_id(customer_id)
|
||||
self._abort_if_unique_id_configured()
|
||||
return self.async_create_entry(title=email, data=user_input)
|
||||
|
||||
return self.async_create_entry(title=email, data=user_input)
|
||||
if self._reauth_entry.unique_id == customer_id:
|
||||
self.hass.config_entries.async_update_entry(
|
||||
self._reauth_entry, data=user_input
|
||||
)
|
||||
await self.hass.config_entries.async_reload(
|
||||
self._reauth_entry.entry_id
|
||||
)
|
||||
return self.async_abort(reason="reauth_successful")
|
||||
|
||||
return self.async_abort(
|
||||
reason="wrong_account",
|
||||
description_placeholders={"email": email},
|
||||
)
|
||||
return self.async_show_form(
|
||||
step_id="user", data_schema=DATA_SCHEMA, errors=errors
|
||||
)
|
||||
|
||||
async def async_step_reauth(self, entry_data: Mapping[str, Any]) -> FlowResult:
|
||||
"""Handle a reauthorization flow request."""
|
||||
self._reauth_entry = self.hass.config_entries.async_get_entry(
|
||||
self.context["entry_id"]
|
||||
)
|
||||
return await self.async_step_user()
|
||||
|
|
|
@ -5,6 +5,5 @@
|
|||
"config_flow": true,
|
||||
"documentation": "https://www.home-assistant.io/integrations/blue_current",
|
||||
"iot_class": "cloud_push",
|
||||
"issue_tracker": "https://github.com/bluecurrent/ha-bluecurrent/issues",
|
||||
"requirements": ["bluecurrent-api==1.0.6"]
|
||||
}
|
||||
|
|
|
@ -18,7 +18,9 @@
|
|||
"unknown": "[%key:common::config_flow::error::unknown%]"
|
||||
},
|
||||
"abort": {
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]"
|
||||
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
|
||||
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
|
||||
"wrong_account": "Wrong account: Please authenticate with the api key for {email}."
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
|
|
|
@ -12,6 +12,9 @@ from homeassistant.components.blue_current.config_flow import (
|
|||
WebsocketError,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_form(hass: HomeAssistant) -> None:
|
||||
|
@ -30,8 +33,12 @@ async def test_user(hass: HomeAssistant) -> None:
|
|||
)
|
||||
assert result["errors"] == {}
|
||||
|
||||
with patch("bluecurrent_api.Client.validate_api_token", return_value=True), patch(
|
||||
"bluecurrent_api.Client.get_email", return_value="test@email.com"
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.validate_api_token",
|
||||
return_value="1234",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.get_email",
|
||||
return_value="test@email.com",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -59,9 +66,9 @@ async def test_user(hass: HomeAssistant) -> None:
|
|||
],
|
||||
)
|
||||
async def test_flow_fails(hass: HomeAssistant, error: Exception, message: str) -> None:
|
||||
"""Test user initialized flow with invalid username."""
|
||||
"""Test bluecurrent api errors during configuration flow."""
|
||||
with patch(
|
||||
"bluecurrent_api.Client.validate_api_token",
|
||||
"homeassistant.components.blue_current.config_flow.Client.validate_api_token",
|
||||
side_effect=error,
|
||||
):
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
|
@ -71,8 +78,12 @@ async def test_flow_fails(hass: HomeAssistant, error: Exception, message: str) -
|
|||
)
|
||||
assert result["errors"]["base"] == message
|
||||
|
||||
with patch("bluecurrent_api.Client.validate_api_token", return_value=True), patch(
|
||||
"bluecurrent_api.Client.get_email", return_value="test@email.com"
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.validate_api_token",
|
||||
return_value="1234",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.get_email",
|
||||
return_value="test@email.com",
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.async_setup_entry",
|
||||
return_value=True,
|
||||
|
@ -87,3 +98,52 @@ async def test_flow_fails(hass: HomeAssistant, error: Exception, message: str) -
|
|||
|
||||
assert result2["title"] == "test@email.com"
|
||||
assert result2["data"] == {"api_token": "123"}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("customer_id", "reason", "expected_api_token"),
|
||||
[
|
||||
("1234", "reauth_successful", "1234567890"),
|
||||
("6666", "wrong_account", "123"),
|
||||
],
|
||||
)
|
||||
async def test_reauth(
|
||||
hass: HomeAssistant, customer_id: str, reason: str, expected_api_token: str
|
||||
) -> None:
|
||||
"""Test reauth flow."""
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.validate_api_token",
|
||||
return_value=customer_id,
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.config_flow.Client.get_email",
|
||||
return_value="test@email.com",
|
||||
):
|
||||
entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="uuid",
|
||||
unique_id="1234",
|
||||
data={"api_token": "123"},
|
||||
)
|
||||
entry.add_to_hass(hass)
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={
|
||||
"source": config_entries.SOURCE_REAUTH,
|
||||
"entry_id": entry.entry_id,
|
||||
"unique_id": entry.unique_id,
|
||||
},
|
||||
data={"api_token": "123"},
|
||||
)
|
||||
|
||||
assert result["type"] == FlowResultType.FORM
|
||||
assert result["step_id"] == "user"
|
||||
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
user_input={"api_token": "1234567890"},
|
||||
)
|
||||
assert result["type"] == FlowResultType.ABORT
|
||||
assert result["reason"] == reason
|
||||
assert entry.data == {"api_token": expected_api_token}
|
||||
|
||||
await hass.async_block_till_done()
|
||||
|
|
|
@ -4,13 +4,22 @@ from datetime import timedelta
|
|||
from unittest.mock import patch
|
||||
|
||||
from bluecurrent_api.client import Client
|
||||
from bluecurrent_api.exceptions import RequestLimitReached, WebsocketError
|
||||
from bluecurrent_api.exceptions import (
|
||||
BlueCurrentException,
|
||||
InvalidApiToken,
|
||||
RequestLimitReached,
|
||||
WebsocketError,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.blue_current import DOMAIN, Connector, async_setup_entry
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.exceptions import (
|
||||
ConfigEntryAuthFailed,
|
||||
ConfigEntryNotReady,
|
||||
IntegrationError,
|
||||
)
|
||||
|
||||
from . import init_integration
|
||||
|
||||
|
@ -29,12 +38,21 @@ async def test_load_unload_entry(hass: HomeAssistant) -> None:
|
|||
assert hass.data[DOMAIN] == {}
|
||||
|
||||
|
||||
async def test_config_not_ready(hass: HomeAssistant) -> None:
|
||||
"""Tests if ConfigEntryNotReady is raised when connect raises a WebsocketError."""
|
||||
@pytest.mark.parametrize(
|
||||
("api_error", "config_error"),
|
||||
[
|
||||
(InvalidApiToken, ConfigEntryAuthFailed),
|
||||
(BlueCurrentException, ConfigEntryNotReady),
|
||||
],
|
||||
)
|
||||
async def test_config_exceptions(
|
||||
hass: HomeAssistant, api_error: BlueCurrentException, config_error: IntegrationError
|
||||
) -> None:
|
||||
"""Tests if the correct config error is raised when connecting to the api fails."""
|
||||
with patch(
|
||||
"bluecurrent_api.Client.connect",
|
||||
side_effect=WebsocketError,
|
||||
), pytest.raises(ConfigEntryNotReady):
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
side_effect=api_error,
|
||||
), pytest.raises(config_error):
|
||||
config_entry = MockConfigEntry(
|
||||
domain=DOMAIN,
|
||||
entry_id="uuid",
|
||||
|
@ -143,14 +161,15 @@ async def test_start_loop(hass: HomeAssistant) -> None:
|
|||
connector = Connector(hass, config_entry, Client)
|
||||
|
||||
with patch(
|
||||
"bluecurrent_api.Client.start_loop",
|
||||
"homeassistant.components.blue_current.Client.start_loop",
|
||||
side_effect=WebsocketError("unknown command"),
|
||||
):
|
||||
await connector.start_loop()
|
||||
test_async_call_later.assert_called_with(hass, 1, connector.reconnect)
|
||||
|
||||
with patch(
|
||||
"bluecurrent_api.Client.start_loop", side_effect=RequestLimitReached
|
||||
"homeassistant.components.blue_current.Client.start_loop",
|
||||
side_effect=RequestLimitReached,
|
||||
):
|
||||
await connector.start_loop()
|
||||
test_async_call_later.assert_called_with(hass, 1, connector.reconnect)
|
||||
|
@ -159,11 +178,7 @@ async def test_start_loop(hass: HomeAssistant) -> None:
|
|||
async def test_reconnect(hass: HomeAssistant) -> None:
|
||||
"""Tests reconnect."""
|
||||
|
||||
with patch("bluecurrent_api.Client.connect"), patch(
|
||||
"bluecurrent_api.Client.connect", side_effect=WebsocketError
|
||||
), patch(
|
||||
"bluecurrent_api.Client.get_next_reset_delta", return_value=timedelta(hours=1)
|
||||
), patch(
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.async_call_later"
|
||||
) as test_async_call_later:
|
||||
config_entry = MockConfigEntry(
|
||||
|
@ -174,12 +189,33 @@ async def test_reconnect(hass: HomeAssistant) -> None:
|
|||
)
|
||||
|
||||
connector = Connector(hass, config_entry, Client)
|
||||
await connector.reconnect()
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
side_effect=WebsocketError,
|
||||
):
|
||||
await connector.reconnect()
|
||||
|
||||
test_async_call_later.assert_called_with(hass, 20, connector.reconnect)
|
||||
|
||||
with patch("bluecurrent_api.Client.connect", side_effect=RequestLimitReached):
|
||||
with patch(
|
||||
"homeassistant.components.blue_current.Client.connect",
|
||||
side_effect=RequestLimitReached,
|
||||
), patch(
|
||||
"homeassistant.components.blue_current.Client.get_next_reset_delta",
|
||||
return_value=timedelta(hours=1),
|
||||
):
|
||||
await connector.reconnect()
|
||||
test_async_call_later.assert_called_with(
|
||||
hass, timedelta(hours=1), connector.reconnect
|
||||
)
|
||||
|
||||
test_async_call_later.assert_called_with(
|
||||
hass, timedelta(hours=1), connector.reconnect
|
||||
)
|
||||
|
||||
with patch("homeassistant.components.blue_current.Client.connect"), patch(
|
||||
"homeassistant.components.blue_current.Connector.start_loop"
|
||||
) as test_start_loop, patch(
|
||||
"homeassistant.components.blue_current.Client.get_charge_points"
|
||||
) as test_get_charge_points:
|
||||
await connector.reconnect()
|
||||
test_start_loop.assert_called_once()
|
||||
test_get_charge_points.assert_called_once()
|
||||
|
|
Loading…
Reference in New Issue