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:
parent
f93a3127f2
commit
a670169325
25 changed files with 286 additions and 1354 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
31
homeassistant/components/aladdin_connect/api.py
Normal file
31
homeassistant/components/aladdin_connect/api.py
Normal 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"])
|
|
@ -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,
|
||||
)
|
|
@ -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__)
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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),
|
||||
}
|
|
@ -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"]
|
||||
}
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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%]"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ To update, run python3 -m script.hassfest
|
|||
"""
|
||||
|
||||
APPLICATION_CREDENTIALS = [
|
||||
"aladdin_connect",
|
||||
"electric_kiwi",
|
||||
"fitbit",
|
||||
"geocaching",
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -1 +1 @@
|
|||
"""The tests for Aladdin Connect platforms."""
|
||||
"""Tests for the Aladdin Connect Garage Door integration."""
|
||||
|
|
|
@ -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
|
|
@ -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',
|
||||
}),
|
||||
]),
|
||||
})
|
||||
# ---
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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"
|
|
@ -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
|
Loading…
Add table
Reference in a new issue