From 8e12d2028d7b731f5510b46ec5f8969585a532cc Mon Sep 17 00:00:00 2001 From: G Johansson Date: Fri, 8 Aug 2025 13:09:01 +0200 Subject: [PATCH] Remove previously deprecated linear_garage_door (#150109) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- .strict-typing | 1 - CODEOWNERS | 2 - .../components/linear_garage_door/__init__.py | 54 ---- .../linear_garage_door/config_flow.py | 160 ------------ .../components/linear_garage_door/const.py | 3 - .../linear_garage_door/coordinator.py | 86 ------- .../components/linear_garage_door/cover.py | 85 ------- .../linear_garage_door/diagnostics.py | 29 --- .../components/linear_garage_door/entity.py | 43 ---- .../components/linear_garage_door/light.py | 78 ------ .../linear_garage_door/manifest.json | 9 - .../linear_garage_door/strings.json | 33 --- homeassistant/generated/config_flows.py | 1 - homeassistant/generated/integrations.json | 6 - mypy.ini | 10 - requirements_all.txt | 3 - requirements_test_all.txt | 3 - .../components/linear_garage_door/__init__.py | 22 -- .../components/linear_garage_door/conftest.py | 67 ----- .../fixtures/get_device_state.json | 42 ---- .../fixtures/get_device_state_1.json | 42 ---- .../fixtures/get_devices.json | 22 -- .../fixtures/get_sites.json | 1 - .../snapshots/test_cover.ambr | 201 --------------- .../snapshots/test_diagnostics.ambr | 83 ------- .../snapshots/test_light.ambr | 233 ------------------ .../linear_garage_door/test_config_flow.py | 132 ---------- .../linear_garage_door/test_cover.py | 120 --------- .../linear_garage_door/test_diagnostics.py | 29 --- .../linear_garage_door/test_init.py | 135 ---------- .../linear_garage_door/test_light.py | 126 ---------- 31 files changed, 1861 deletions(-) delete mode 100644 homeassistant/components/linear_garage_door/__init__.py delete mode 100644 homeassistant/components/linear_garage_door/config_flow.py delete mode 100644 homeassistant/components/linear_garage_door/const.py delete mode 100644 homeassistant/components/linear_garage_door/coordinator.py delete mode 100644 homeassistant/components/linear_garage_door/cover.py delete mode 100644 homeassistant/components/linear_garage_door/diagnostics.py delete mode 100644 homeassistant/components/linear_garage_door/entity.py delete mode 100644 homeassistant/components/linear_garage_door/light.py delete mode 100644 homeassistant/components/linear_garage_door/manifest.json delete mode 100644 homeassistant/components/linear_garage_door/strings.json delete mode 100644 tests/components/linear_garage_door/__init__.py delete mode 100644 tests/components/linear_garage_door/conftest.py delete mode 100644 tests/components/linear_garage_door/fixtures/get_device_state.json delete mode 100644 tests/components/linear_garage_door/fixtures/get_device_state_1.json delete mode 100644 tests/components/linear_garage_door/fixtures/get_devices.json delete mode 100644 tests/components/linear_garage_door/fixtures/get_sites.json delete mode 100644 tests/components/linear_garage_door/snapshots/test_cover.ambr delete mode 100644 tests/components/linear_garage_door/snapshots/test_diagnostics.ambr delete mode 100644 tests/components/linear_garage_door/snapshots/test_light.ambr delete mode 100644 tests/components/linear_garage_door/test_config_flow.py delete mode 100644 tests/components/linear_garage_door/test_cover.py delete mode 100644 tests/components/linear_garage_door/test_diagnostics.py delete mode 100644 tests/components/linear_garage_door/test_init.py delete mode 100644 tests/components/linear_garage_door/test_light.py diff --git a/.strict-typing b/.strict-typing index c125e85bbfc..98973c89a5a 100644 --- a/.strict-typing +++ b/.strict-typing @@ -310,7 +310,6 @@ homeassistant.components.letpot.* homeassistant.components.lidarr.* homeassistant.components.lifx.* homeassistant.components.light.* -homeassistant.components.linear_garage_door.* homeassistant.components.linkplay.* homeassistant.components.litejet.* homeassistant.components.litterrobot.* diff --git a/CODEOWNERS b/CODEOWNERS index 84a07305d36..d52349d49e8 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -862,8 +862,6 @@ build.json @home-assistant/supervisor /tests/components/lifx/ @Djelibeybi /homeassistant/components/light/ @home-assistant/core /tests/components/light/ @home-assistant/core -/homeassistant/components/linear_garage_door/ @IceBotYT -/tests/components/linear_garage_door/ @IceBotYT /homeassistant/components/linkplay/ @Velleman /tests/components/linkplay/ @Velleman /homeassistant/components/linux_battery/ @fabaff diff --git a/homeassistant/components/linear_garage_door/__init__.py b/homeassistant/components/linear_garage_door/__init__.py deleted file mode 100644 index a80aa99628b..00000000000 --- a/homeassistant/components/linear_garage_door/__init__.py +++ /dev/null @@ -1,54 +0,0 @@ -"""The Linear Garage Door integration.""" - -from __future__ import annotations - -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import issue_registry as ir - -from .const import DOMAIN -from .coordinator import LinearConfigEntry, LinearUpdateCoordinator - -PLATFORMS: list[Platform] = [Platform.COVER, Platform.LIGHT] - - -async def async_setup_entry(hass: HomeAssistant, entry: LinearConfigEntry) -> bool: - """Set up Linear Garage Door from a config entry.""" - - ir.async_create_issue( - hass, - DOMAIN, - DOMAIN, - breaks_in_ha_version="2025.8.0", - is_fixable=False, - issue_domain=DOMAIN, - severity=ir.IssueSeverity.WARNING, - translation_key="deprecated_integration", - translation_placeholders={ - "nice_go": "https://www.home-assistant.io/integrations/linear_garage_door", - "entries": "/config/integrations/integration/linear_garage_door", - }, - ) - - coordinator = LinearUpdateCoordinator(hass, entry) - - await coordinator.async_config_entry_first_refresh() - - entry.runtime_data = coordinator - await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) - - return True - - -async def async_unload_entry(hass: HomeAssistant, entry: LinearConfigEntry) -> bool: - """Unload a config entry.""" - return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) - - -async def async_remove_entry(hass: HomeAssistant, entry: LinearConfigEntry) -> None: - """Remove a config entry.""" - if not hass.config_entries.async_loaded_entries(DOMAIN): - ir.async_delete_issue(hass, DOMAIN, DOMAIN) - # Remove any remaining disabled or ignored entries - for _entry in hass.config_entries.async_entries(DOMAIN): - hass.async_create_task(hass.config_entries.async_remove(_entry.entry_id)) diff --git a/homeassistant/components/linear_garage_door/config_flow.py b/homeassistant/components/linear_garage_door/config_flow.py deleted file mode 100644 index 2cfd0af6a8f..00000000000 --- a/homeassistant/components/linear_garage_door/config_flow.py +++ /dev/null @@ -1,160 +0,0 @@ -"""Config flow for Linear Garage Door integration.""" - -from __future__ import annotations - -from collections.abc import Collection, Mapping, Sequence -import logging -from typing import Any -import uuid - -from linear_garage_door import Linear -from linear_garage_door.errors import InvalidLoginError -import voluptuous as vol - -from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlow, ConfigFlowResult -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import HomeAssistantError -from homeassistant.helpers.aiohttp_client import async_get_clientsession - -from .const import DOMAIN - -_LOGGER = logging.getLogger(__name__) - -STEP_USER_DATA_SCHEMA = { - vol.Required(CONF_EMAIL): str, - vol.Required(CONF_PASSWORD): str, -} - - -async def validate_input( - hass: HomeAssistant, - data: dict[str, str], -) -> dict[str, Sequence[Collection[str]]]: - """Validate the user input allows us to connect. - - Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. - """ - - hub = Linear() - - device_id = str(uuid.uuid4()) - try: - await hub.login( - data["email"], - data["password"], - device_id=device_id, - client_session=async_get_clientsession(hass), - ) - - sites = await hub.get_sites() - except InvalidLoginError as err: - raise InvalidAuth from err - finally: - await hub.close() - - return { - "email": data["email"], - "password": data["password"], - "sites": sites, - "device_id": device_id, - } - - -class LinearGarageDoorConfigFlow(ConfigFlow, domain=DOMAIN): - """Handle a config flow for Linear Garage Door.""" - - VERSION = 1 - - def __init__(self) -> None: - """Initialize the config flow.""" - self.data: dict[str, Sequence[Collection[str]]] = {} - - async def async_step_user( - self, user_input: dict[str, Any] | None = None - ) -> ConfigFlowResult: - """Handle the initial step.""" - data_schema = vol.Schema(STEP_USER_DATA_SCHEMA) - - if user_input is None: - return self.async_show_form(step_id="user", data_schema=data_schema) - - errors = {} - - try: - info = await validate_input(self.hass, user_input) - except InvalidAuth: - errors["base"] = "invalid_auth" - except Exception: - _LOGGER.exception("Unexpected exception") - errors["base"] = "unknown" - else: - self.data = info - - # Check if we are reauthenticating - if self.source == SOURCE_REAUTH: - return self.async_update_reload_and_abort( - self._get_reauth_entry(), - data_updates={ - CONF_EMAIL: self.data["email"], - CONF_PASSWORD: self.data["password"], - }, - ) - - return await self.async_step_site() - - return self.async_show_form( - step_id="user", data_schema=data_schema, errors=errors - ) - - async def async_step_site( - self, - user_input: dict[str, Any] | None = None, - ) -> ConfigFlowResult: - """Handle the site step.""" - - if isinstance(self.data["sites"], list): - sites: list[dict[str, str]] = self.data["sites"] - - if not user_input: - return self.async_show_form( - step_id="site", - data_schema=vol.Schema( - { - vol.Required("site"): vol.In( - {site["id"]: site["name"] for site in sites} - ) - } - ), - ) - - site_id = user_input["site"] - - site_name = next(site["name"] for site in sites if site["id"] == site_id) - - await self.async_set_unique_id(site_id) - self._abort_if_unique_id_configured() - - return self.async_create_entry( - title=site_name, - data={ - "site_id": site_id, - "email": self.data["email"], - "password": self.data["password"], - "device_id": self.data["device_id"], - }, - ) - - async def async_step_reauth( - self, entry_data: Mapping[str, Any] - ) -> ConfigFlowResult: - """Reauth in case of a password change or other error.""" - return await self.async_step_user() - - -class InvalidAuth(HomeAssistantError): - """Error to indicate there is invalid auth.""" - - -class InvalidDeviceID(HomeAssistantError): - """Error to indicate there is invalid device ID.""" diff --git a/homeassistant/components/linear_garage_door/const.py b/homeassistant/components/linear_garage_door/const.py deleted file mode 100644 index 7b3625c7c67..00000000000 --- a/homeassistant/components/linear_garage_door/const.py +++ /dev/null @@ -1,3 +0,0 @@ -"""Constants for the Linear Garage Door integration.""" - -DOMAIN = "linear_garage_door" diff --git a/homeassistant/components/linear_garage_door/coordinator.py b/homeassistant/components/linear_garage_door/coordinator.py deleted file mode 100644 index 3844e1ae7de..00000000000 --- a/homeassistant/components/linear_garage_door/coordinator.py +++ /dev/null @@ -1,86 +0,0 @@ -"""DataUpdateCoordinator for Linear.""" - -from __future__ import annotations - -from collections.abc import Awaitable, Callable -from dataclasses import dataclass -from datetime import timedelta -import logging -from typing import Any, cast - -from linear_garage_door import Linear -from linear_garage_door.errors import InvalidLoginError - -from homeassistant.config_entries import ConfigEntry -from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady -from homeassistant.helpers.aiohttp_client import async_get_clientsession -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator - -_LOGGER = logging.getLogger(__name__) - -type LinearConfigEntry = ConfigEntry[LinearUpdateCoordinator] - - -@dataclass -class LinearDevice: - """Linear device dataclass.""" - - name: str - subdevices: dict[str, dict[str, str]] - - -class LinearUpdateCoordinator(DataUpdateCoordinator[dict[str, LinearDevice]]): - """DataUpdateCoordinator for Linear.""" - - _devices: list[dict[str, Any]] | None = None - config_entry: LinearConfigEntry - - def __init__(self, hass: HomeAssistant, config_entry: LinearConfigEntry) -> None: - """Initialize DataUpdateCoordinator for Linear.""" - super().__init__( - hass, - _LOGGER, - config_entry=config_entry, - name="Linear Garage Door", - update_interval=timedelta(seconds=60), - ) - self.site_id = config_entry.data["site_id"] - - async def _async_update_data(self) -> dict[str, LinearDevice]: - """Get the data for Linear.""" - - async def update_data(linear: Linear) -> dict[str, Any]: - if not self._devices: - self._devices = await linear.get_devices(self.site_id) - - data = {} - - for device in self._devices: - device_id = str(device["id"]) - state = await linear.get_device_state(device_id) - data[device_id] = LinearDevice(cast(str, device["name"]), state) - return data - - return await self.execute(update_data) - - async def execute[_T](self, func: Callable[[Linear], Awaitable[_T]]) -> _T: - """Execute an API call.""" - linear = Linear() - try: - await linear.login( - email=self.config_entry.data["email"], - password=self.config_entry.data["password"], - device_id=self.config_entry.data["device_id"], - client_session=async_get_clientsession(self.hass), - ) - except InvalidLoginError as err: - if ( - str(err) - == "Login error: Login provided is invalid, please check the email and password" - ): - raise ConfigEntryAuthFailed from err - raise ConfigEntryNotReady from err - result = await func(linear) - await linear.close() - return result diff --git a/homeassistant/components/linear_garage_door/cover.py b/homeassistant/components/linear_garage_door/cover.py deleted file mode 100644 index 1f6c0999531..00000000000 --- a/homeassistant/components/linear_garage_door/cover.py +++ /dev/null @@ -1,85 +0,0 @@ -"""Cover entity for Linear Garage Doors.""" - -from datetime import timedelta -from typing import Any - -from homeassistant.components.cover import ( - CoverDeviceClass, - CoverEntity, - CoverEntityFeature, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback - -from .coordinator import LinearConfigEntry -from .entity import LinearEntity - -SUPPORTED_SUBDEVICES = ["GDO"] -PARALLEL_UPDATES = 1 -SCAN_INTERVAL = timedelta(seconds=10) - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: LinearConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, -) -> None: - """Set up Linear Garage Door cover.""" - coordinator = config_entry.runtime_data - - async_add_entities( - LinearCoverEntity(coordinator, device_id, device_data.name, sub_device_id) - for device_id, device_data in coordinator.data.items() - for sub_device_id in device_data.subdevices - if sub_device_id in SUPPORTED_SUBDEVICES - ) - - -class LinearCoverEntity(LinearEntity, CoverEntity): - """Representation of a Linear cover.""" - - _attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE - _attr_name = None - _attr_device_class = CoverDeviceClass.GARAGE - - @property - def is_closed(self) -> bool: - """Return if cover is closed.""" - return self.sub_device.get("Open_B") == "false" - - @property - def is_opened(self) -> bool: - """Return if cover is open.""" - return self.sub_device.get("Open_B") == "true" - - @property - def is_opening(self) -> bool: - """Return if cover is opening.""" - return self.sub_device.get("Opening_P") == "0" - - @property - def is_closing(self) -> bool: - """Return if cover is closing.""" - return self.sub_device.get("Opening_P") == "100" - - async def async_close_cover(self, **kwargs: Any) -> None: - """Close the garage door.""" - if self.is_closed: - return - - await self.coordinator.execute( - lambda linear: linear.operate_device( - self._device_id, self._sub_device_id, "Close" - ) - ) - - async def async_open_cover(self, **kwargs: Any) -> None: - """Open the garage door.""" - if self.is_opened: - return - - await self.coordinator.execute( - lambda linear: linear.operate_device( - self._device_id, self._sub_device_id, "Open" - ) - ) diff --git a/homeassistant/components/linear_garage_door/diagnostics.py b/homeassistant/components/linear_garage_door/diagnostics.py deleted file mode 100644 index ff5ca5639bf..00000000000 --- a/homeassistant/components/linear_garage_door/diagnostics.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Diagnostics support for Linear Garage Door.""" - -from __future__ import annotations - -from dataclasses import asdict -from typing import Any - -from homeassistant.components.diagnostics import async_redact_data -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant - -from .coordinator import LinearConfigEntry - -TO_REDACT = {CONF_PASSWORD, CONF_EMAIL} - - -async def async_get_config_entry_diagnostics( - hass: HomeAssistant, entry: LinearConfigEntry -) -> dict[str, Any]: - """Return diagnostics for a config entry.""" - coordinator = entry.runtime_data - - return { - "entry": async_redact_data(entry.as_dict(), TO_REDACT), - "coordinator_data": { - device_id: asdict(device_data) - for device_id, device_data in coordinator.data.items() - }, - } diff --git a/homeassistant/components/linear_garage_door/entity.py b/homeassistant/components/linear_garage_door/entity.py deleted file mode 100644 index a7adf95f82e..00000000000 --- a/homeassistant/components/linear_garage_door/entity.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Base entity for Linear.""" - -from homeassistant.helpers.device_registry import DeviceInfo -from homeassistant.helpers.update_coordinator import CoordinatorEntity - -from .const import DOMAIN -from .coordinator import LinearDevice, LinearUpdateCoordinator - - -class LinearEntity(CoordinatorEntity[LinearUpdateCoordinator]): - """Common base for Linear entities.""" - - _attr_has_entity_name = True - - def __init__( - self, - coordinator: LinearUpdateCoordinator, - device_id: str, - device_name: str, - sub_device_id: str, - ) -> None: - """Initialize the entity.""" - super().__init__(coordinator) - - self._attr_unique_id = f"{device_id}-{sub_device_id}" - self._device_id = device_id - self._sub_device_id = sub_device_id - self._attr_device_info = DeviceInfo( - identifiers={(DOMAIN, device_id)}, - name=device_name, - manufacturer="Linear", - model="Garage Door Opener", - ) - - @property - def linear_device(self) -> LinearDevice: - """Return the Linear device.""" - return self.coordinator.data[self._device_id] - - @property - def sub_device(self) -> dict[str, str]: - """Return the subdevice.""" - return self.linear_device.subdevices[self._sub_device_id] diff --git a/homeassistant/components/linear_garage_door/light.py b/homeassistant/components/linear_garage_door/light.py deleted file mode 100644 index 59243817fbb..00000000000 --- a/homeassistant/components/linear_garage_door/light.py +++ /dev/null @@ -1,78 +0,0 @@ -"""Linear garage door light.""" - -from typing import Any - -from linear_garage_door import Linear - -from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback - -from .coordinator import LinearConfigEntry -from .entity import LinearEntity - -SUPPORTED_SUBDEVICES = ["Light"] - - -async def async_setup_entry( - hass: HomeAssistant, - config_entry: LinearConfigEntry, - async_add_entities: AddConfigEntryEntitiesCallback, -) -> None: - """Set up Linear Garage Door cover.""" - coordinator = config_entry.runtime_data - data = coordinator.data - - async_add_entities( - LinearLightEntity( - device_id=device_id, - device_name=data[device_id].name, - sub_device_id=subdev, - coordinator=coordinator, - ) - for device_id in data - for subdev in data[device_id].subdevices - if subdev in SUPPORTED_SUBDEVICES - ) - - -class LinearLightEntity(LinearEntity, LightEntity): - """Light for Linear devices.""" - - _attr_color_mode = ColorMode.BRIGHTNESS - _attr_supported_color_modes = {ColorMode.BRIGHTNESS} - _attr_translation_key = "light" - - @property - def is_on(self) -> bool: - """Return if the light is on or not.""" - return bool(self.sub_device["On_B"] == "true") - - @property - def brightness(self) -> int | None: - """Return the brightness of the light.""" - return round(int(self.sub_device["On_P"]) / 100 * 255) - - async def async_turn_on(self, **kwargs: Any) -> None: - """Turn on the light.""" - - async def _turn_on(linear: Linear) -> None: - """Turn on the light.""" - if not kwargs: - await linear.operate_device(self._device_id, self._sub_device_id, "On") - elif ATTR_BRIGHTNESS in kwargs: - brightness = round((kwargs[ATTR_BRIGHTNESS] / 255) * 100) - await linear.operate_device( - self._device_id, self._sub_device_id, f"DimPercent:{brightness}" - ) - - await self.coordinator.execute(_turn_on) - - async def async_turn_off(self, **kwargs: Any) -> None: - """Turn off the light.""" - - await self.coordinator.execute( - lambda linear: linear.operate_device( - self._device_id, self._sub_device_id, "Off" - ) - ) diff --git a/homeassistant/components/linear_garage_door/manifest.json b/homeassistant/components/linear_garage_door/manifest.json deleted file mode 100644 index f1eb4302cf0..00000000000 --- a/homeassistant/components/linear_garage_door/manifest.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "domain": "linear_garage_door", - "name": "Linear Garage Door", - "codeowners": ["@IceBotYT"], - "config_flow": true, - "documentation": "https://www.home-assistant.io/integrations/linear_garage_door", - "iot_class": "cloud_polling", - "requirements": ["linear-garage-door==0.2.9"] -} diff --git a/homeassistant/components/linear_garage_door/strings.json b/homeassistant/components/linear_garage_door/strings.json deleted file mode 100644 index 40ffcf22e8d..00000000000 --- a/homeassistant/components/linear_garage_door/strings.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "config": { - "step": { - "user": { - "data": { - "email": "[%key:common::config_flow::data::email%]", - "password": "[%key:common::config_flow::data::password%]" - } - } - }, - "error": { - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" - }, - "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", - "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" - } - }, - "entity": { - "light": { - "light": { - "name": "[%key:component::light::title%]" - } - } - }, - "issues": { - "deprecated_integration": { - "title": "The Linear Garage Door integration will be removed", - "description": "The Linear Garage Door integration will be removed as it has been replaced by the [Nice G.O.]({nice_go}) integration. Please migrate to the new integration.\n\nTo resolve this issue, please remove all Linear Garage Door entries from your configuration and add the new Nice G.O. integration. [Click here to see your existing Linear Garage Door integration entries]({entries})." - } - } -} diff --git a/homeassistant/generated/config_flows.py b/homeassistant/generated/config_flows.py index 5816a0ddbd9..8de75b21bba 100644 --- a/homeassistant/generated/config_flows.py +++ b/homeassistant/generated/config_flows.py @@ -348,7 +348,6 @@ FLOWS = { "lg_thinq", "lidarr", "lifx", - "linear_garage_door", "linkplay", "litejet", "litterrobot", diff --git a/homeassistant/generated/integrations.json b/homeassistant/generated/integrations.json index c606d79f2c5..e9a8f46a496 100644 --- a/homeassistant/generated/integrations.json +++ b/homeassistant/generated/integrations.json @@ -3512,12 +3512,6 @@ "integration_type": "virtual", "supported_by": "idasen_desk" }, - "linear_garage_door": { - "name": "Linear Garage Door", - "integration_type": "hub", - "config_flow": true, - "iot_class": "cloud_polling" - }, "linkedgo": { "name": "LinkedGo", "integration_type": "virtual", diff --git a/mypy.ini b/mypy.ini index 8482138cc45..91c75beb64a 100644 --- a/mypy.ini +++ b/mypy.ini @@ -2856,16 +2856,6 @@ disallow_untyped_defs = true warn_return_any = true warn_unreachable = true -[mypy-homeassistant.components.linear_garage_door.*] -check_untyped_defs = true -disallow_incomplete_defs = true -disallow_subclassing_any = true -disallow_untyped_calls = true -disallow_untyped_decorators = true -disallow_untyped_defs = true -warn_return_any = true -warn_unreachable = true - [mypy-homeassistant.components.linkplay.*] check_untyped_defs = true disallow_incomplete_defs = true diff --git a/requirements_all.txt b/requirements_all.txt index 1e27ad4ded9..c29a2c81b08 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1363,9 +1363,6 @@ lightwave==0.24 # homeassistant.components.limitlessled limitlessled==1.1.3 -# homeassistant.components.linear_garage_door -linear-garage-door==0.2.9 - # homeassistant.components.linode linode-api==4.1.9b1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index caf90997fa5..dc27d14f1ad 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -1170,9 +1170,6 @@ librouteros==3.2.0 # homeassistant.components.soundtouch libsoundtouch==0.8 -# homeassistant.components.linear_garage_door -linear-garage-door==0.2.9 - # homeassistant.components.livisi livisi==0.0.25 diff --git a/tests/components/linear_garage_door/__init__.py b/tests/components/linear_garage_door/__init__.py deleted file mode 100644 index 67bd1ee2da2..00000000000 --- a/tests/components/linear_garage_door/__init__.py +++ /dev/null @@ -1,22 +0,0 @@ -"""Tests for the Linear Garage Door integration.""" - -from unittest.mock import patch - -from homeassistant.const import Platform -from homeassistant.core import HomeAssistant - -from tests.common import MockConfigEntry - - -async def setup_integration( - hass: HomeAssistant, config_entry: MockConfigEntry, platforms: list[Platform] -) -> None: - """Fixture for setting up the component.""" - config_entry.add_to_hass(hass) - - with patch( - "homeassistant.components.linear_garage_door.PLATFORMS", - platforms, - ): - await hass.config_entries.async_setup(config_entry.entry_id) - await hass.async_block_till_done() diff --git a/tests/components/linear_garage_door/conftest.py b/tests/components/linear_garage_door/conftest.py deleted file mode 100644 index 4ed7662e5d0..00000000000 --- a/tests/components/linear_garage_door/conftest.py +++ /dev/null @@ -1,67 +0,0 @@ -"""Common fixtures for the Linear Garage Door tests.""" - -from collections.abc import Generator -from unittest.mock import AsyncMock, patch - -import pytest - -from homeassistant.components.linear_garage_door import DOMAIN -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD - -from tests.common import ( - MockConfigEntry, - load_json_array_fixture, - load_json_object_fixture, -) - - -@pytest.fixture -def mock_setup_entry() -> Generator[AsyncMock]: - """Override async_setup_entry.""" - with patch( - "homeassistant.components.linear_garage_door.async_setup_entry", - return_value=True, - ) as mock_setup_entry: - yield mock_setup_entry - - -@pytest.fixture -def mock_linear() -> Generator[AsyncMock]: - """Mock a Linear Garage Door client.""" - with ( - patch( - "homeassistant.components.linear_garage_door.coordinator.Linear", - autospec=True, - ) as mock_client, - patch( - "homeassistant.components.linear_garage_door.config_flow.Linear", - new=mock_client, - ), - ): - client = mock_client.return_value - client.login.return_value = True - client.get_devices.return_value = load_json_array_fixture( - "get_devices.json", DOMAIN - ) - client.get_sites.return_value = load_json_array_fixture( - "get_sites.json", DOMAIN - ) - device_states = load_json_object_fixture("get_device_state.json", DOMAIN) - client.get_device_state.side_effect = lambda device_id: device_states[device_id] - yield client - - -@pytest.fixture -def mock_config_entry() -> MockConfigEntry: - """Mock a config entry.""" - return MockConfigEntry( - domain=DOMAIN, - entry_id="acefdd4b3a4a0911067d1cf51414201e", - title="test-site-name", - data={ - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) diff --git a/tests/components/linear_garage_door/fixtures/get_device_state.json b/tests/components/linear_garage_door/fixtures/get_device_state.json deleted file mode 100644 index 14247610e06..00000000000 --- a/tests/components/linear_garage_door/fixtures/get_device_state.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "test1": { - "GDO": { - "Open_B": "true", - "Open_P": "100" - }, - "Light": { - "On_B": "true", - "On_P": "100" - } - }, - "test2": { - "GDO": { - "Open_B": "false", - "Open_P": "0" - }, - "Light": { - "On_B": "false", - "On_P": "0" - } - }, - "test3": { - "GDO": { - "Open_B": "false", - "Opening_P": "0" - }, - "Light": { - "On_B": "false", - "On_P": "0" - } - }, - "test4": { - "GDO": { - "Open_B": "true", - "Opening_P": "100" - }, - "Light": { - "On_B": "true", - "On_P": "100" - } - } -} diff --git a/tests/components/linear_garage_door/fixtures/get_device_state_1.json b/tests/components/linear_garage_door/fixtures/get_device_state_1.json deleted file mode 100644 index 1f41d4fd153..00000000000 --- a/tests/components/linear_garage_door/fixtures/get_device_state_1.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "test1": { - "GDO": { - "Open_B": "true", - "Opening_P": "100" - }, - "Light": { - "On_B": "false", - "On_P": "0" - } - }, - "test2": { - "GDO": { - "Open_B": "false", - "Opening_P": "0" - }, - "Light": { - "On_B": "true", - "On_P": "100" - } - }, - "test3": { - "GDO": { - "Open_B": "false", - "Opening_P": "0" - }, - "Light": { - "On_B": "false", - "On_P": "0" - } - }, - "test4": { - "GDO": { - "Open_B": "true", - "Opening_P": "100" - }, - "Light": { - "On_B": "true", - "On_P": "100" - } - } -} diff --git a/tests/components/linear_garage_door/fixtures/get_devices.json b/tests/components/linear_garage_door/fixtures/get_devices.json deleted file mode 100644 index da6eeaf7448..00000000000 --- a/tests/components/linear_garage_door/fixtures/get_devices.json +++ /dev/null @@ -1,22 +0,0 @@ -[ - { - "id": "test1", - "name": "Test Garage 1", - "subdevices": ["GDO", "Light"] - }, - { - "id": "test2", - "name": "Test Garage 2", - "subdevices": ["GDO", "Light"] - }, - { - "id": "test3", - "name": "Test Garage 3", - "subdevices": ["GDO", "Light"] - }, - { - "id": "test4", - "name": "Test Garage 4", - "subdevices": ["GDO", "Light"] - } -] diff --git a/tests/components/linear_garage_door/fixtures/get_sites.json b/tests/components/linear_garage_door/fixtures/get_sites.json deleted file mode 100644 index 2b0a49b9007..00000000000 --- a/tests/components/linear_garage_door/fixtures/get_sites.json +++ /dev/null @@ -1 +0,0 @@ -[{ "id": "test-site-id", "name": "test-site-name" }] diff --git a/tests/components/linear_garage_door/snapshots/test_cover.ambr b/tests/components/linear_garage_door/snapshots/test_cover.ambr deleted file mode 100644 index dc3df6684bc..00000000000 --- a/tests/components/linear_garage_door/snapshots/test_cover.ambr +++ /dev/null @@ -1,201 +0,0 @@ -# serializer version: 1 -# name: test_covers[cover.test_garage_1-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'cover', - 'entity_category': None, - 'entity_id': 'cover.test_garage_1', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': None, - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': , - 'translation_key': None, - 'unique_id': 'test1-GDO', - 'unit_of_measurement': None, - }) -# --- -# name: test_covers[cover.test_garage_1-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'garage', - 'friendly_name': 'Test Garage 1', - 'supported_features': , - }), - 'context': , - 'entity_id': 'cover.test_garage_1', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'open', - }) -# --- -# name: test_covers[cover.test_garage_2-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'cover', - 'entity_category': None, - 'entity_id': 'cover.test_garage_2', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': None, - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': , - 'translation_key': None, - 'unique_id': 'test2-GDO', - 'unit_of_measurement': None, - }) -# --- -# name: test_covers[cover.test_garage_2-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'garage', - 'friendly_name': 'Test Garage 2', - 'supported_features': , - }), - 'context': , - 'entity_id': 'cover.test_garage_2', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'closed', - }) -# --- -# name: test_covers[cover.test_garage_3-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'cover', - 'entity_category': None, - 'entity_id': 'cover.test_garage_3', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': None, - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': , - 'translation_key': None, - 'unique_id': 'test3-GDO', - 'unit_of_measurement': None, - }) -# --- -# name: test_covers[cover.test_garage_3-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'garage', - 'friendly_name': 'Test Garage 3', - 'supported_features': , - }), - 'context': , - 'entity_id': 'cover.test_garage_3', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'opening', - }) -# --- -# name: test_covers[cover.test_garage_4-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': None, - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'cover', - 'entity_category': None, - 'entity_id': 'cover.test_garage_4', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': , - 'original_icon': None, - 'original_name': None, - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': , - 'translation_key': None, - 'unique_id': 'test4-GDO', - 'unit_of_measurement': None, - }) -# --- -# name: test_covers[cover.test_garage_4-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'device_class': 'garage', - 'friendly_name': 'Test Garage 4', - 'supported_features': , - }), - 'context': , - 'entity_id': 'cover.test_garage_4', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'closing', - }) -# --- diff --git a/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr b/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr deleted file mode 100644 index db82f41eb73..00000000000 --- a/tests/components/linear_garage_door/snapshots/test_diagnostics.ambr +++ /dev/null @@ -1,83 +0,0 @@ -# serializer version: 1 -# name: test_entry_diagnostics - dict({ - 'coordinator_data': dict({ - 'test1': dict({ - 'name': 'Test Garage 1', - 'subdevices': dict({ - 'GDO': dict({ - 'Open_B': 'true', - 'Open_P': '100', - }), - 'Light': dict({ - 'On_B': 'true', - 'On_P': '100', - }), - }), - }), - 'test2': dict({ - 'name': 'Test Garage 2', - 'subdevices': dict({ - 'GDO': dict({ - 'Open_B': 'false', - 'Open_P': '0', - }), - 'Light': dict({ - 'On_B': 'false', - 'On_P': '0', - }), - }), - }), - 'test3': dict({ - 'name': 'Test Garage 3', - 'subdevices': dict({ - 'GDO': dict({ - 'Open_B': 'false', - 'Opening_P': '0', - }), - 'Light': dict({ - 'On_B': 'false', - 'On_P': '0', - }), - }), - }), - 'test4': dict({ - 'name': 'Test Garage 4', - 'subdevices': dict({ - 'GDO': dict({ - 'Open_B': 'true', - 'Opening_P': '100', - }), - 'Light': dict({ - 'On_B': 'true', - 'On_P': '100', - }), - }), - }), - }), - 'entry': dict({ - 'data': dict({ - 'device_id': 'test-uuid', - 'email': '**REDACTED**', - 'password': '**REDACTED**', - 'site_id': 'test-site-id', - }), - 'disabled_by': None, - 'discovery_keys': dict({ - }), - 'domain': 'linear_garage_door', - 'entry_id': 'acefdd4b3a4a0911067d1cf51414201e', - 'minor_version': 1, - 'options': dict({ - }), - 'pref_disable_new_entities': False, - 'pref_disable_polling': False, - 'source': 'user', - 'subentries': list([ - ]), - 'title': 'test-site-name', - 'unique_id': None, - 'version': 1, - }), - }) -# --- diff --git a/tests/components/linear_garage_door/snapshots/test_light.ambr b/tests/components/linear_garage_door/snapshots/test_light.ambr deleted file mode 100644 index 930d78d4706..00000000000 --- a/tests/components/linear_garage_door/snapshots/test_light.ambr +++ /dev/null @@ -1,233 +0,0 @@ -# serializer version: 1 -# name: test_data[light.test_garage_1_light-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'supported_color_modes': list([ - , - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'light', - 'entity_category': None, - 'entity_id': 'light.test_garage_1_light', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Light', - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'light', - 'unique_id': 'test1-Light', - 'unit_of_measurement': None, - }) -# --- -# name: test_data[light.test_garage_1_light-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'brightness': 255, - 'color_mode': , - 'friendly_name': 'Test Garage 1 Light', - 'supported_color_modes': list([ - , - ]), - 'supported_features': , - }), - 'context': , - 'entity_id': 'light.test_garage_1_light', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'on', - }) -# --- -# name: test_data[light.test_garage_2_light-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'supported_color_modes': list([ - , - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'light', - 'entity_category': None, - 'entity_id': 'light.test_garage_2_light', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Light', - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'light', - 'unique_id': 'test2-Light', - 'unit_of_measurement': None, - }) -# --- -# name: test_data[light.test_garage_2_light-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'brightness': None, - 'color_mode': None, - 'friendly_name': 'Test Garage 2 Light', - 'supported_color_modes': list([ - , - ]), - 'supported_features': , - }), - 'context': , - 'entity_id': 'light.test_garage_2_light', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'off', - }) -# --- -# name: test_data[light.test_garage_3_light-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'supported_color_modes': list([ - , - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'light', - 'entity_category': None, - 'entity_id': 'light.test_garage_3_light', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Light', - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'light', - 'unique_id': 'test3-Light', - 'unit_of_measurement': None, - }) -# --- -# name: test_data[light.test_garage_3_light-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'brightness': None, - 'color_mode': None, - 'friendly_name': 'Test Garage 3 Light', - 'supported_color_modes': list([ - , - ]), - 'supported_features': , - }), - 'context': , - 'entity_id': 'light.test_garage_3_light', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'off', - }) -# --- -# name: test_data[light.test_garage_4_light-entry] - EntityRegistryEntrySnapshot({ - 'aliases': set({ - }), - 'area_id': None, - 'capabilities': dict({ - 'supported_color_modes': list([ - , - ]), - }), - 'config_entry_id': , - 'config_subentry_id': , - 'device_class': None, - 'device_id': , - 'disabled_by': None, - 'domain': 'light', - 'entity_category': None, - 'entity_id': 'light.test_garage_4_light', - 'has_entity_name': True, - 'hidden_by': None, - 'icon': None, - 'id': , - 'labels': set({ - }), - 'name': None, - 'options': dict({ - }), - 'original_device_class': None, - 'original_icon': None, - 'original_name': 'Light', - 'platform': 'linear_garage_door', - 'previous_unique_id': None, - 'suggested_object_id': None, - 'supported_features': 0, - 'translation_key': 'light', - 'unique_id': 'test4-Light', - 'unit_of_measurement': None, - }) -# --- -# name: test_data[light.test_garage_4_light-state] - StateSnapshot({ - 'attributes': ReadOnlyDict({ - 'brightness': 255, - 'color_mode': , - 'friendly_name': 'Test Garage 4 Light', - 'supported_color_modes': list([ - , - ]), - 'supported_features': , - }), - 'context': , - 'entity_id': 'light.test_garage_4_light', - 'last_changed': , - 'last_reported': , - 'last_updated': , - 'state': 'on', - }) -# --- diff --git a/tests/components/linear_garage_door/test_config_flow.py b/tests/components/linear_garage_door/test_config_flow.py deleted file mode 100644 index 64bdc589194..00000000000 --- a/tests/components/linear_garage_door/test_config_flow.py +++ /dev/null @@ -1,132 +0,0 @@ -"""Test the Linear Garage Door config flow.""" - -from unittest.mock import AsyncMock, patch - -from linear_garage_door.errors import InvalidLoginError -import pytest - -from homeassistant.components.linear_garage_door.const import DOMAIN -from homeassistant.config_entries import SOURCE_USER -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant -from homeassistant.data_entry_flow import FlowResultType - -from tests.common import MockConfigEntry - - -async def test_form( - hass: HomeAssistant, mock_linear: AsyncMock, mock_setup_entry: AsyncMock -) -> None: - """Test we get the form.""" - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - assert result["type"] is FlowResultType.FORM - assert not result["errors"] - - with patch( - "uuid.uuid4", - return_value="test-uuid", - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - }, - ) - await hass.async_block_till_done() - - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"site": "test-site-id"} - ) - await hass.async_block_till_done() - - assert result["type"] is FlowResultType.CREATE_ENTRY - assert result["title"] == "test-site-name" - assert result["data"] == { - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - } - assert len(mock_setup_entry.mock_calls) == 1 - - -async def test_reauth( - hass: HomeAssistant, - mock_linear: AsyncMock, - mock_setup_entry: AsyncMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test reauthentication.""" - mock_config_entry.add_to_hass(hass) - result = await mock_config_entry.start_reauth_flow(hass) - assert result["type"] is FlowResultType.FORM - assert result["step_id"] == "user" - - with patch( - "uuid.uuid4", - return_value="test-uuid", - ): - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_EMAIL: "new-email", - CONF_PASSWORD: "new-password", - }, - ) - await hass.async_block_till_done() - - assert result["type"] is FlowResultType.ABORT - assert result["reason"] == "reauth_successful" - - assert mock_config_entry.data == { - CONF_EMAIL: "new-email", - CONF_PASSWORD: "new-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - } - - -@pytest.mark.parametrize( - ("side_effect", "expected_error"), - [(InvalidLoginError, "invalid_auth"), (Exception, "unknown")], -) -async def test_form_exceptions( - hass: HomeAssistant, - mock_linear: AsyncMock, - mock_setup_entry: AsyncMock, - side_effect: Exception, - expected_error: str, -) -> None: - """Test we handle invalid auth.""" - mock_linear.login.side_effect = side_effect - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - await hass.async_block_till_done() - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - }, - ) - - assert result["type"] is FlowResultType.FORM - assert result["errors"] == {"base": expected_error} - mock_linear.login.side_effect = None - result = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - }, - ) - result = await hass.config_entries.flow.async_configure( - result["flow_id"], {"site": "test-site-id"} - ) - await hass.async_block_till_done() - - assert result["type"] is FlowResultType.CREATE_ENTRY diff --git a/tests/components/linear_garage_door/test_cover.py b/tests/components/linear_garage_door/test_cover.py deleted file mode 100644 index c031db88180..00000000000 --- a/tests/components/linear_garage_door/test_cover.py +++ /dev/null @@ -1,120 +0,0 @@ -"""Test Linear Garage Door cover.""" - -from datetime import timedelta -from unittest.mock import AsyncMock - -from freezegun.api import FrozenDateTimeFactory -from syrupy.assertion import SnapshotAssertion - -from homeassistant.components.cover import ( - DOMAIN as COVER_DOMAIN, - SERVICE_CLOSE_COVER, - SERVICE_OPEN_COVER, - CoverState, -) -from homeassistant.components.linear_garage_door import DOMAIN -from homeassistant.const import ATTR_ENTITY_ID, Platform -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import setup_integration - -from tests.common import ( - MockConfigEntry, - async_fire_time_changed, - async_load_json_object_fixture, - snapshot_platform, -) - - -async def test_covers( - hass: HomeAssistant, - mock_linear: AsyncMock, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, - mock_config_entry: MockConfigEntry, -) -> None: - """Test that data gets parsed and returned appropriately.""" - - await setup_integration(hass, mock_config_entry, [Platform.COVER]) - - await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -async def test_open_cover( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test that opening the cover works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.COVER]) - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_1"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 0 - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_OPEN_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_2"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 1 - - -async def test_close_cover( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test that closing the cover works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.COVER]) - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_2"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 0 - - await hass.services.async_call( - COVER_DOMAIN, - SERVICE_CLOSE_COVER, - {ATTR_ENTITY_ID: "cover.test_garage_1"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 1 - - -async def test_update_cover_state( - hass: HomeAssistant, - mock_linear: AsyncMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Test that closing the cover works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.COVER]) - - assert hass.states.get("cover.test_garage_1").state == CoverState.OPEN - assert hass.states.get("cover.test_garage_2").state == CoverState.CLOSED - - device_states = await async_load_json_object_fixture( - hass, "get_device_state_1.json", DOMAIN - ) - mock_linear.get_device_state.side_effect = lambda device_id: device_states[ - device_id - ] - - freezer.tick(timedelta(seconds=60)) - async_fire_time_changed(hass) - - assert hass.states.get("cover.test_garage_1").state == CoverState.CLOSING - assert hass.states.get("cover.test_garage_2").state == CoverState.OPENING diff --git a/tests/components/linear_garage_door/test_diagnostics.py b/tests/components/linear_garage_door/test_diagnostics.py deleted file mode 100644 index f51bb0a366c..00000000000 --- a/tests/components/linear_garage_door/test_diagnostics.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Test diagnostics of Linear Garage Door.""" - -from unittest.mock import AsyncMock - -from syrupy.assertion import SnapshotAssertion -from syrupy.filters import props - -from homeassistant.core import HomeAssistant - -from . import setup_integration - -from tests.common import MockConfigEntry -from tests.components.diagnostics import get_diagnostics_for_config_entry -from tests.typing import ClientSessionGenerator - - -async def test_entry_diagnostics( - hass: HomeAssistant, - hass_client: ClientSessionGenerator, - snapshot: SnapshotAssertion, - mock_linear: AsyncMock, - mock_config_entry: MockConfigEntry, -) -> None: - """Test config entry diagnostics.""" - await setup_integration(hass, mock_config_entry, []) - result = await get_diagnostics_for_config_entry( - hass, hass_client, mock_config_entry - ) - assert result == snapshot(exclude=props("created_at", "modified_at")) diff --git a/tests/components/linear_garage_door/test_init.py b/tests/components/linear_garage_door/test_init.py deleted file mode 100644 index 2693eda60bb..00000000000 --- a/tests/components/linear_garage_door/test_init.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Test Linear Garage Door init.""" - -from unittest.mock import AsyncMock - -from linear_garage_door import InvalidLoginError -import pytest - -from homeassistant.components.linear_garage_door.const import DOMAIN -from homeassistant.config_entries import ( - SOURCE_IGNORE, - ConfigEntryDisabler, - ConfigEntryState, -) -from homeassistant.const import CONF_EMAIL, CONF_PASSWORD -from homeassistant.core import HomeAssistant -from homeassistant.helpers import issue_registry as ir - -from . import setup_integration - -from tests.common import MockConfigEntry - - -async def test_unload_entry( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test the unload entry.""" - - await setup_integration(hass, mock_config_entry, []) - assert mock_config_entry.state is ConfigEntryState.LOADED - - await hass.config_entries.async_unload(mock_config_entry.entry_id) - await hass.async_block_till_done() - assert mock_config_entry.state is ConfigEntryState.NOT_LOADED - - -@pytest.mark.parametrize( - ("side_effect", "entry_state"), - [ - ( - InvalidLoginError( - "Login provided is invalid, please check the email and password" - ), - ConfigEntryState.SETUP_ERROR, - ), - (InvalidLoginError("Invalid login"), ConfigEntryState.SETUP_RETRY), - ], -) -async def test_setup_failure( - hass: HomeAssistant, - mock_linear: AsyncMock, - mock_config_entry: MockConfigEntry, - side_effect: Exception, - entry_state: ConfigEntryState, -) -> None: - """Test reauth trigger setup.""" - - mock_linear.login.side_effect = side_effect - - await setup_integration(hass, mock_config_entry, []) - assert mock_config_entry.state == entry_state - - -async def test_repair_issue( - hass: HomeAssistant, - mock_linear: AsyncMock, - issue_registry: ir.IssueRegistry, -) -> None: - """Test the Linear Garage Door configuration entry loading/unloading handles the repair.""" - config_entry_1 = MockConfigEntry( - domain=DOMAIN, - entry_id="acefdd4b3a4a0911067d1cf51414201e", - title="test-site-name", - data={ - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - await setup_integration(hass, config_entry_1, []) - assert config_entry_1.state is ConfigEntryState.LOADED - - # Add a second one - config_entry_2 = MockConfigEntry( - domain=DOMAIN, - entry_id="acefdd4b3a4a0911067d1cf51414201f", - title="test-site-name", - data={ - CONF_EMAIL: "test-email", - CONF_PASSWORD: "test-password", - "site_id": "test-site-id", - "device_id": "test-uuid", - }, - ) - await setup_integration(hass, config_entry_2, []) - assert config_entry_2.state is ConfigEntryState.LOADED - assert issue_registry.async_get_issue(DOMAIN, DOMAIN) - - # Add an ignored entry - config_entry_3 = MockConfigEntry( - source=SOURCE_IGNORE, - domain=DOMAIN, - ) - config_entry_3.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry_3.entry_id) - await hass.async_block_till_done() - - assert config_entry_3.state is ConfigEntryState.NOT_LOADED - - # Add a disabled entry - config_entry_4 = MockConfigEntry( - disabled_by=ConfigEntryDisabler.USER, - domain=DOMAIN, - ) - config_entry_4.add_to_hass(hass) - await hass.config_entries.async_setup(config_entry_4.entry_id) - await hass.async_block_till_done() - - assert config_entry_4.state is ConfigEntryState.NOT_LOADED - - # 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 - - # Check the ignored and disabled entries are removed - assert not hass.config_entries.async_entries(DOMAIN) diff --git a/tests/components/linear_garage_door/test_light.py b/tests/components/linear_garage_door/test_light.py deleted file mode 100644 index 1985b27aacd..00000000000 --- a/tests/components/linear_garage_door/test_light.py +++ /dev/null @@ -1,126 +0,0 @@ -"""Test Linear Garage Door light.""" - -from datetime import timedelta -from unittest.mock import AsyncMock - -from freezegun.api import FrozenDateTimeFactory -from syrupy.assertion import SnapshotAssertion - -from homeassistant.components.light import ( - DOMAIN as LIGHT_DOMAIN, - SERVICE_TURN_OFF, - SERVICE_TURN_ON, -) -from homeassistant.components.linear_garage_door import DOMAIN -from homeassistant.const import ( - ATTR_ENTITY_ID, - CONF_BRIGHTNESS, - STATE_OFF, - STATE_ON, - Platform, -) -from homeassistant.core import HomeAssistant -from homeassistant.helpers import entity_registry as er - -from . import setup_integration - -from tests.common import ( - MockConfigEntry, - async_fire_time_changed, - async_load_json_object_fixture, - snapshot_platform, -) - - -async def test_data( - hass: HomeAssistant, - mock_linear: AsyncMock, - entity_registry: er.EntityRegistry, - snapshot: SnapshotAssertion, - mock_config_entry: MockConfigEntry, -) -> None: - """Test that data gets parsed and returned appropriately.""" - - await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) - - await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) - - -async def test_turn_on( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test that turning on the light works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) - - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.test_garage_2_light"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 1 - - -async def test_turn_on_with_brightness( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test that turning on the light works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) - - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_ON, - {ATTR_ENTITY_ID: "light.test_garage_2_light", CONF_BRIGHTNESS: 50}, - blocking=True, - ) - - mock_linear.operate_device.assert_called_once_with( - "test2", "Light", "DimPercent:20" - ) - - -async def test_turn_off( - hass: HomeAssistant, mock_linear: AsyncMock, mock_config_entry: MockConfigEntry -) -> None: - """Test that turning off the light works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) - - await hass.services.async_call( - LIGHT_DOMAIN, - SERVICE_TURN_OFF, - {ATTR_ENTITY_ID: "light.test_garage_1_light"}, - blocking=True, - ) - - assert mock_linear.operate_device.call_count == 1 - - -async def test_update_light_state( - hass: HomeAssistant, - mock_linear: AsyncMock, - mock_config_entry: MockConfigEntry, - freezer: FrozenDateTimeFactory, -) -> None: - """Test that turning off the light works as intended.""" - - await setup_integration(hass, mock_config_entry, [Platform.LIGHT]) - - assert hass.states.get("light.test_garage_1_light").state == STATE_ON - assert hass.states.get("light.test_garage_2_light").state == STATE_OFF - - device_states = await async_load_json_object_fixture( - hass, "get_device_state_1.json", DOMAIN - ) - mock_linear.get_device_state.side_effect = lambda device_id: device_states[ - device_id - ] - - freezer.tick(timedelta(seconds=60)) - async_fire_time_changed(hass) - - assert hass.states.get("light.test_garage_1_light").state == STATE_OFF - assert hass.states.get("light.test_garage_2_light").state == STATE_ON