Update Aseko to support new API (#126133)

* Update Aseko to support new API

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Use self.unit instead of self._unit

* Refactor sensor setup entry

* Keep same unique id and identifier

* Revert rename free_chlorine translation key

* Remove new heating entity to keep PR small

* Fix keep same unique id

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Milan Meulemans 2024-09-18 16:26:09 +02:00 committed by GitHub
parent e2f1c60981
commit 12dbabb849
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 222 additions and 176 deletions

View File

@ -4,13 +4,12 @@ from __future__ import annotations
import logging import logging
from aioaseko import APIUnavailable, InvalidAuthCredentials, MobileAccount from aioaseko import Aseko, AsekoNotLoggedIn
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AsekoDataUpdateCoordinator from .coordinator import AsekoDataUpdateCoordinator
@ -22,28 +21,17 @@ PLATFORMS: list[str] = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Aseko Pool Live from a config entry.""" """Set up Aseko Pool Live from a config entry."""
account = MobileAccount( aseko = Aseko(entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD])
async_get_clientsession(hass),
username=entry.data[CONF_EMAIL],
password=entry.data[CONF_PASSWORD],
)
try: try:
units = await account.get_units() await aseko.login()
except InvalidAuthCredentials as err: except AsekoNotLoggedIn as err:
raise ConfigEntryAuthFailed from err raise ConfigEntryAuthFailed from err
except APIUnavailable as err:
raise ConfigEntryNotReady from err
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = []
for unit in units:
coordinator = AsekoDataUpdateCoordinator(hass, unit)
await coordinator.async_config_entry_first_refresh()
hass.data[DOMAIN][entry.entry_id].append((unit, coordinator))
coordinator = AsekoDataUpdateCoordinator(hass, aseko)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True return True
@ -51,7 +39,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok return unload_ok

View File

@ -8,7 +8,6 @@ from dataclasses import dataclass
from aioaseko import Unit from aioaseko import Unit
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity, BinarySensorEntity,
BinarySensorEntityDescription, BinarySensorEntityDescription,
) )
@ -25,26 +24,14 @@ from .entity import AsekoEntity
class AsekoBinarySensorEntityDescription(BinarySensorEntityDescription): class AsekoBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes an Aseko binary sensor entity.""" """Describes an Aseko binary sensor entity."""
value_fn: Callable[[Unit], bool] value_fn: Callable[[Unit], bool | None]
UNIT_BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = ( BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = (
AsekoBinarySensorEntityDescription( AsekoBinarySensorEntityDescription(
key="water_flow", key="water_flow",
translation_key="water_flow", translation_key="water_flow_to_probes",
value_fn=lambda unit: unit.water_flow, value_fn=lambda unit: unit.water_flow_to_probes,
),
AsekoBinarySensorEntityDescription(
key="has_alarm",
translation_key="alarm",
value_fn=lambda unit: unit.has_alarm,
device_class=BinarySensorDeviceClass.SAFETY,
),
AsekoBinarySensorEntityDescription(
key="has_error",
translation_key="error",
value_fn=lambda unit: unit.has_error,
device_class=BinarySensorDeviceClass.PROBLEM,
), ),
) )
@ -55,33 +42,22 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Aseko Pool Live binary sensors.""" """Set up the Aseko Pool Live binary sensors."""
data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][ coordinator: AsekoDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
config_entry.entry_id units = coordinator.data.values()
]
async_add_entities( async_add_entities(
AsekoUnitBinarySensorEntity(unit, coordinator, description) AsekoBinarySensorEntity(unit, coordinator, description)
for unit, coordinator in data for description in BINARY_SENSORS
for description in UNIT_BINARY_SENSORS for unit in units
if description.value_fn(unit) is not None
) )
class AsekoUnitBinarySensorEntity(AsekoEntity, BinarySensorEntity): class AsekoBinarySensorEntity(AsekoEntity, BinarySensorEntity):
"""Representation of a unit water flow binary sensor entity.""" """Representation of an Aseko binary sensor entity."""
entity_description: AsekoBinarySensorEntityDescription entity_description: AsekoBinarySensorEntityDescription
def __init__(
self,
unit: Unit,
coordinator: AsekoDataUpdateCoordinator,
entity_description: AsekoBinarySensorEntityDescription,
) -> None:
"""Initialize the unit binary sensor."""
super().__init__(unit, coordinator)
self.entity_description = entity_description
self._attr_unique_id = f"{self._unit.serial_number}_{entity_description.key}"
@property @property
def is_on(self) -> bool: def is_on(self) -> bool | None:
"""Return the state of the sensor.""" """Return the state of the sensor."""
return self.entity_description.value_fn(self._unit) return self.entity_description.value_fn(self.unit)

View File

@ -6,12 +6,11 @@ from collections.abc import Mapping
import logging import logging
from typing import Any from typing import Any
from aioaseko import APIUnavailable, InvalidAuthCredentials, WebAccount from aioaseko import Aseko, AsekoAPIError, AsekoInvalidCredentials
import voluptuous as vol import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_UNIQUE_ID from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, CONF_UNIQUE_ID
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN from .const import DOMAIN
@ -34,15 +33,12 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
async def get_account_info(self, email: str, password: str) -> dict: async def get_account_info(self, email: str, password: str) -> dict:
"""Get account info from the mobile API and the web API.""" """Get account info from the mobile API and the web API."""
session = async_get_clientsession(self.hass) aseko = Aseko(email, password)
user = await aseko.login()
web_account = WebAccount(session, email, password)
web_account_info = await web_account.login()
return { return {
CONF_EMAIL: email, CONF_EMAIL: email,
CONF_PASSWORD: password, CONF_PASSWORD: password,
CONF_UNIQUE_ID: web_account_info.user_id, CONF_UNIQUE_ID: user.user_id,
} }
async def async_step_user( async def async_step_user(
@ -58,9 +54,9 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
info = await self.get_account_info( info = await self.get_account_info(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD] user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
) )
except APIUnavailable: except AsekoAPIError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuthCredentials: except AsekoInvalidCredentials:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except Exception: except Exception:
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")
@ -122,9 +118,9 @@ class AsekoConfigFlow(ConfigFlow, domain=DOMAIN):
info = await self.get_account_info( info = await self.get_account_info(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD] user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
) )
except APIUnavailable: except AsekoAPIError:
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except InvalidAuthCredentials: except AsekoInvalidCredentials:
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except Exception: except Exception:
_LOGGER.exception("Unexpected exception") _LOGGER.exception("Unexpected exception")

View File

@ -5,34 +5,31 @@ from __future__ import annotations
from datetime import timedelta from datetime import timedelta
import logging import logging
from aioaseko import Unit, Variable from aioaseko import Aseko, Unit
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
class AsekoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Variable]]): class AsekoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Unit]]):
"""Class to manage fetching Aseko unit data from single endpoint.""" """Class to manage fetching Aseko unit data from single endpoint."""
def __init__(self, hass: HomeAssistant, unit: Unit) -> None: def __init__(self, hass: HomeAssistant, aseko: Aseko) -> None:
"""Initialize global Aseko unit data updater.""" """Initialize global Aseko unit data updater."""
self._unit = unit self._aseko = aseko
if self._unit.name:
name = self._unit.name
else:
name = f"{self._unit.type}-{self._unit.serial_number}"
super().__init__( super().__init__(
hass, hass,
_LOGGER, _LOGGER,
name=name, name=DOMAIN,
update_interval=timedelta(minutes=2), update_interval=timedelta(minutes=2),
) )
async def _async_update_data(self) -> dict[str, Variable]: async def _async_update_data(self) -> dict[str, Unit]:
"""Fetch unit data.""" """Fetch unit data."""
await self._unit.get_state() units = await self._aseko.get_units()
return {variable.type: variable for variable in self._unit.variables} return {unit.serial_number: unit for unit in units}

View File

@ -3,6 +3,7 @@
from aioaseko import Unit from aioaseko import Unit
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN from .const import DOMAIN
@ -14,20 +15,44 @@ class AsekoEntity(CoordinatorEntity[AsekoDataUpdateCoordinator]):
_attr_has_entity_name = True _attr_has_entity_name = True
def __init__(self, unit: Unit, coordinator: AsekoDataUpdateCoordinator) -> None: def __init__(
self,
unit: Unit,
coordinator: AsekoDataUpdateCoordinator,
description: EntityDescription,
) -> None:
"""Initialize the aseko entity.""" """Initialize the aseko entity."""
super().__init__(coordinator) super().__init__(coordinator)
self.entity_description = description
self._unit = unit self._unit = unit
self._attr_unique_id = f"{self.unit.serial_number}{self.entity_description.key}"
if self._unit.type == "Remote":
self._device_model = "ASIN Pool"
else:
self._device_model = f"ASIN AQUA {self._unit.type}"
self._device_name = self._unit.name if self._unit.name else self._device_model
self._attr_device_info = DeviceInfo( self._attr_device_info = DeviceInfo(
name=self._device_name, identifiers={(DOMAIN, self.unit.serial_number)},
identifiers={(DOMAIN, str(self._unit.serial_number))}, serial_number=self.unit.serial_number,
manufacturer="Aseko", name=unit.name or unit.serial_number,
model=self._device_model, manufacturer=(
self.unit.brand_name.primary
if self.unit.brand_name is not None
else None
),
model=(
self.unit.brand_name.secondary
if self.unit.brand_name is not None
else None
),
configuration_url=f"https://aseko.cloud/unit/{self.unit.serial_number}",
)
@property
def unit(self) -> Unit:
"""Return the aseko unit."""
return self.coordinator.data[self._unit.serial_number]
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
super().available
and self.unit.serial_number in self.coordinator.data
and self.unit.online
) )

View File

@ -1,16 +1,25 @@
{ {
"entity": { "entity": {
"binary_sensor": { "binary_sensor": {
"water_flow": { "water_flow_to_probes": {
"default": "mdi:waves-arrow-right" "default": "mdi:waves-arrow-right"
} }
}, },
"sensor": { "sensor": {
"air_temperature": {
"default": "mdi:thermometer-lines"
},
"free_chlorine": { "free_chlorine": {
"default": "mdi:flask" "default": "mdi:pool"
},
"redox": {
"default": "mdi:pool"
},
"salinity": {
"default": "mdi:pool"
}, },
"water_temperature": { "water_temperature": {
"default": "mdi:coolant-temperature" "default": "mdi:pool-thermometer"
} }
} }
} }

View File

@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aseko_pool_live", "documentation": "https://www.home-assistant.io/integrations/aseko_pool_live",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["aioaseko"], "loggers": ["aioaseko"],
"requirements": ["aioaseko==0.2.0"] "requirements": ["aioaseko==1.0.0"]
} }

View File

@ -2,77 +2,104 @@
from __future__ import annotations from __future__ import annotations
from aioaseko import Unit, Variable from collections.abc import Callable
from dataclasses import dataclass
from aioaseko import Unit
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorDeviceClass, SensorDeviceClass,
SensorEntity, SensorEntity,
SensorEntityDescription,
SensorStateClass, SensorStateClass,
) )
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfElectricPotential, UnitOfTemperature
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AsekoDataUpdateCoordinator from .coordinator import AsekoDataUpdateCoordinator
from .entity import AsekoEntity from .entity import AsekoEntity
@dataclass(frozen=True, kw_only=True)
class AsekoSensorEntityDescription(SensorEntityDescription):
"""Describes an Aseko sensor entity."""
value_fn: Callable[[Unit], StateType]
SENSORS: list[AsekoSensorEntityDescription] = [
AsekoSensorEntityDescription(
key="airTemp",
translation_key="air_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.air_temperature,
),
AsekoSensorEntityDescription(
key="free_chlorine",
translation_key="free_chlorine",
native_unit_of_measurement="mg/l",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.cl_free,
),
AsekoSensorEntityDescription(
key="ph",
device_class=SensorDeviceClass.PH,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.ph,
),
AsekoSensorEntityDescription(
key="rx",
translation_key="redox",
native_unit_of_measurement=UnitOfElectricPotential.MILLIVOLT,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.redox,
),
AsekoSensorEntityDescription(
key="salinity",
translation_key="salinity",
native_unit_of_measurement="kg/m³",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.salinity,
),
AsekoSensorEntityDescription(
key="waterTemp",
translation_key="water_temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda unit: unit.water_temperature,
),
]
async def async_setup_entry( async def async_setup_entry(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: ConfigEntry, config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up the Aseko Pool Live sensors.""" """Set up the Aseko Pool Live sensors."""
data: list[tuple[Unit, AsekoDataUpdateCoordinator]] = hass.data[DOMAIN][ coordinator: AsekoDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
config_entry.entry_id units = coordinator.data.values()
]
async_add_entities( async_add_entities(
VariableSensorEntity(unit, variable, coordinator) AsekoSensorEntity(unit, coordinator, description)
for unit, coordinator in data for description in SENSORS
for variable in unit.variables for unit in units
if description.value_fn(unit) is not None
) )
class VariableSensorEntity(AsekoEntity, SensorEntity): class AsekoSensorEntity(AsekoEntity, SensorEntity):
"""Representation of a unit variable sensor entity.""" """Representation of an Aseko unit sensor entity."""
_attr_state_class = SensorStateClass.MEASUREMENT entity_description: AsekoSensorEntityDescription
def __init__(
self, unit: Unit, variable: Variable, coordinator: AsekoDataUpdateCoordinator
) -> None:
"""Initialize the variable sensor."""
super().__init__(unit, coordinator)
self._variable = variable
translation_key = {
"Air temp.": "air_temperature",
"Cl free": "free_chlorine",
"Water temp.": "water_temperature",
}.get(self._variable.name)
if translation_key is not None:
self._attr_translation_key = translation_key
else:
self._attr_name = self._variable.name
self._attr_unique_id = f"{self._unit.serial_number}{self._variable.type}"
self._attr_native_unit_of_measurement = self._variable.unit
self._attr_icon = {
"rx": "mdi:test-tube",
"waterLevel": "mdi:waves",
}.get(self._variable.type)
self._attr_device_class = {
"airTemp": SensorDeviceClass.TEMPERATURE,
"waterTemp": SensorDeviceClass.TEMPERATURE,
"ph": SensorDeviceClass.PH,
}.get(self._variable.type)
@property @property
def native_value(self) -> int | None: def native_value(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
variable = self.coordinator.data[self._variable.type] return self.entity_description.value_fn(self.unit)
return variable.current_value

View File

@ -26,11 +26,8 @@
}, },
"entity": { "entity": {
"binary_sensor": { "binary_sensor": {
"water_flow": { "water_flow_to_probes": {
"name": "Water flow" "name": "Water flow to probes"
},
"alarm": {
"name": "Alarm"
} }
}, },
"sensor": { "sensor": {
@ -40,6 +37,12 @@
"free_chlorine": { "free_chlorine": {
"name": "Free chlorine" "name": "Free chlorine"
}, },
"redox": {
"name": "Redox potential"
},
"salinity": {
"name": "Salinity"
},
"water_temperature": { "water_temperature": {
"name": "Water temperature" "name": "Water temperature"
} }

View File

@ -192,7 +192,7 @@ aioapcaccess==0.4.2
aioaquacell==0.2.0 aioaquacell==0.2.0
# homeassistant.components.aseko_pool_live # homeassistant.components.aseko_pool_live
aioaseko==0.2.0 aioaseko==1.0.0
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.4.0 aioasuswrt==1.4.0

View File

@ -180,7 +180,7 @@ aioapcaccess==0.4.2
aioaquacell==0.2.0 aioaquacell==0.2.0
# homeassistant.components.aseko_pool_live # homeassistant.components.aseko_pool_live
aioaseko==0.2.0 aioaseko==1.0.0
# homeassistant.components.asuswrt # homeassistant.components.asuswrt
aioasuswrt==1.4.0 aioasuswrt==1.4.0

View File

@ -0,0 +1,20 @@
"""Aseko Pool Live conftest."""
from datetime import datetime
from aioaseko import User
import pytest
@pytest.fixture
def user() -> User:
"""Aseko User fixture."""
return User(
user_id="a_user_id",
created_at=datetime.now(),
updated_at=datetime.now(),
name="John",
surname="Doe",
language="any_language",
is_active=True,
)

View File

@ -2,7 +2,7 @@
from unittest.mock import patch from unittest.mock import patch
from aioaseko import AccountInfo, APIUnavailable, InvalidAuthCredentials from aioaseko import AsekoAPIError, AsekoInvalidCredentials, User
import pytest import pytest
from homeassistant import config_entries from homeassistant import config_entries
@ -23,7 +23,7 @@ async def test_async_step_user_form(hass: HomeAssistant) -> None:
assert result["errors"] == {} assert result["errors"] == {}
async def test_async_step_user_success(hass: HomeAssistant) -> None: async def test_async_step_user_success(hass: HomeAssistant, user: User) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -31,8 +31,8 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
with ( with (
patch( patch(
"homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), return_value=user,
), ),
patch( patch(
"homeassistant.components.aseko_pool_live.async_setup_entry", "homeassistant.components.aseko_pool_live.async_setup_entry",
@ -60,13 +60,13 @@ async def test_async_step_user_success(hass: HomeAssistant) -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
("error_web", "reason"), ("error_web", "reason"),
[ [
(APIUnavailable, "cannot_connect"), (AsekoAPIError, "cannot_connect"),
(InvalidAuthCredentials, "invalid_auth"), (AsekoInvalidCredentials, "invalid_auth"),
(Exception, "unknown"), (Exception, "unknown"),
], ],
) )
async def test_async_step_user_exception( async def test_async_step_user_exception(
hass: HomeAssistant, error_web: Exception, reason: str hass: HomeAssistant, user: User, error_web: Exception, reason: str
) -> None: ) -> None:
"""Test we get the form.""" """Test we get the form."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -74,8 +74,8 @@ async def test_async_step_user_exception(
) )
with patch( with patch(
"homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), return_value=user,
side_effect=error_web, side_effect=error_web,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -93,13 +93,13 @@ async def test_async_step_user_exception(
@pytest.mark.parametrize( @pytest.mark.parametrize(
("error_web", "reason"), ("error_web", "reason"),
[ [
(APIUnavailable, "cannot_connect"), (AsekoAPIError, "cannot_connect"),
(InvalidAuthCredentials, "invalid_auth"), (AsekoInvalidCredentials, "invalid_auth"),
(Exception, "unknown"), (Exception, "unknown"),
], ],
) )
async def test_get_account_info_exceptions( async def test_get_account_info_exceptions(
hass: HomeAssistant, error_web: Exception, reason: str hass: HomeAssistant, user: User, error_web: Exception, reason: str
) -> None: ) -> None:
"""Test we handle config flow exceptions.""" """Test we handle config flow exceptions."""
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
@ -107,8 +107,8 @@ async def test_get_account_info_exceptions(
) )
with patch( with patch(
"homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), return_value=user,
side_effect=error_web, side_effect=error_web,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
@ -123,7 +123,7 @@ async def test_get_account_info_exceptions(
assert result2["errors"] == {"base": reason} assert result2["errors"] == {"base": reason}
async def test_async_step_reauth_success(hass: HomeAssistant) -> None: async def test_async_step_reauth_success(hass: HomeAssistant, user: User) -> None:
"""Test successful reauthentication.""" """Test successful reauthentication."""
mock_entry = MockConfigEntry( mock_entry = MockConfigEntry(
@ -139,10 +139,16 @@ async def test_async_step_reauth_success(hass: HomeAssistant) -> None:
assert result["step_id"] == "reauth_confirm" assert result["step_id"] == "reauth_confirm"
assert result["errors"] == {} assert result["errors"] == {}
with patch( with (
"homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", patch(
return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
) as mock_setup_entry: return_value=user,
),
patch(
"homeassistant.components.aseko_pool_live.async_setup_entry",
return_value=True,
) as mock_setup_entry,
):
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
{CONF_EMAIL: "aseko@example.com", CONF_PASSWORD: "passw0rd"}, {CONF_EMAIL: "aseko@example.com", CONF_PASSWORD: "passw0rd"},
@ -156,13 +162,13 @@ async def test_async_step_reauth_success(hass: HomeAssistant) -> None:
@pytest.mark.parametrize( @pytest.mark.parametrize(
("error_web", "reason"), ("error_web", "reason"),
[ [
(APIUnavailable, "cannot_connect"), (AsekoAPIError, "cannot_connect"),
(InvalidAuthCredentials, "invalid_auth"), (AsekoInvalidCredentials, "invalid_auth"),
(Exception, "unknown"), (Exception, "unknown"),
], ],
) )
async def test_async_step_reauth_exception( async def test_async_step_reauth_exception(
hass: HomeAssistant, error_web: Exception, reason: str hass: HomeAssistant, user: User, error_web: Exception, reason: str
) -> None: ) -> None:
"""Test we get the form.""" """Test we get the form."""
@ -176,8 +182,8 @@ async def test_async_step_reauth_exception(
result = await mock_entry.start_reauth_flow(hass) result = await mock_entry.start_reauth_flow(hass)
with patch( with patch(
"homeassistant.components.aseko_pool_live.config_flow.WebAccount.login", "homeassistant.components.aseko_pool_live.config_flow.Aseko.login",
return_value=AccountInfo("aseko@example.com", "a_user_id", "any_language"), return_value=user,
side_effect=error_web, side_effect=error_web,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(