From ae707299320df6dbfa3f1cfb642afc05fdf93aeb Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Sun, 4 Feb 2024 15:02:21 +0100 Subject: [PATCH] Revert "Add webhook support to tedee integration (#106846)" (#109408) --- homeassistant/components/tedee/__init__.py | 80 +------------- homeassistant/components/tedee/config_flow.py | 8 +- homeassistant/components/tedee/coordinator.py | 26 +---- homeassistant/components/tedee/manifest.json | 2 +- tests/components/tedee/conftest.py | 7 +- tests/components/tedee/test_config_flow.py | 43 +++----- tests/components/tedee/test_init.py | 104 +----------------- tests/components/tedee/test_lock.py | 34 +----- 8 files changed, 30 insertions(+), 274 deletions(-) diff --git a/homeassistant/components/tedee/__init__.py b/homeassistant/components/tedee/__init__.py index cbc608d03a6..eeb0f8e0d5a 100644 --- a/homeassistant/components/tedee/__init__.py +++ b/homeassistant/components/tedee/__init__.py @@ -1,25 +1,12 @@ """Init the tedee component.""" -from collections.abc import Awaitable, Callable -from http import HTTPStatus import logging -from typing import Any -from aiohttp.hdrs import METH_POST -from aiohttp.web import Request, Response -from pytedee_async.exception import TedeeWebhookException - -from homeassistant.components.http import HomeAssistantView -from homeassistant.components.webhook import ( - async_generate_url as webhook_generate_url, - async_register as webhook_register, - async_unregister as webhook_unregister, -) from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_WEBHOOK_ID, EVENT_HOMEASSISTANT_STOP, Platform +from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from .const import DOMAIN, NAME +from .const import DOMAIN from .coordinator import TedeeApiCoordinator PLATFORMS = [ @@ -50,38 +37,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator - async def unregister_webhook(_: Any) -> None: - await coordinator.async_unregister_webhook() - webhook_unregister(hass, entry.data[CONF_WEBHOOK_ID]) - - async def register_webhook() -> None: - webhook_url = webhook_generate_url(hass, entry.data[CONF_WEBHOOK_ID]) - webhook_name = "Tedee" - if entry.title != NAME: - webhook_name = f"{NAME} {entry.title}" - - webhook_register( - hass, - DOMAIN, - webhook_name, - entry.data[CONF_WEBHOOK_ID], - get_webhook_handler(coordinator), - allowed_methods=[METH_POST], - ) - _LOGGER.debug("Registered Tedee webhook at hass: %s", webhook_url) - - try: - await coordinator.async_register_webhook(webhook_url) - except TedeeWebhookException as ex: - _LOGGER.warning("Failed to register Tedee webhook from bridge: %s", ex) - else: - entry.async_on_unload( - hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook) - ) - - entry.async_create_background_task( - hass, register_webhook(), "tedee_register_webhook" - ) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) return True @@ -90,34 +45,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Unload a config entry.""" - if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS): + unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) + + if unload_ok: hass.data[DOMAIN].pop(entry.entry_id) return unload_ok - - -def get_webhook_handler( - coordinator: TedeeApiCoordinator, -) -> Callable[[HomeAssistant, str, Request], Awaitable[Response | None]]: - """Return webhook handler.""" - - async def async_webhook_handler( - hass: HomeAssistant, webhook_id: str, request: Request - ) -> Response | None: - # Handle http post calls to the path. - if not request.body_exists: - return HomeAssistantView.json( - result="No Body", status_code=HTTPStatus.BAD_REQUEST - ) - - body = await request.json() - try: - coordinator.webhook_received(body) - except TedeeWebhookException as ex: - return HomeAssistantView.json( - result=str(ex), status_code=HTTPStatus.BAD_REQUEST - ) - - return HomeAssistantView.json(result="OK", status_code=HTTPStatus.OK) - - return async_webhook_handler diff --git a/homeassistant/components/tedee/config_flow.py b/homeassistant/components/tedee/config_flow.py index 8bd9efd2b17..075a4c998ea 100644 --- a/homeassistant/components/tedee/config_flow.py +++ b/homeassistant/components/tedee/config_flow.py @@ -11,9 +11,8 @@ from pytedee_async import ( ) import voluptuous as vol -from homeassistant.components.webhook import async_generate_id as webhook_generate_id from homeassistant.config_entries import ConfigEntry, ConfigFlow -from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID +from homeassistant.const import CONF_HOST from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers.aiohttp_client import async_get_clientsession @@ -62,10 +61,7 @@ class TedeeConfigFlow(ConfigFlow, domain=DOMAIN): return self.async_abort(reason="reauth_successful") await self.async_set_unique_id(local_bridge.serial) self._abort_if_unique_id_configured() - return self.async_create_entry( - title=NAME, - data={**user_input, CONF_WEBHOOK_ID: webhook_generate_id()}, - ) + return self.async_create_entry(title=NAME, data=user_input) return self.async_show_form( step_id="user", diff --git a/homeassistant/components/tedee/coordinator.py b/homeassistant/components/tedee/coordinator.py index cdd907b2e58..c846f2a8d9a 100644 --- a/homeassistant/components/tedee/coordinator.py +++ b/homeassistant/components/tedee/coordinator.py @@ -3,7 +3,6 @@ from collections.abc import Awaitable, Callable from datetime import timedelta import logging import time -from typing import Any from pytedee_async import ( TedeeClient, @@ -11,7 +10,6 @@ from pytedee_async import ( TedeeDataUpdateException, TedeeLocalAuthException, TedeeLock, - TedeeWebhookException, ) from pytedee_async.bridge import TedeeBridge @@ -25,7 +23,7 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN -SCAN_INTERVAL = timedelta(seconds=30) +SCAN_INTERVAL = timedelta(seconds=20) GET_LOCKS_INTERVAL_SECONDS = 3600 _LOGGER = logging.getLogger(__name__) @@ -55,7 +53,6 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]): self._next_get_locks = time.time() self._locks_last_update: set[int] = set() self.new_lock_callbacks: list[Callable[[int], None]] = [] - self.tedee_webhook_id: int | None = None @property def bridge(self) -> TedeeBridge: @@ -106,27 +103,6 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]): except (TedeeClientException, TimeoutError) as ex: raise UpdateFailed("Querying API failed. Error: %s" % str(ex)) from ex - def webhook_received(self, message: dict[str, Any]) -> None: - """Handle webhook message.""" - self.tedee_client.parse_webhook_message(message) - self.async_set_updated_data(self.tedee_client.locks_dict) - - async def async_register_webhook(self, webhook_url: str) -> None: - """Register the webhook at the Tedee bridge.""" - self.tedee_webhook_id = await self.tedee_client.register_webhook(webhook_url) - - async def async_unregister_webhook(self) -> None: - """Unregister the webhook at the Tedee bridge.""" - if self.tedee_webhook_id is not None: - try: - await self.tedee_client.delete_webhook(self.tedee_webhook_id) - except TedeeWebhookException as ex: - _LOGGER.warning( - "Failed to unregister Tedee webhook from bridge: %s", ex - ) - else: - _LOGGER.debug("Unregistered Tedee webhook") - def _async_add_remove_locks(self) -> None: """Add new locks, remove non-existing locks.""" if not self._locks_last_update: diff --git a/homeassistant/components/tedee/manifest.json b/homeassistant/components/tedee/manifest.json index 0a13b2266fa..1776e3b7ab2 100644 --- a/homeassistant/components/tedee/manifest.json +++ b/homeassistant/components/tedee/manifest.json @@ -3,7 +3,7 @@ "name": "Tedee", "codeowners": ["@patrickhilker", "@zweckj"], "config_flow": true, - "dependencies": ["http", "webhook"], + "dependencies": ["http"], "documentation": "https://www.home-assistant.io/integrations/tedee", "iot_class": "local_push", "requirements": ["pytedee-async==0.2.13"] diff --git a/tests/components/tedee/conftest.py b/tests/components/tedee/conftest.py index a633b1642ea..21fb4047ab3 100644 --- a/tests/components/tedee/conftest.py +++ b/tests/components/tedee/conftest.py @@ -10,13 +10,11 @@ from pytedee_async.lock import TedeeLock import pytest from homeassistant.components.tedee.const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN -from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID +from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from tests.common import MockConfigEntry, load_fixture -WEBHOOK_ID = "bq33efxmdi3vxy55q2wbnudbra7iv8mjrq9x0gea33g4zqtd87093pwveg8xcb33" - @pytest.fixture def mock_config_entry() -> MockConfigEntry: @@ -27,7 +25,6 @@ def mock_config_entry() -> MockConfigEntry: data={ CONF_LOCAL_ACCESS_TOKEN: "api_token", CONF_HOST: "192.168.1.42", - CONF_WEBHOOK_ID: WEBHOOK_ID, }, unique_id="0000-0000", ) @@ -62,8 +59,6 @@ def mock_tedee(request) -> Generator[MagicMock, None, None]: tedee.get_local_bridge.return_value = TedeeBridge(0, "0000-0000", "Bridge-AB1C") tedee.parse_webhook_message.return_value = None - tedee.register_webhook.return_value = 1 - tedee.delete_webhooks.return_value = None locks_json = json.loads(load_fixture("locks.json", DOMAIN)) diff --git a/tests/components/tedee/test_config_flow.py b/tests/components/tedee/test_config_flow.py index 68a61842fc3..bc5b73aa4a9 100644 --- a/tests/components/tedee/test_config_flow.py +++ b/tests/components/tedee/test_config_flow.py @@ -1,5 +1,5 @@ """Test the Tedee config flow.""" -from unittest.mock import MagicMock, patch +from unittest.mock import MagicMock from pytedee_async import ( TedeeClientException, @@ -10,12 +10,10 @@ import pytest from homeassistant.components.tedee.const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER -from homeassistant.const import CONF_HOST, CONF_WEBHOOK_ID +from homeassistant.const import CONF_HOST from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType -from .conftest import WEBHOOK_ID - from tests.common import MockConfigEntry FLOW_UNIQUE_ID = "112233445566778899" @@ -24,30 +22,25 @@ LOCAL_ACCESS_TOKEN = "api_token" async def test_flow(hass: HomeAssistant, mock_tedee: MagicMock) -> None: """Test config flow with one bridge.""" - with patch( - "homeassistant.components.tedee.config_flow.webhook_generate_id", - return_value=WEBHOOK_ID, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, context={"source": SOURCE_USER} - ) - await hass.async_block_till_done() - assert result["type"] == FlowResultType.FORM + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": SOURCE_USER} + ) + await hass.async_block_till_done() + assert result["type"] == FlowResultType.FORM - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - { - CONF_HOST: "192.168.1.62", - CONF_LOCAL_ACCESS_TOKEN: "token", - }, - ) - - assert result2["type"] == FlowResultType.CREATE_ENTRY - assert result2["data"] == { + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + { CONF_HOST: "192.168.1.62", CONF_LOCAL_ACCESS_TOKEN: "token", - CONF_WEBHOOK_ID: WEBHOOK_ID, - } + }, + ) + + assert result2["type"] == FlowResultType.CREATE_ENTRY + assert result2["data"] == { + CONF_HOST: "192.168.1.62", + CONF_LOCAL_ACCESS_TOKEN: "token", + } async def test_flow_already_configured( diff --git a/tests/components/tedee/test_init.py b/tests/components/tedee/test_init.py index 05fb2c1d6eb..ca64c01a983 100644 --- a/tests/components/tedee/test_init.py +++ b/tests/components/tedee/test_init.py @@ -1,27 +1,15 @@ """Test initialization of tedee.""" -from http import HTTPStatus -from typing import Any from unittest.mock import MagicMock -from urllib.parse import urlparse -from pytedee_async.exception import ( - TedeeAuthException, - TedeeClientException, - TedeeWebhookException, -) +from pytedee_async.exception import TedeeAuthException, TedeeClientException import pytest from syrupy import SnapshotAssertion -from homeassistant.components.webhook import async_generate_url from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr -from .conftest import WEBHOOK_ID - from tests.common import MockConfigEntry -from tests.typing import ClientSessionGenerator async def test_load_unload_config_entry( @@ -62,62 +50,6 @@ async def test_config_entry_not_ready( assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY -async def test_cleanup_on_shutdown( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_tedee: MagicMock, -) -> None: - """Test the webhook is cleaned up on shutdown.""" - mock_config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - assert mock_config_entry.state is ConfigEntryState.LOADED - - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - mock_tedee.delete_webhook.assert_called_once() - - -async def test_webhook_cleanup_errors( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_tedee: MagicMock, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the webhook is cleaned up on shutdown.""" - mock_config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - assert mock_config_entry.state is ConfigEntryState.LOADED - - mock_tedee.delete_webhook.side_effect = TedeeWebhookException("") - - hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) - await hass.async_block_till_done() - mock_tedee.delete_webhook.assert_called_once() - assert "Failed to unregister Tedee webhook from bridge" in caplog.text - - -async def test_webhook_registration_errors( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_tedee: MagicMock, - caplog: pytest.LogCaptureFixture, -) -> None: - """Test the webhook is cleaned up on shutdown.""" - mock_tedee.register_webhook.side_effect = TedeeWebhookException("") - mock_config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - assert mock_config_entry.state is ConfigEntryState.LOADED - - mock_tedee.register_webhook.assert_called_once() - assert "Failed to register Tedee webhook from bridge" in caplog.text - - async def test_bridge_device( hass: HomeAssistant, mock_config_entry: MockConfigEntry, @@ -135,37 +67,3 @@ async def test_bridge_device( ) assert device assert device == snapshot - - -@pytest.mark.parametrize( - ("body", "expected_code", "side_effect"), - [ - ({"hello": "world"}, HTTPStatus.OK, None), # Success - (None, HTTPStatus.BAD_REQUEST, None), # Missing data - ({}, HTTPStatus.BAD_REQUEST, TedeeWebhookException), # Error - ], -) -async def test_webhook_post( - hass: HomeAssistant, - mock_config_entry: MockConfigEntry, - mock_tedee: MagicMock, - hass_client_no_auth: ClientSessionGenerator, - body: dict[str, Any], - expected_code: HTTPStatus, - side_effect: Exception, -) -> None: - """Test webhook callback.""" - - mock_config_entry.add_to_hass(hass) - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - client = await hass_client_no_auth() - webhook_url = async_generate_url(hass, WEBHOOK_ID) - mock_tedee.parse_webhook_message.side_effect = side_effect - resp = await client.post(urlparse(webhook_url).path, json=body) - - # Wait for remaining tasks to complete. - await hass.async_block_till_done() - - assert resp.status == expected_code diff --git a/tests/components/tedee/test_lock.py b/tests/components/tedee/test_lock.py index 2f8b1e2b36d..fca1ae2b07f 100644 --- a/tests/components/tedee/test_lock.py +++ b/tests/components/tedee/test_lock.py @@ -1,10 +1,9 @@ """Tests for tedee lock.""" from datetime import timedelta from unittest.mock import MagicMock -from urllib.parse import urlparse from freezegun.api import FrozenDateTimeFactory -from pytedee_async import TedeeLock, TedeeLockState +from pytedee_async import TedeeLock from pytedee_async.exception import ( TedeeClientException, TedeeDataUpdateException, @@ -18,21 +17,15 @@ from homeassistant.components.lock import ( SERVICE_LOCK, SERVICE_OPEN, SERVICE_UNLOCK, - STATE_LOCKED, STATE_LOCKING, - STATE_UNLOCKED, STATE_UNLOCKING, ) -from homeassistant.components.webhook import async_generate_url from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er -from .conftest import WEBHOOK_ID - from tests.common import MockConfigEntry, async_fire_time_changed -from tests.typing import ClientSessionGenerator pytestmark = pytest.mark.usefixtures("init_integration") @@ -273,28 +266,3 @@ async def test_new_lock( assert state state = hass.states.get("lock.lock_6g7h") assert state - - -async def test_webhook_update( - hass: HomeAssistant, - mock_tedee: MagicMock, - hass_client_no_auth: ClientSessionGenerator, -) -> None: - """Test updated data set through webhook.""" - - state = hass.states.get("lock.lock_1a2b") - assert state - assert state.state == STATE_UNLOCKED - - webhook_data = {"dummystate": 6} - mock_tedee.locks_dict[ - 12345 - ].state = TedeeLockState.LOCKED # is updated in the lib, so mock and assert in L296 - client = await hass_client_no_auth() - webhook_url = async_generate_url(hass, WEBHOOK_ID) - await client.post(urlparse(webhook_url).path, json=webhook_data) - mock_tedee.parse_webhook_message.assert_called_once_with(webhook_data) - - state = hass.states.get("lock.lock_1a2b") - assert state - assert state.state == STATE_LOCKED