mirror of
https://github.com/home-assistant/core.git
synced 2025-04-23 16:57:53 +00:00
Remove Aladdin Connect integration (#120980)
This commit is contained in:
parent
24afbde79e
commit
98a2e46d4a
@ -58,11 +58,6 @@ 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/sensor.py
|
||||
homeassistant/components/alarmdecoder/__init__.py
|
||||
homeassistant/components/alarmdecoder/alarm_control_panel.py
|
||||
homeassistant/components/alarmdecoder/binary_sensor.py
|
||||
|
@ -80,8 +80,6 @@ build.json @home-assistant/supervisor
|
||||
/tests/components/airzone/ @Noltari
|
||||
/homeassistant/components/airzone_cloud/ @Noltari
|
||||
/tests/components/airzone_cloud/ @Noltari
|
||||
/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,94 +1,38 @@
|
||||
"""The Aladdin Connect Genie integration."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import device_registry as dr
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import (
|
||||
OAuth2Session,
|
||||
async_get_config_entry_implementation,
|
||||
)
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from .api import AsyncConfigEntryAuth
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AladdinConnectCoordinator
|
||||
|
||||
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
|
||||
|
||||
type AladdinConnectConfigEntry = ConfigEntry[AladdinConnectCoordinator]
|
||||
DOMAIN = "aladdin_connect"
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant, entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Set up Aladdin Connect Genie from a config entry."""
|
||||
implementation = await async_get_config_entry_implementation(hass, entry)
|
||||
|
||||
session = OAuth2Session(hass, entry, implementation)
|
||||
auth = AsyncConfigEntryAuth(async_get_clientsession(hass), session)
|
||||
coordinator = AladdinConnectCoordinator(hass, AladdinConnectClient(auth))
|
||||
|
||||
await coordinator.async_setup()
|
||||
await coordinator.async_config_entry_first_refresh()
|
||||
|
||||
entry.runtime_data = coordinator
|
||||
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
async_remove_stale_devices(hass, entry)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_unload_entry(
|
||||
hass: HomeAssistant, entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_migrate_entry(
|
||||
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
|
||||
) -> bool:
|
||||
"""Migrate old config."""
|
||||
if config_entry.version < 2:
|
||||
config_entry.async_start_reauth(hass)
|
||||
hass.config_entries.async_update_entry(
|
||||
config_entry,
|
||||
version=2,
|
||||
minor_version=1,
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def async_remove_stale_devices(
|
||||
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
|
||||
) -> 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
|
||||
async def async_setup_entry(hass: HomeAssistant, _: ConfigEntry) -> bool:
|
||||
"""Set up Aladdin Connect from a config entry."""
|
||||
ir.async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
DOMAIN,
|
||||
is_fixable=False,
|
||||
severity=ir.IssueSeverity.ERROR,
|
||||
translation_key="integration_removed",
|
||||
translation_placeholders={
|
||||
"entries": "/config/integrations/integration/aladdin_connect",
|
||||
},
|
||||
)
|
||||
all_device_ids = {door.unique_id for door in config_entry.runtime_data.doors}
|
||||
|
||||
for device_entry in device_entries:
|
||||
device_id: str | None = None
|
||||
return True
|
||||
|
||||
for identifier in device_entry.identifiers:
|
||||
if identifier[0] == DOMAIN:
|
||||
device_id = identifier[1]
|
||||
break
|
||||
|
||||
if device_id is None or device_id not in all_device_ids:
|
||||
# If device_id is None an invalid device entry was found for this config entry.
|
||||
# If the device_id is not in existing device ids it's a stale device entry.
|
||||
# Remove config entry from this device entry in either case.
|
||||
device_registry.async_update_device(
|
||||
device_entry.id, remove_config_entry_id=config_entry.entry_id
|
||||
)
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
if all(
|
||||
config_entry.state is ConfigEntryState.NOT_LOADED
|
||||
for config_entry in hass.config_entries.async_entries(DOMAIN)
|
||||
if config_entry.entry_id != entry.entry_id
|
||||
):
|
||||
ir.async_delete_issue(hass, DOMAIN, DOMAIN)
|
||||
|
||||
return True
|
||||
|
@ -1,33 +0,0 @@
|
||||
"""API for Aladdin Connect Genie bound to Home Assistant OAuth."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from typing import cast
|
||||
|
||||
from aiohttp import ClientSession
|
||||
|
||||
# from genie_partner_sdk.auth import Auth
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
|
||||
|
||||
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: 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."""
|
||||
await self._oauth_session.async_ensure_token_valid()
|
||||
|
||||
return cast(str, self._oauth_session.token["access_token"])
|
@ -1,14 +0,0 @@
|
||||
"""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,70 +1,11 @@
|
||||
"""Config flow for Aladdin Connect Genie."""
|
||||
"""Config flow for Aladdin Connect integration."""
|
||||
|
||||
from collections.abc import Mapping
|
||||
import logging
|
||||
from typing import Any
|
||||
from homeassistant.config_entries import ConfigFlow
|
||||
|
||||
import jwt
|
||||
|
||||
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
|
||||
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
|
||||
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
|
||||
|
||||
from .const import DOMAIN
|
||||
from . import DOMAIN
|
||||
|
||||
|
||||
class AladdinConnectOAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
|
||||
"""Config flow to handle Aladdin Connect Genie OAuth2 authentication."""
|
||||
class AladdinConnectConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for Aladdin Connect."""
|
||||
|
||||
DOMAIN = DOMAIN
|
||||
VERSION = 2
|
||||
MINOR_VERSION = 1
|
||||
|
||||
reauth_entry: ConfigEntry | None = None
|
||||
|
||||
async def async_step_reauth(
|
||||
self, user_input: Mapping[str, Any]
|
||||
) -> ConfigFlowResult:
|
||||
"""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: Mapping[str, Any] | None = None
|
||||
) -> ConfigFlowResult:
|
||||
"""Dialog that informs the user that reauth is required."""
|
||||
if user_input is None:
|
||||
return self.async_show_form(step_id="reauth_confirm")
|
||||
return await self.async_step_user()
|
||||
|
||||
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
|
||||
"""Create an oauth config entry or update existing entry for reauth."""
|
||||
token_payload = jwt.decode(
|
||||
data[CONF_TOKEN][CONF_ACCESS_TOKEN], options={"verify_signature": False}
|
||||
)
|
||||
if not self.reauth_entry:
|
||||
await self.async_set_unique_id(token_payload["sub"])
|
||||
self._abort_if_unique_id_configured()
|
||||
|
||||
return self.async_create_entry(
|
||||
title=token_payload["username"],
|
||||
data=data,
|
||||
)
|
||||
|
||||
if self.reauth_entry.unique_id == token_payload["username"]:
|
||||
return self.async_update_reload_and_abort(
|
||||
self.reauth_entry,
|
||||
data=data,
|
||||
unique_id=token_payload["sub"],
|
||||
)
|
||||
if self.reauth_entry.unique_id == token_payload["sub"]:
|
||||
return self.async_update_reload_and_abort(self.reauth_entry, data=data)
|
||||
|
||||
return self.async_abort(reason="wrong_account")
|
||||
|
||||
@property
|
||||
def logger(self) -> logging.Logger:
|
||||
"""Return logger."""
|
||||
return logging.getLogger(__name__)
|
||||
VERSION = 1
|
||||
|
@ -1,6 +0,0 @@
|
||||
"""Constants for the Aladdin Connect Genie integration."""
|
||||
|
||||
DOMAIN = "aladdin_connect"
|
||||
|
||||
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.net/login.html"
|
||||
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
|
@ -1,38 +0,0 @@
|
||||
"""Define an object to coordinate fetching Aladdin Connect data."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class AladdinConnectCoordinator(DataUpdateCoordinator[None]):
|
||||
"""Aladdin Connect Data Update Coordinator."""
|
||||
|
||||
def __init__(self, hass: HomeAssistant, acc: AladdinConnectClient) -> None:
|
||||
"""Initialize."""
|
||||
super().__init__(
|
||||
hass,
|
||||
logger=_LOGGER,
|
||||
name=DOMAIN,
|
||||
update_interval=timedelta(seconds=15),
|
||||
)
|
||||
self.acc = acc
|
||||
self.doors: list[GarageDoor] = []
|
||||
|
||||
async def async_setup(self) -> None:
|
||||
"""Fetch initial data."""
|
||||
self.doors = await self.acc.get_doors()
|
||||
|
||||
async def _async_update_data(self) -> None:
|
||||
"""Fetch data from API endpoint."""
|
||||
for door in self.doors:
|
||||
await self.acc.update_door(door.device_id, door.door_number)
|
@ -1,84 +0,0 @@
|
||||
"""Cover Entity for Genie Garage Door."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from typing import Any
|
||||
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.components.cover import (
|
||||
CoverDeviceClass,
|
||||
CoverEntity,
|
||||
CoverEntityFeature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
|
||||
from .entity import AladdinConnectEntity
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
config_entry: AladdinConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up the Aladdin Connect platform."""
|
||||
coordinator = config_entry.runtime_data
|
||||
|
||||
async_add_entities(AladdinDevice(coordinator, door) for door in coordinator.doors)
|
||||
|
||||
|
||||
class AladdinDevice(AladdinConnectEntity, CoverEntity):
|
||||
"""Representation of Aladdin Connect cover."""
|
||||
|
||||
_attr_device_class = CoverDeviceClass.GARAGE
|
||||
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
|
||||
_attr_name = None
|
||||
|
||||
def __init__(
|
||||
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
|
||||
) -> None:
|
||||
"""Initialize the Aladdin Connect cover."""
|
||||
super().__init__(coordinator, device)
|
||||
self._attr_unique_id = device.unique_id
|
||||
|
||||
async def async_open_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue open command to cover."""
|
||||
await self.coordinator.acc.open_door(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
|
||||
async def async_close_cover(self, **kwargs: Any) -> None:
|
||||
"""Issue close command to cover."""
|
||||
await self.coordinator.acc.close_door(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
|
||||
@property
|
||||
def is_closed(self) -> bool | None:
|
||||
"""Update is closed attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "closed")
|
||||
|
||||
@property
|
||||
def is_closing(self) -> bool | None:
|
||||
"""Update is closing attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "closing")
|
||||
|
||||
@property
|
||||
def is_opening(self) -> bool | None:
|
||||
"""Update is opening attribute."""
|
||||
value = self.coordinator.acc.get_door_status(
|
||||
self._device.device_id, self._device.door_number
|
||||
)
|
||||
if value is None:
|
||||
return None
|
||||
return bool(value == "opening")
|
@ -1,27 +0,0 @@
|
||||
"""Defines a base Aladdin Connect entity."""
|
||||
# mypy: ignore-errors
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
|
||||
from homeassistant.helpers.device_registry import DeviceInfo
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import AladdinConnectCoordinator
|
||||
|
||||
|
||||
class AladdinConnectEntity(CoordinatorEntity[AladdinConnectCoordinator]):
|
||||
"""Defines a base Aladdin Connect entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
def __init__(
|
||||
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator)
|
||||
self._device = device
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, device.unique_id)},
|
||||
name=device.name,
|
||||
manufacturer="Overhead Door",
|
||||
)
|
@ -1,11 +1,9 @@
|
||||
{
|
||||
"domain": "aladdin_connect",
|
||||
"name": "Aladdin Connect",
|
||||
"codeowners": ["@swcloudgenie"],
|
||||
"config_flow": true,
|
||||
"dependencies": ["application_credentials"],
|
||||
"disabled": "This integration is disabled because it uses non-open source code to operate.",
|
||||
"codeowners": [],
|
||||
"documentation": "https://www.home-assistant.io/integrations/aladdin_connect",
|
||||
"integration_type": "system",
|
||||
"iot_class": "cloud_polling",
|
||||
"requirements": ["genie-partner-sdk==1.0.2"]
|
||||
"requirements": []
|
||||
}
|
||||
|
@ -1,5 +0,0 @@
|
||||
extend = "../../../pyproject.toml"
|
||||
|
||||
lint.extend-ignore = [
|
||||
"F821"
|
||||
]
|
@ -1,80 +0,0 @@
|
||||
"""Support for Aladdin Connect Garage Door sensors."""
|
||||
|
||||
# mypy: ignore-errors
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass
|
||||
|
||||
# from genie_partner_sdk.client import AladdinConnectClient
|
||||
# from genie_partner_sdk.model import GarageDoor
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
SensorEntity,
|
||||
SensorEntityDescription,
|
||||
SensorStateClass,
|
||||
)
|
||||
from homeassistant.const import PERCENTAGE
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
|
||||
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
|
||||
from .entity import AladdinConnectEntity
|
||||
|
||||
|
||||
@dataclass(frozen=True, kw_only=True)
|
||||
class AccSensorEntityDescription(SensorEntityDescription):
|
||||
"""Describes AladdinConnect sensor entity."""
|
||||
|
||||
value_fn: Callable[[AladdinConnectClient, str, int], float | None]
|
||||
|
||||
|
||||
SENSORS: tuple[AccSensorEntityDescription, ...] = (
|
||||
AccSensorEntityDescription(
|
||||
key="battery_level",
|
||||
device_class=SensorDeviceClass.BATTERY,
|
||||
entity_registry_enabled_default=False,
|
||||
native_unit_of_measurement=PERCENTAGE,
|
||||
state_class=SensorStateClass.MEASUREMENT,
|
||||
value_fn=AladdinConnectClient.get_battery_status,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_entry(
|
||||
hass: HomeAssistant,
|
||||
entry: AladdinConnectConfigEntry,
|
||||
async_add_entities: AddEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Aladdin Connect sensor devices."""
|
||||
coordinator = entry.runtime_data
|
||||
|
||||
async_add_entities(
|
||||
AladdinConnectSensor(coordinator, door, description)
|
||||
for description in SENSORS
|
||||
for door in coordinator.doors
|
||||
)
|
||||
|
||||
|
||||
class AladdinConnectSensor(AladdinConnectEntity, SensorEntity):
|
||||
"""A sensor implementation for Aladdin Connect devices."""
|
||||
|
||||
entity_description: AccSensorEntityDescription
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: AladdinConnectCoordinator,
|
||||
device: GarageDoor,
|
||||
description: AccSensorEntityDescription,
|
||||
) -> None:
|
||||
"""Initialize a sensor for an Aladdin Connect device."""
|
||||
super().__init__(coordinator, device)
|
||||
self.entity_description = description
|
||||
self._attr_unique_id = f"{device.unique_id}-{description.key}"
|
||||
|
||||
@property
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the state of the sensor."""
|
||||
return self.entity_description.value_fn(
|
||||
self.coordinator.acc, self._device.device_id, self._device.door_number
|
||||
)
|
@ -1,29 +1,8 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"pick_implementation": {
|
||||
"title": "[%key:common::config_flow::title::oauth2_pick_implementation%]"
|
||||
},
|
||||
"reauth_confirm": {
|
||||
"title": "[%key:common::config_flow::title::reauth%]",
|
||||
"description": "Aladdin Connect needs to re-authenticate your account"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"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%]"
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
"issues": {
|
||||
"integration_removed": {
|
||||
"title": "The Aladdin Connect integration has been removed",
|
||||
"description": "The Aladdin Connect integration has been removed from Home Assistant.\n\nTo resolve this issue, please remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing Aladdin Connect integration entries]({entries})."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ To update, run python3 -m script.hassfest
|
||||
"""
|
||||
|
||||
APPLICATION_CREDENTIALS = [
|
||||
"aladdin_connect",
|
||||
"electric_kiwi",
|
||||
"fitbit",
|
||||
"geocaching",
|
||||
|
@ -42,7 +42,6 @@ FLOWS = {
|
||||
"airvisual_pro",
|
||||
"airzone",
|
||||
"airzone_cloud",
|
||||
"aladdin_connect",
|
||||
"alarmdecoder",
|
||||
"amberelectric",
|
||||
"ambient_network",
|
||||
|
@ -180,12 +180,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"aladdin_connect": {
|
||||
"name": "Aladdin Connect",
|
||||
"integration_type": "hub",
|
||||
"config_flow": true,
|
||||
"iot_class": "cloud_polling"
|
||||
},
|
||||
"alarmdecoder": {
|
||||
"name": "AlarmDecoder",
|
||||
"integration_type": "device",
|
||||
|
@ -1,29 +0,0 @@
|
||||
"""Test fixtures for the Aladdin Connect Garage Door integration."""
|
||||
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import pytest
|
||||
from typing_extensions import Generator
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
"""Override async_setup_entry."""
|
||||
with patch(
|
||||
"homeassistant.components.aladdin_connect.async_setup_entry", return_value=True
|
||||
) as mock_setup_entry:
|
||||
yield mock_setup_entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Return an Aladdin Connect config entry."""
|
||||
return MockConfigEntry(
|
||||
domain="aladdin_connect",
|
||||
data={},
|
||||
title="test@test.com",
|
||||
unique_id="aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
|
||||
version=2,
|
||||
)
|
@ -1,230 +0,0 @@
|
||||
"""Test the Aladdin Connect Garage Door config flow."""
|
||||
|
||||
# from unittest.mock import AsyncMock
|
||||
#
|
||||
# import pytest
|
||||
#
|
||||
# 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.config_entries import SOURCE_REAUTH, SOURCE_USER, ConfigFlowResult
|
||||
# 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
|
||||
# from tests.test_util.aiohttp import AiohttpClientMocker
|
||||
# from tests.typing import ClientSessionGenerator
|
||||
#
|
||||
# CLIENT_ID = "1234"
|
||||
# CLIENT_SECRET = "5678"
|
||||
#
|
||||
# EXAMPLE_TOKEN = (
|
||||
# "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhYWFhYWFhYS1iYmJiLWNjY2MtZGRk"
|
||||
# "ZC1lZWVlZWVlZWVlZWUiLCJuYW1lIjoiSm9obiBEb2UiLCJpYXQiOjE1MTYyMzkwMjIsInVzZXJuYW"
|
||||
# "1lIjoidGVzdEB0ZXN0LmNvbSJ9.CTU1YItIrUl8nSM3koJxlFJr5CjLghgc9gS6h45D8dE"
|
||||
# )
|
||||
#
|
||||
#
|
||||
# @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,
|
||||
# ClientCredential(CLIENT_ID, CLIENT_SECRET),
|
||||
# )
|
||||
#
|
||||
#
|
||||
# async def _oauth_actions(
|
||||
# hass: HomeAssistant,
|
||||
# result: ConfigFlowResult,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# ) -> None:
|
||||
# state = config_entry_oauth2_flow._encode_jwt(
|
||||
# hass,
|
||||
# {
|
||||
# "flow_id": result["flow_id"],
|
||||
# "redirect_uri": "https://example.com/auth/external/callback",
|
||||
# },
|
||||
# )
|
||||
#
|
||||
# assert result["url"] == (
|
||||
# f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
|
||||
# "&redirect_uri=https://example.com/auth/external/callback"
|
||||
# f"&state={state}"
|
||||
# )
|
||||
#
|
||||
# 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": EXAMPLE_TOKEN,
|
||||
# "type": "Bearer",
|
||||
# "expires_in": 60,
|
||||
# },
|
||||
# )
|
||||
#
|
||||
#
|
||||
# @pytest.mark.skip(reason="Integration disabled")
|
||||
# @pytest.mark.usefixtures("current_request_with_host")
|
||||
# async def test_full_flow(
|
||||
# hass: HomeAssistant,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# setup_credentials: None,
|
||||
# mock_setup_entry: AsyncMock,
|
||||
# ) -> None:
|
||||
# """Check full flow."""
|
||||
# result = await hass.config_entries.flow.async_init(
|
||||
# DOMAIN, context={"source": SOURCE_USER}
|
||||
# )
|
||||
# await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
#
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
# assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
# assert result["title"] == "test@test.com"
|
||||
# assert result["data"]["token"]["access_token"] == EXAMPLE_TOKEN
|
||||
# assert result["data"]["token"]["refresh_token"] == "mock-refresh-token"
|
||||
# assert result["result"].unique_id == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
||||
#
|
||||
# assert len(hass.config_entries.async_entries(DOMAIN)) == 1
|
||||
# assert len(mock_setup_entry.mock_calls) == 1
|
||||
#
|
||||
#
|
||||
# @pytest.mark.skip(reason="Integration disabled")
|
||||
# @pytest.mark.usefixtures("current_request_with_host")
|
||||
# async def test_duplicate_entry(
|
||||
# hass: HomeAssistant,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# setup_credentials: None,
|
||||
# mock_config_entry: MockConfigEntry,
|
||||
# ) -> None:
|
||||
# """Test we abort with duplicate entry."""
|
||||
# mock_config_entry.add_to_hass(hass)
|
||||
# result = await hass.config_entries.flow.async_init(
|
||||
# DOMAIN, context={"source": SOURCE_USER}
|
||||
# )
|
||||
# await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
#
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
# assert result["type"] is FlowResultType.ABORT
|
||||
# assert result["reason"] == "already_configured"
|
||||
#
|
||||
#
|
||||
# @pytest.mark.skip(reason="Integration disabled")
|
||||
# @pytest.mark.usefixtures("current_request_with_host")
|
||||
# async def test_reauth(
|
||||
# hass: HomeAssistant,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# setup_credentials: None,
|
||||
# mock_config_entry: MockConfigEntry,
|
||||
# mock_setup_entry: AsyncMock,
|
||||
# ) -> None:
|
||||
# """Test reauthentication."""
|
||||
# mock_config_entry.add_to_hass(hass)
|
||||
# result = await hass.config_entries.flow.async_init(
|
||||
# DOMAIN,
|
||||
# context={
|
||||
# "source": SOURCE_REAUTH,
|
||||
# "entry_id": mock_config_entry.entry_id,
|
||||
# },
|
||||
# data=mock_config_entry.data,
|
||||
# )
|
||||
# assert result["type"] is FlowResultType.FORM
|
||||
# assert result["step_id"] == "reauth_confirm"
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
# await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
#
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
# assert result["type"] is FlowResultType.ABORT
|
||||
# assert result["reason"] == "reauth_successful"
|
||||
#
|
||||
#
|
||||
# @pytest.mark.skip(reason="Integration disabled")
|
||||
# @pytest.mark.usefixtures("current_request_with_host")
|
||||
# async def test_reauth_wrong_account(
|
||||
# hass: HomeAssistant,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# setup_credentials: None,
|
||||
# mock_setup_entry: AsyncMock,
|
||||
# ) -> None:
|
||||
# """Test reauthentication with wrong account."""
|
||||
# config_entry = MockConfigEntry(
|
||||
# domain=DOMAIN,
|
||||
# data={},
|
||||
# title="test@test.com",
|
||||
# unique_id="aaaaaaaa-bbbb-ffff-dddd-eeeeeeeeeeee",
|
||||
# version=2,
|
||||
# )
|
||||
# config_entry.add_to_hass(hass)
|
||||
# result = await hass.config_entries.flow.async_init(
|
||||
# DOMAIN,
|
||||
# context={
|
||||
# "source": SOURCE_REAUTH,
|
||||
# "entry_id": config_entry.entry_id,
|
||||
# },
|
||||
# data=config_entry.data,
|
||||
# )
|
||||
# assert result["type"] is FlowResultType.FORM
|
||||
# assert result["step_id"] == "reauth_confirm"
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
# await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
#
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
# assert result["type"] is FlowResultType.ABORT
|
||||
# assert result["reason"] == "wrong_account"
|
||||
#
|
||||
#
|
||||
# @pytest.mark.skip(reason="Integration disabled")
|
||||
# @pytest.mark.usefixtures("current_request_with_host")
|
||||
# async def test_reauth_old_account(
|
||||
# hass: HomeAssistant,
|
||||
# hass_client_no_auth: ClientSessionGenerator,
|
||||
# aioclient_mock: AiohttpClientMocker,
|
||||
# setup_credentials: None,
|
||||
# mock_setup_entry: AsyncMock,
|
||||
# ) -> None:
|
||||
# """Test reauthentication with old account."""
|
||||
# config_entry = MockConfigEntry(
|
||||
# domain=DOMAIN,
|
||||
# data={},
|
||||
# title="test@test.com",
|
||||
# unique_id="test@test.com",
|
||||
# version=2,
|
||||
# )
|
||||
# config_entry.add_to_hass(hass)
|
||||
# result = await hass.config_entries.flow.async_init(
|
||||
# DOMAIN,
|
||||
# context={
|
||||
# "source": SOURCE_REAUTH,
|
||||
# "entry_id": config_entry.entry_id,
|
||||
# },
|
||||
# data=config_entry.data,
|
||||
# )
|
||||
# assert result["type"] is FlowResultType.FORM
|
||||
# assert result["step_id"] == "reauth_confirm"
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"], {})
|
||||
# await _oauth_actions(hass, result, hass_client_no_auth, aioclient_mock)
|
||||
#
|
||||
# result = await hass.config_entries.flow.async_configure(result["flow_id"])
|
||||
# assert result["type"] is FlowResultType.ABORT
|
||||
# assert result["reason"] == "reauth_successful"
|
||||
# assert config_entry.unique_id == "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"
|
50
tests/components/aladdin_connect/test_init.py
Normal file
50
tests/components/aladdin_connect/test_init.py
Normal file
@ -0,0 +1,50 @@
|
||||
"""Tests for the Aladdin Connect integration."""
|
||||
|
||||
from homeassistant.components.aladdin_connect import DOMAIN
|
||||
from homeassistant.config_entries import ConfigEntryState
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import issue_registry as ir
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
async def test_aladdin_connect_repair_issue(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test the Aladdin Connect configuration entry loading/unloading handles the repair."""
|
||||
config_entry_1 = MockConfigEntry(
|
||||
title="Example 1",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_1.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
assert config_entry_1.state is ConfigEntryState.LOADED
|
||||
|
||||
# Add a second one
|
||||
config_entry_2 = MockConfigEntry(
|
||||
title="Example 2",
|
||||
domain=DOMAIN,
|
||||
)
|
||||
config_entry_2.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the first one
|
||||
await hass.config_entries.async_remove(config_entry_1.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN)
|
||||
|
||||
# Remove the second one
|
||||
await hass.config_entries.async_remove(config_entry_2.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert config_entry_1.state is ConfigEntryState.NOT_LOADED
|
||||
assert config_entry_2.state is ConfigEntryState.NOT_LOADED
|
||||
assert issue_registry.async_get_issue(DOMAIN, DOMAIN) is None
|
Loading…
x
Reference in New Issue
Block a user