New official genie garage integration (#117020)

* new official genie garage integration

* move api constants into api module

* move scan interval constant to cover.py
This commit is contained in:
swcloudgenie 2024-05-29 15:13:28 -05:00 committed by GitHub
parent f93a3127f2
commit a670169325
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 286 additions and 1354 deletions

View file

@ -58,6 +58,12 @@ omit =
homeassistant/components/airvisual/sensor.py
homeassistant/components/airvisual_pro/__init__.py
homeassistant/components/airvisual_pro/sensor.py
homeassistant/components/aladdin_connect/__init__.py
homeassistant/components/aladdin_connect/api.py
homeassistant/components/aladdin_connect/application_credentials.py
homeassistant/components/aladdin_connect/cover.py
homeassistant/components/aladdin_connect/model.py
homeassistant/components/aladdin_connect/sensor.py
homeassistant/components/alarmdecoder/__init__.py
homeassistant/components/alarmdecoder/alarm_control_panel.py
homeassistant/components/alarmdecoder/binary_sensor.py

View file

@ -80,8 +80,8 @@ build.json @home-assistant/supervisor
/tests/components/airzone/ @Noltari
/homeassistant/components/airzone_cloud/ @Noltari
/tests/components/airzone_cloud/ @Noltari
/homeassistant/components/aladdin_connect/ @mkmer
/tests/components/aladdin_connect/ @mkmer
/homeassistant/components/aladdin_connect/ @swcloudgenie
/tests/components/aladdin_connect/ @swcloudgenie
/homeassistant/components/alarm_control_panel/ @home-assistant/core
/tests/components/alarm_control_panel/ @home-assistant/core
/homeassistant/components/alert/ @home-assistant/core @frenck

View file

@ -1,40 +1,33 @@
"""The aladdin_connect component."""
"""The Aladdin Connect Genie integration."""
import logging
from typing import Final
from AIOAladdinConnect import AladdinConnectClient
import AIOAladdinConnect.session_manager as Aladdin
from aiohttp import ClientError
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
from .const import CLIENT_ID, DOMAIN
from . import api
from .const import CONFIG_FLOW_MINOR_VERSION, CONFIG_FLOW_VERSION
_LOGGER: Final = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
PLATFORMS: list[Platform] = [Platform.COVER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up platform from a ConfigEntry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
acc = AladdinConnectClient(
username, password, async_get_clientsession(hass), CLIENT_ID
"""Set up Aladdin Connect Genie from a config entry."""
implementation = (
await config_entry_oauth2_flow.async_get_config_entry_implementation(
hass, entry
)
)
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
# If using an aiohttp-based API lib
entry.runtime_data = api.AsyncConfigEntryAuth(
aiohttp_client.async_get_clientsession(hass), session
)
try:
await acc.login()
except (ClientError, TimeoutError, Aladdin.ConnectionError) as ex:
raise ConfigEntryNotReady("Can not connect to host") from ex
except Aladdin.InvalidPasswordError as ex:
raise ConfigEntryAuthFailed("Incorrect Password") from ex
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = acc
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
@ -42,7 +35,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
return unload_ok
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old config."""
if config_entry.version < CONFIG_FLOW_VERSION:
config_entry.async_start_reauth(hass)
new_data = {**config_entry.data}
hass.config_entries.async_update_entry(
config_entry,
data=new_data,
version=CONFIG_FLOW_VERSION,
minor_version=CONFIG_FLOW_MINOR_VERSION,
)
return True

View file

@ -0,0 +1,31 @@
"""API for Aladdin Connect Genie bound to Home Assistant OAuth."""
from aiohttp import ClientSession
from genie_partner_sdk.auth import Auth
from homeassistant.helpers import config_entry_oauth2_flow
API_URL = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1"
API_KEY = "k6QaiQmcTm2zfaNns5L1Z8duBtJmhDOW8JawlCC3"
class AsyncConfigEntryAuth(Auth): # type: ignore[misc]
"""Provide Aladdin Connect Genie authentication tied to an OAuth2 based config entry."""
def __init__(
self,
websession: ClientSession,
oauth_session: config_entry_oauth2_flow.OAuth2Session,
) -> None:
"""Initialize Aladdin Connect Genie auth."""
super().__init__(
websession, API_URL, oauth_session.token["access_token"], API_KEY
)
self._oauth_session = oauth_session
async def async_get_access_token(self) -> str:
"""Return a valid access token."""
if not self._oauth_session.valid_token:
await self._oauth_session.async_ensure_token_valid()
return str(self._oauth_session.token["access_token"])

View file

@ -0,0 +1,14 @@
"""application_credentials platform the Aladdin Connect Genie integration."""
from homeassistant.components.application_credentials import AuthorizationServer
from homeassistant.core import HomeAssistant
from .const import OAUTH2_AUTHORIZE, OAUTH2_TOKEN
async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer:
"""Return authorization server."""
return AuthorizationServer(
authorize_url=OAUTH2_AUTHORIZE,
token_url=OAUTH2_TOKEN,
)

View file

@ -1,137 +1,58 @@
"""Config flow for Aladdin Connect cover integration."""
from __future__ import annotations
"""Config flow for Aladdin Connect Genie."""
from collections.abc import Mapping
import logging
from typing import Any
from AIOAladdinConnect import AladdinConnectClient
import AIOAladdinConnect.session_manager as Aladdin
from aiohttp.client_exceptions import ClientError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry, ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
from homeassistant.helpers import config_entry_oauth2_flow
from .const import CLIENT_ID, DOMAIN
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
}
)
REAUTH_SCHEMA = vol.Schema({vol.Required(CONF_PASSWORD): str})
from .const import CONFIG_FLOW_MINOR_VERSION, CONFIG_FLOW_VERSION, DOMAIN
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
"""Validate the user input allows us to connect.
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle Aladdin Connect Genie OAuth2 authentication."""
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
"""
acc = AladdinConnectClient(
data[CONF_USERNAME],
data[CONF_PASSWORD],
async_get_clientsession(hass),
CLIENT_ID,
)
try:
await acc.login()
except (ClientError, TimeoutError, Aladdin.ConnectionError):
raise
DOMAIN = DOMAIN
VERSION = CONFIG_FLOW_VERSION
MINOR_VERSION = CONFIG_FLOW_MINOR_VERSION
except Aladdin.InvalidPasswordError as ex:
raise InvalidAuth from ex
class AladdinConnectConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Aladdin Connect."""
VERSION = 1
entry: ConfigEntry | None
reauth_entry: ConfigEntry | None = None
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
self, user_input: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle re-authentication with Aladdin Connect."""
self.entry = self.hass.config_entries.async_get_entry(self.context["entry_id"])
"""Perform reauth upon API auth error or upgrade from v1 to v2."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
self, user_input: Mapping[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm re-authentication with Aladdin Connect."""
errors: dict[str, str] = {}
if user_input:
assert self.entry is not None
password = user_input[CONF_PASSWORD]
data = {
CONF_USERNAME: self.entry.data[CONF_USERNAME],
CONF_PASSWORD: password,
}
try:
await validate_input(self.hass, data)
except InvalidAuth:
errors["base"] = "invalid_auth"
except (ClientError, TimeoutError, Aladdin.ConnectionError):
errors["base"] = "cannot_connect"
else:
self.hass.config_entries.async_update_entry(
self.entry,
data={
**self.entry.data,
CONF_PASSWORD: password,
},
)
await self.hass.config_entries.async_reload(self.entry.entry_id)
return self.async_abort(reason="reauth_successful")
return self.async_show_form(
step_id="reauth_confirm",
data_schema=REAUTH_SCHEMA,
errors=errors,
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
errors = {}
try:
await validate_input(self.hass, user_input)
except InvalidAuth:
errors["base"] = "invalid_auth"
except (ClientError, TimeoutError, Aladdin.ConnectionError):
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
user_input["username"].lower(), raise_on_progress=False
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
"""Create an oauth config entry or update existing entry for reauth."""
if self.reauth_entry:
return self.async_update_reload_and_abort(
self.reauth_entry,
data=data,
)
self._abort_if_unique_id_configured()
return self.async_create_entry(title="Aladdin Connect", data=user_input)
return await super().async_oauth_create_entry(data)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors
)
class InvalidAuth(HomeAssistantError):
"""Error to indicate there is invalid auth."""
@property
def logger(self) -> logging.Logger:
"""Return logger."""
return logging.getLogger(__name__)

View file

@ -1,22 +1,14 @@
"""Platform for the Aladdin Connect cover component."""
from __future__ import annotations
"""Constants for the Aladdin Connect Genie integration."""
from typing import Final
from homeassistant.components.cover import CoverEntityFeature
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING
NOTIFICATION_ID: Final = "aladdin_notification"
NOTIFICATION_TITLE: Final = "Aladdin Connect Cover Setup"
STATES_MAP: Final[dict[str, str]] = {
"open": STATE_OPEN,
"opening": STATE_OPENING,
"closed": STATE_CLOSED,
"closing": STATE_CLOSING,
}
DOMAIN = "aladdin_connect"
CONFIG_FLOW_VERSION = 2
CONFIG_FLOW_MINOR_VERSION = 1
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.com/login.html"
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
CLIENT_ID = "1000"

View file

@ -1,25 +1,23 @@
"""Platform for the Aladdin Connect cover component."""
from __future__ import annotations
"""Cover Entity for Genie Garage Door."""
from datetime import timedelta
from typing import Any
from AIOAladdinConnect import AladdinConnectClient, session_manager
from genie_partner_sdk.client import AladdinConnectClient
from homeassistant.components.cover import CoverDeviceClass, CoverEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import STATE_CLOSED, STATE_CLOSING, STATE_OPENING
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, PlatformNotReady
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, STATES_MAP, SUPPORTED_FEATURES
from .model import DoorDevice
from . import api
from .const import DOMAIN, SUPPORTED_FEATURES
from .model import GarageDoor
SCAN_INTERVAL = timedelta(seconds=300)
SCAN_INTERVAL = timedelta(seconds=15)
async def async_setup_entry(
@ -28,25 +26,33 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aladdin Connect platform."""
acc: AladdinConnectClient = hass.data[DOMAIN][config_entry.entry_id]
session: api.AsyncConfigEntryAuth = config_entry.runtime_data
acc = AladdinConnectClient(session)
doors = await acc.get_doors()
if doors is None:
raise PlatformNotReady("Error from Aladdin Connect getting doors")
device_registry = dr.async_get(hass)
doors_to_add = []
for door in doors:
existing = device_registry.async_get(door.unique_id)
if existing is None:
doors_to_add.append(door)
async_add_entities(
(AladdinDevice(acc, door, config_entry) for door in doors),
(AladdinDevice(acc, door, config_entry) for door in doors_to_add),
)
remove_stale_devices(hass, config_entry, doors)
def remove_stale_devices(
hass: HomeAssistant, config_entry: ConfigEntry, devices: list[dict]
hass: HomeAssistant, config_entry: ConfigEntry, devices: list[GarageDoor]
) -> None:
"""Remove stale devices from device registry."""
device_registry = dr.async_get(hass)
device_entries = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
all_device_ids = {f"{door['device_id']}-{door['door_number']}" for door in devices}
all_device_ids = {door.unique_id for door in devices}
for device_entry in device_entries:
device_id: str | None = None
@ -74,74 +80,52 @@ class AladdinDevice(CoverEntity):
_attr_name = None
def __init__(
self, acc: AladdinConnectClient, device: DoorDevice, entry: ConfigEntry
self, acc: AladdinConnectClient, device: GarageDoor, entry: ConfigEntry
) -> None:
"""Initialize the Aladdin Connect cover."""
self._acc = acc
self._device_id = device["device_id"]
self._number = device["door_number"]
self._serial = device["serial"]
self._device_id = device.device_id
self._number = device.door_number
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=device["name"],
identifiers={(DOMAIN, device.unique_id)},
name=device.name,
manufacturer="Overhead Door",
model=device["model"],
)
self._attr_unique_id = f"{self._device_id}-{self._number}"
async def async_added_to_hass(self) -> None:
"""Connect Aladdin Connect to the cloud."""
self._acc.register_callback(
self.async_write_ha_state, self._serial, self._number
)
await self._acc.get_doors(self._serial)
async def async_will_remove_from_hass(self) -> None:
"""Close Aladdin Connect before removing."""
self._acc.unregister_callback(self._serial, self._number)
await self._acc.close()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Issue close command to cover."""
if not await self._acc.close_door(self._device_id, self._number):
raise HomeAssistantError("Aladdin Connect API failed to close the cover")
self._attr_unique_id = device.unique_id
async def async_open_cover(self, **kwargs: Any) -> None:
"""Issue open command to cover."""
if not await self._acc.open_door(self._device_id, self._number):
raise HomeAssistantError("Aladdin Connect API failed to open the cover")
await self._acc.open_door(self._device_id, self._number)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Issue close command to cover."""
await self._acc.close_door(self._device_id, self._number)
async def async_update(self) -> None:
"""Update status of cover."""
try:
await self._acc.get_doors(self._serial)
self._attr_available = True
except (session_manager.ConnectionError, session_manager.InvalidPasswordError):
self._attr_available = False
await self._acc.update_door(self._device_id, self._number)
@property
def is_closed(self) -> bool | None:
"""Update is closed attribute."""
value = STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
value = self._acc.get_door_status(self._device_id, self._number)
if value is None:
return None
return value == STATE_CLOSED
return bool(value == "closed")
@property
def is_closing(self) -> bool:
def is_closing(self) -> bool | None:
"""Update is closing attribute."""
return (
STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
== STATE_CLOSING
)
value = self._acc.get_door_status(self._device_id, self._number)
if value is None:
return None
return bool(value == "closing")
@property
def is_opening(self) -> bool:
def is_opening(self) -> bool | None:
"""Update is opening attribute."""
return (
STATES_MAP.get(self._acc.get_door_status(self._device_id, self._number))
== STATE_OPENING
)
value = self._acc.get_door_status(self._device_id, self._number)
if value is None:
return None
return bool(value == "opening")

View file

@ -1,28 +0,0 @@
"""Diagnostics support for Aladdin Connect."""
from __future__ import annotations
from typing import Any
from AIOAladdinConnect import AladdinConnectClient
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
TO_REDACT = {"serial", "device_id"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
config_entry: ConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
acc: AladdinConnectClient = hass.data[DOMAIN][config_entry.entry_id]
return {
"doors": async_redact_data(acc.doors, TO_REDACT),
}

View file

@ -1,11 +1,10 @@
{
"domain": "aladdin_connect",
"name": "Aladdin Connect",
"codeowners": ["@mkmer"],
"codeowners": ["@swcloudgenie"],
"config_flow": true,
"dependencies": ["application_credentials"],
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
"iot_class": "cloud_polling",
"loggers": ["aladdin_connect"],
"quality_scale": "platinum",
"requirements": ["AIOAladdinConnect==0.1.58"]
"requirements": ["genie-partner-sdk==1.0.2"]
}

View file

@ -5,12 +5,26 @@ from __future__ import annotations
from typing import TypedDict
class DoorDevice(TypedDict):
"""Aladdin door device."""
class GarageDoorData(TypedDict):
"""Aladdin door data."""
device_id: str
door_number: int
name: str
status: str
serial: str
model: str
link_status: str
battery_level: int
class GarageDoor:
"""Aladdin Garage Door Entity."""
def __init__(self, data: GarageDoorData) -> None:
"""Create `GarageDoor` from dictionary of data."""
self.device_id = data["device_id"]
self.door_number = data["door_number"]
self.unique_id = f"{self.device_id}-{self.door_number}"
self.name = data["name"]
self.status = data["status"]
self.link_status = data["link_status"]
self.battery_level = data["battery_level"]

View file

@ -6,7 +6,7 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from AIOAladdinConnect import AladdinConnectClient
from genie_partner_sdk.client import AladdinConnectClient
from homeassistant.components.sensor import (
SensorDeviceClass,
@ -15,13 +15,14 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, SIGNAL_STRENGTH_DECIBELS
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import api
from .const import DOMAIN
from .model import DoorDevice
from .model import GarageDoor
@dataclass(frozen=True, kw_only=True)
@ -40,24 +41,6 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_battery_status,
),
AccSensorEntityDescription(
key="rssi",
translation_key="wifi_strength",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_rssi_status,
),
AccSensorEntityDescription(
key="ble_strength",
translation_key="ble_strength",
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
entity_registry_enabled_default=False,
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
state_class=SensorStateClass.MEASUREMENT,
value_fn=AladdinConnectClient.get_ble_strength,
),
)
@ -66,7 +49,8 @@ async def async_setup_entry(
) -> None:
"""Set up Aladdin Connect sensor devices."""
acc: AladdinConnectClient = hass.data[DOMAIN][entry.entry_id]
session: api.AsyncConfigEntryAuth = hass.data[DOMAIN][entry.entry_id]
acc = AladdinConnectClient(session)
entities = []
doors = await acc.get_doors()
@ -88,26 +72,20 @@ class AladdinConnectSensor(SensorEntity):
def __init__(
self,
acc: AladdinConnectClient,
device: DoorDevice,
device: GarageDoor,
description: AccSensorEntityDescription,
) -> None:
"""Initialize a sensor for an Aladdin Connect device."""
self._device_id = device["device_id"]
self._number = device["door_number"]
self._device_id = device.device_id
self._number = device.door_number
self._acc = acc
self.entity_description = description
self._attr_unique_id = f"{self._device_id}-{self._number}-{description.key}"
self._attr_unique_id = f"{device.unique_id}-{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"{self._device_id}-{self._number}")},
name=device["name"],
identifiers={(DOMAIN, device.unique_id)},
name=device.name,
manufacturer="Overhead Door",
model=device["model"],
)
if device["model"] == "01" and description.key in (
"battery_level",
"ble_strength",
):
self._attr_entity_registry_enabled_default = True
@property
def native_value(self) -> float | None:

View file

@ -1,39 +1,29 @@
{
"config": {
"step": {
"user": {
"data": {
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]"
}
"pick_implementation": {
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
},
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
"description": "The Aladdin Connect integration needs to re-authenticate your account",
"data": {
"password": "[%key:common::config_flow::data::password%]"
}
"description": "Aladdin Connect needs to re-authenticate your account"
}
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"oauth_error": "[%key:common::config_flow::abort::oauth2_error%]",
"oauth_failed": "[%key:common::config_flow::abort::oauth2_failed%]",
"oauth_timeout": "[%key:common::config_flow::abort::oauth2_timeout%]",
"oauth_unauthorized": "[%key:common::config_flow::abort::oauth2_unauthorized%]",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",
"no_url_available": "[%key:common::config_flow::abort::oauth2_no_url_available%]",
"user_rejected_authorize": "[%key:common::config_flow::abort::oauth2_user_rejected_authorize%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}
},
"entity": {
"sensor": {
"wifi_strength": {
"name": "Wi-Fi RSSI"
},
"ble_strength": {
"name": "BLE Strength"
}
},
"create_entry": {
"default": "[%key:common::config_flow::create_entry::authenticated%]"
}
}
}

View file

@ -4,6 +4,7 @@ To update, run python3 -m script.hassfest
"""
APPLICATION_CREDENTIALS = [
"aladdin_connect",
"electric_kiwi",
"fitbit",
"geocaching",

View file

@ -6,9 +6,6 @@
# homeassistant.components.aemet
AEMET-OpenData==0.5.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.58
# homeassistant.components.honeywell
AIOSomecomfort==0.0.25
@ -923,6 +920,9 @@ gassist-text==0.0.11
# homeassistant.components.google
gcal-sync==6.0.4
# homeassistant.components.aladdin_connect
genie-partner-sdk==1.0.2
# homeassistant.components.geniushub
geniushub-client==0.7.1

View file

@ -6,9 +6,6 @@
# homeassistant.components.aemet
AEMET-OpenData==0.5.1
# homeassistant.components.aladdin_connect
AIOAladdinConnect==0.1.58
# homeassistant.components.honeywell
AIOSomecomfort==0.0.25
@ -758,6 +755,9 @@ gassist-text==0.0.11
# homeassistant.components.google
gcal-sync==6.0.4
# homeassistant.components.aladdin_connect
genie-partner-sdk==1.0.2
# homeassistant.components.geocaching
geocachingapi==0.2.1

View file

@ -1 +1 @@
"""The tests for Aladdin Connect platforms."""
"""Tests for the Aladdin Connect Garage Door integration."""

View file

@ -1,48 +0,0 @@
"""Fixtures for the Aladdin Connect integration tests."""
from unittest import mock
from unittest.mock import AsyncMock
import pytest
DEVICE_CONFIG_OPEN = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "open",
"link_status": "Connected",
"serial": "12345",
"model": "02",
"rssi": -67,
"ble_strength": 0,
"vendor": "GENIE",
"battery_level": 0,
}
@pytest.fixture(name="mock_aladdinconnect_api")
def fixture_mock_aladdinconnect_api():
"""Set up aladdin connect API fixture."""
with mock.patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient"
) as mock_opener:
mock_opener.login = AsyncMock(return_value=True)
mock_opener.close = AsyncMock(return_value=True)
mock_opener.async_get_door_status = AsyncMock(return_value="open")
mock_opener.get_door_status.return_value = "open"
mock_opener.async_get_door_link_status = AsyncMock(return_value="connected")
mock_opener.get_door_link_status.return_value = "connected"
mock_opener.async_get_battery_status = AsyncMock(return_value="99")
mock_opener.get_battery_status.return_value = "99"
mock_opener.async_get_rssi_status = AsyncMock(return_value="-55")
mock_opener.get_rssi_status.return_value = "-55"
mock_opener.async_get_ble_strength = AsyncMock(return_value="-45")
mock_opener.get_ble_strength.return_value = "-45"
mock_opener.get_doors = AsyncMock(return_value=[DEVICE_CONFIG_OPEN])
mock_opener.doors = [DEVICE_CONFIG_OPEN]
mock_opener.register_callback = mock.Mock(return_value=True)
mock_opener.open_door = AsyncMock(return_value=True)
mock_opener.close_door = AsyncMock(return_value=True)
return mock_opener

View file

@ -1,20 +0,0 @@
# serializer version: 1
# name: test_entry_diagnostics
dict({
'doors': list([
dict({
'battery_level': 0,
'ble_strength': 0,
'device_id': '**REDACTED**',
'door_number': 1,
'link_status': 'Connected',
'model': '02',
'name': 'home',
'rssi': -67,
'serial': '**REDACTED**',
'status': 'open',
'vendor': 'GENIE',
}),
]),
})
# ---

View file

@ -1,278 +1,82 @@
"""Test the Aladdin Connect config flow."""
"""Test the Aladdin Connect Garage Door config flow."""
from unittest.mock import MagicMock, patch
from unittest.mock import patch
from AIOAladdinConnect.session_manager import InvalidPasswordError
from aiohttp.client_exceptions import ClientConnectionError
import pytest
from homeassistant import config_entries
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.components.aladdin_connect.const import (
DOMAIN,
OAUTH2_AUTHORIZE,
OAUTH2_TOKEN,
)
from homeassistant.components.application_credentials import (
ClientCredential,
async_import_client_credential,
)
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
CLIENT_ID = "1234"
CLIENT_SECRET = "5678"
async def test_form(hass: HomeAssistant, mock_aladdinconnect_api: MagicMock) -> 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"] is None
with (
patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
),
patch(
"homeassistant.components.aladdin_connect.async_setup_entry",
return_value=True,
) as mock_setup_entry,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == "Aladdin Connect"
assert result2["data"] == {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
}
assert len(mock_setup_entry.mock_calls) == 1
async def test_form_failed_auth(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test we handle failed authentication error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
mock_aladdinconnect_api.login.return_value = False
mock_aladdinconnect_api.login.side_effect = InvalidPasswordError
with patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_form_connection_timeout(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test we handle http timeout error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
mock_aladdinconnect_api.login.side_effect = ClientConnectionError
with patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_already_configured(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test we handle already configured error."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={CONF_USERNAME: "test-username", CONF_PASSWORD: "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == config_entries.SOURCE_USER
with patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
CONF_USERNAME: "test-username",
CONF_PASSWORD: "test-password",
},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "already_configured"
async def test_reauth_flow(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test a successful reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-username", "password": "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
@pytest.fixture
async def setup_credentials(hass: HomeAssistant) -> None:
"""Fixture to setup credentials."""
assert await async_setup_component(hass, "application_credentials", {})
await async_import_client_credential(
hass,
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
},
data={"username": "test-username", "password": "new-password"},
ClientCredential(CLIENT_ID, CLIENT_SECRET),
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
with (
patch(
"homeassistant.components.aladdin_connect.async_setup_entry",
return_value=True,
),
patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.ABORT
assert result2["reason"] == "reauth_successful"
assert mock_entry.data == {
CONF_USERNAME: "test-username",
CONF_PASSWORD: "new-password",
}
async def test_reauth_flow_auth_error(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
async def test_full_flow(
hass: HomeAssistant,
hass_client_no_auth,
aioclient_mock,
current_request_with_host,
setup_credentials,
) -> None:
"""Test an authorization error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-username", "password": "test-password"},
unique_id="test-username",
)
mock_entry.add_to_hass(hass)
"""Check full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
state = config_entry_oauth2_flow._encode_jwt( # noqa: SLF001
hass,
{
"flow_id": result["flow_id"],
"redirect_uri": "https://example.com/auth/external/callback",
},
data={"username": "test-username", "password": "new-password"},
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
mock_aladdinconnect_api.login.return_value = False
mock_aladdinconnect_api.login.side_effect = InvalidPasswordError
with (
patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
),
patch(
"homeassistant.components.aladdin_connect.cover.async_setup_entry",
return_value=True,
),
patch(
"homeassistant.components.aladdin_connect.cover.async_setup_entry",
return_value=True,
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "invalid_auth"}
async def test_reauth_flow_connnection_error(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test a connection error reauth flow."""
mock_entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-username", "password": "test-password"},
unique_id="test-username",
assert result["url"] == (
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
)
mock_entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={
"source": config_entries.SOURCE_REAUTH,
"unique_id": mock_entry.unique_id,
"entry_id": mock_entry.entry_id,
client = await hass_client_no_auth()
resp = await client.get(f"/auth/external/callback?code=abcd&state={state}")
assert resp.status == 200
assert resp.headers["content-type"] == "text/html; charset=utf-8"
aioclient_mock.post(
OAUTH2_TOKEN,
json={
"refresh_token": "mock-refresh-token",
"access_token": "mock-access-token",
"type": "Bearer",
"expires_in": 60,
},
data={"username": "test-username", "password": "new-password"},
)
assert result["step_id"] == "reauth_confirm"
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {}
mock_aladdinconnect_api.login.side_effect = ClientConnectionError
with patch(
"homeassistant.components.aladdin_connect.config_flow.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_PASSWORD: "new-password"},
)
await hass.async_block_till_done()
"homeassistant.components.aladdin_connect.async_setup_entry", return_value=True
) as mock_setup:
await hass.config_entries.flow.async_configure(result["flow_id"])
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {"base": "cannot_connect"}
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert len(mock_setup.mock_calls) == 1

View file

@ -1,228 +0,0 @@
"""Test the Aladdin Connect Cover."""
from unittest.mock import AsyncMock, MagicMock, patch
from AIOAladdinConnect import session_manager
import pytest
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.components.aladdin_connect.cover import SCAN_INTERVAL
from homeassistant.components.cover import DOMAIN as COVER_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_CLOSE_COVER,
SERVICE_OPEN_COVER,
STATE_CLOSED,
STATE_CLOSING,
STATE_OPEN,
STATE_OPENING,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
YAML_CONFIG = {"username": "test-user", "password": "test-password"}
DEVICE_CONFIG_OPEN = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "open",
"link_status": "Connected",
"serial": "12345",
}
DEVICE_CONFIG_OPENING = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "opening",
"link_status": "Connected",
"serial": "12345",
}
DEVICE_CONFIG_CLOSED = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "closed",
"link_status": "Connected",
"serial": "12345",
}
DEVICE_CONFIG_CLOSING = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "closing",
"link_status": "Connected",
"serial": "12345",
}
DEVICE_CONFIG_DISCONNECTED = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "open",
"link_status": "Disconnected",
"serial": "12345",
}
DEVICE_CONFIG_BAD = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "open",
}
DEVICE_CONFIG_BAD_NO_DOOR = {
"device_id": 533255,
"door_number": 2,
"name": "home",
"status": "open",
"link_status": "Disconnected",
}
async def test_cover_operation(
hass: HomeAssistant,
mock_aladdinconnect_api: MagicMock,
) -> None:
"""Test Cover Operation states (open,close,opening,closing) cover."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=YAML_CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
assert await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
mock_aladdinconnect_api.async_get_door_status = AsyncMock(return_value=STATE_OPEN)
mock_aladdinconnect_api.get_door_status.return_value = STATE_OPEN
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert COVER_DOMAIN in hass.config.components
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: "cover.home"},
blocking=True,
)
assert hass.states.get("cover.home").state == STATE_OPEN
mock_aladdinconnect_api.open_door.return_value = False
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_OPEN_COVER,
{ATTR_ENTITY_ID: "cover.home"},
blocking=True,
)
mock_aladdinconnect_api.open_door.return_value = True
mock_aladdinconnect_api.async_get_door_status = AsyncMock(return_value=STATE_CLOSED)
mock_aladdinconnect_api.get_door_status.return_value = STATE_CLOSED
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.home"},
blocking=True,
)
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_CLOSED
mock_aladdinconnect_api.close_door.return_value = False
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.home"},
blocking=True,
)
mock_aladdinconnect_api.close_door.return_value = True
mock_aladdinconnect_api.async_get_door_status = AsyncMock(
return_value=STATE_CLOSING
)
mock_aladdinconnect_api.get_door_status.return_value = STATE_CLOSING
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_CLOSING
mock_aladdinconnect_api.async_get_door_status = AsyncMock(
return_value=STATE_OPENING
)
mock_aladdinconnect_api.get_door_status.return_value = STATE_OPENING
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_OPENING
mock_aladdinconnect_api.async_get_door_status = AsyncMock(return_value=None)
mock_aladdinconnect_api.get_door_status.return_value = None
await hass.services.async_call(
COVER_DOMAIN,
SERVICE_CLOSE_COVER,
{ATTR_ENTITY_ID: "cover.home"},
blocking=True,
)
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_UNKNOWN
mock_aladdinconnect_api.get_doors.side_effect = session_manager.ConnectionError
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_UNAVAILABLE
mock_aladdinconnect_api.get_doors.side_effect = session_manager.InvalidPasswordError
mock_aladdinconnect_api.login.return_value = False
mock_aladdinconnect_api.login.side_effect = session_manager.InvalidPasswordError
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
assert hass.states.get("cover.home").state == STATE_UNAVAILABLE

View file

@ -1,41 +0,0 @@
"""Test AccuWeather diagnostics."""
from unittest.mock import MagicMock, patch
from syrupy import SnapshotAssertion
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
from tests.components.diagnostics import get_diagnostics_for_config_entry
from tests.typing import ClientSessionGenerator
YAML_CONFIG = {"username": "test-user", "password": "test-password"}
async def test_entry_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
snapshot: SnapshotAssertion,
mock_aladdinconnect_api: MagicMock,
) -> None:
"""Test config entry diagnostics."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=YAML_CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
result = await get_diagnostics_for_config_entry(hass, hass_client, config_entry)
assert result == snapshot

View file

@ -1,258 +0,0 @@
"""Test for Aladdin Connect init logic."""
from unittest.mock import MagicMock, patch
from AIOAladdinConnect.session_manager import InvalidPasswordError
from aiohttp import ClientConnectionError
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .conftest import DEVICE_CONFIG_OPEN
from tests.common import AsyncMock, MockConfigEntry
CONFIG = {"username": "test-user", "password": "test-password"}
ID = "533255-1"
async def test_setup_get_doors_errors(hass: HomeAssistant) -> None:
"""Test component setup Get Doors Errors."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
with (
patch(
"homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login",
return_value=True,
),
patch(
"homeassistant.components.aladdin_connect.cover.AladdinConnectClient.get_doors",
return_value=None,
),
):
assert await hass.config_entries.async_setup(config_entry.entry_id) is True
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
async def test_setup_login_error(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test component setup Login Errors."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id=ID,
)
config_entry.add_to_hass(hass)
mock_aladdinconnect_api.login.return_value = False
mock_aladdinconnect_api.login.side_effect = InvalidPasswordError
with patch(
"homeassistant.components.aladdin_connect.cover.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
assert await hass.config_entries.async_setup(config_entry.entry_id) is False
async def test_setup_connection_error(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test component setup Login Errors."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id=ID,
)
config_entry.add_to_hass(hass)
mock_aladdinconnect_api.login.side_effect = ClientConnectionError
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
assert await hass.config_entries.async_setup(config_entry.entry_id) is False
async def test_setup_component_no_error(hass: HomeAssistant) -> None:
"""Test component setup No Error."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id=ID,
)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.aladdin_connect.cover.AladdinConnectClient.login",
return_value=True,
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
async def test_entry_password_fail(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test password fail during entry."""
entry = MockConfigEntry(
domain=DOMAIN,
data={"username": "test-user", "password": "test-password"},
)
entry.add_to_hass(hass)
mock_aladdinconnect_api.login = AsyncMock(return_value=False)
mock_aladdinconnect_api.login.side_effect = InvalidPasswordError
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
assert entry.state is ConfigEntryState.SETUP_ERROR
async def test_load_and_unload(
hass: HomeAssistant, mock_aladdinconnect_api: MagicMock
) -> None:
"""Test loading and unloading Aladdin Connect entry."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id=ID,
)
config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED
async def test_stale_device_removal(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
mock_aladdinconnect_api: MagicMock,
) -> None:
"""Test component setup missing door device is removed."""
DEVICE_CONFIG_DOOR_2 = {
"device_id": 533255,
"door_number": 2,
"name": "home 2",
"status": "open",
"link_status": "Connected",
"serial": "12346",
"model": "02",
}
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id=ID,
)
config_entry.add_to_hass(hass)
mock_aladdinconnect_api.get_doors = AsyncMock(
return_value=[DEVICE_CONFIG_OPEN, DEVICE_CONFIG_DOOR_2]
)
config_entry_other = MockConfigEntry(
domain="OtherDomain",
data=CONFIG,
unique_id="unique_id",
)
config_entry_other.add_to_hass(hass)
device_entry_other = device_registry.async_get_or_create(
config_entry_id=config_entry_other.entry_id,
identifiers={("OtherDomain", "533255-2")},
)
device_registry.async_update_device(
device_entry_other.id,
add_config_entry_id=config_entry.entry_id,
merge_identifiers={(DOMAIN, "533255-2")},
)
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
device_entries = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
assert len(device_entries) == 2
assert any((DOMAIN, "533255-1") in device.identifiers for device in device_entries)
assert any((DOMAIN, "533255-2") in device.identifiers for device in device_entries)
assert any(
("OtherDomain", "533255-2") in device.identifiers for device in device_entries
)
device_entries_other = dr.async_entries_for_config_entry(
device_registry, config_entry_other.entry_id
)
assert len(device_entries_other) == 1
assert any(
(DOMAIN, "533255-2") in device.identifiers for device in device_entries_other
)
assert any(
("OtherDomain", "533255-2") in device.identifiers
for device in device_entries_other
)
assert await hass.config_entries.async_unload(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.NOT_LOADED
mock_aladdinconnect_api.get_doors = AsyncMock(return_value=[DEVICE_CONFIG_OPEN])
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.LOADED
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
device_entries = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
assert len(device_entries) == 1
assert any((DOMAIN, "533255-1") in device.identifiers for device in device_entries)
assert not any(
(DOMAIN, "533255-2") in device.identifiers for device in device_entries
)
assert not any(
("OtherDomain", "533255-2") in device.identifiers for device in device_entries
)
device_entries_other = dr.async_entries_for_config_entry(
device_registry, config_entry_other.entry_id
)
assert len(device_entries_other) == 1
assert any(
("OtherDomain", "533255-2") in device.identifiers
for device in device_entries_other
)
assert any(
(DOMAIN, "533255-2") in device.identifiers for device in device_entries_other
)

View file

@ -1,19 +0,0 @@
"""Test the Aladdin Connect model class."""
from homeassistant.components.aladdin_connect.model import DoorDevice
from homeassistant.core import HomeAssistant
async def test_model(hass: HomeAssistant) -> None:
"""Test model for Aladdin Connect Model."""
test_values = {
"device_id": "1",
"door_number": "2",
"name": "my door",
"status": "good",
}
result2 = DoorDevice(test_values)
assert result2["device_id"] == "1"
assert result2["door_number"] == "2"
assert result2["name"] == "my door"
assert result2["status"] == "good"

View file

@ -1,165 +0,0 @@
"""Test the Aladdin Connect Sensors."""
from datetime import timedelta
from unittest.mock import AsyncMock, MagicMock, patch
from homeassistant.components.aladdin_connect.const import DOMAIN
from homeassistant.components.aladdin_connect.cover import SCAN_INTERVAL
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util.dt import utcnow
from tests.common import MockConfigEntry, async_fire_time_changed
DEVICE_CONFIG_MODEL_01 = {
"device_id": 533255,
"door_number": 1,
"name": "home",
"status": "closed",
"link_status": "Connected",
"serial": "12345",
"model": "01",
}
CONFIG = {"username": "test-user", "password": "test-password"}
RELOAD_AFTER_UPDATE_DELAY = timedelta(seconds=31)
async def test_sensors(
hass: HomeAssistant,
mock_aladdinconnect_api: MagicMock,
entity_registry: er.EntityRegistry,
) -> None:
"""Test Sensors for AladdinConnect."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
await hass.async_block_till_done()
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
entry = entity_registry.async_get("sensor.home_battery")
assert entry
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
update_entry = entity_registry.async_update_entity(
entry.entity_id, disabled_by=None
)
await hass.async_block_till_done()
assert update_entry != entry
assert update_entry.disabled is False
state = hass.states.get("sensor.home_battery")
assert state is None
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.home_battery")
assert state
entry = entity_registry.async_get("sensor.home_wi_fi_rssi")
await hass.async_block_till_done()
assert entry
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
update_entry = entity_registry.async_update_entity(
entry.entity_id, disabled_by=None
)
await hass.async_block_till_done()
assert update_entry != entry
assert update_entry.disabled is False
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state is None
update_entry = entity_registry.async_update_entity(
entry.entity_id, disabled_by=None
)
await hass.async_block_till_done()
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state
async def test_sensors_model_01(
hass: HomeAssistant,
mock_aladdinconnect_api: MagicMock,
entity_registry: er.EntityRegistry,
) -> None:
"""Test Sensors for AladdinConnect."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=CONFIG,
unique_id="test-id",
)
config_entry.add_to_hass(hass)
await hass.async_block_till_done()
with patch(
"homeassistant.components.aladdin_connect.AladdinConnectClient",
return_value=mock_aladdinconnect_api,
):
mock_aladdinconnect_api.get_doors = AsyncMock(
return_value=[DEVICE_CONFIG_MODEL_01]
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
entry = entity_registry.async_get("sensor.home_battery")
assert entry
assert entry.disabled is False
assert entry.disabled_by is None
state = hass.states.get("sensor.home_battery")
assert state
entry = entity_registry.async_get("sensor.home_wi_fi_rssi")
await hass.async_block_till_done()
assert entry
assert entry.disabled
assert entry.disabled_by is er.RegistryEntryDisabler.INTEGRATION
update_entry = entity_registry.async_update_entity(
entry.entity_id, disabled_by=None
)
await hass.async_block_till_done()
assert update_entry != entry
assert update_entry.disabled is False
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state is None
update_entry = entity_registry.async_update_entity(
entry.entity_id, disabled_by=None
)
await hass.async_block_till_done()
async_fire_time_changed(
hass,
utcnow() + SCAN_INTERVAL,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.home_wi_fi_rssi")
assert state
entry = entity_registry.async_get("sensor.home_ble_strength")
await hass.async_block_till_done()
assert entry
assert entry.disabled is False
assert entry.disabled_by is None
state = hass.states.get("sensor.home_ble_strength")
assert state