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.json
pull/106719/head
Floris272 2023-12-30 13:53:35 +01:00 committed by GitHub
parent f17470cb29
commit ee1b0b46ce
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 34 deletions

View File

@ -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

View File

@ -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()

View File

@ -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"]
}

View File

@ -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": {

View File

@ -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()

View File

@ -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()