From 9f2a6af1ecb8bb9c3a39f6a81acdc7b258e1f839 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 22 Jan 2025 19:58:48 +0100 Subject: [PATCH] Only add Overseerr event if we are push based (#136258) --- .../components/overseerr/__init__.py | 7 +-- .../components/overseerr/coordinator.py | 1 + homeassistant/components/overseerr/event.py | 17 ++++-- tests/components/overseerr/test_event.py | 61 +++++++++++++++++++ tests/components/overseerr/test_init.py | 43 ++++++++++++- 5 files changed, 120 insertions(+), 9 deletions(-) diff --git a/homeassistant/components/overseerr/__init__.py b/homeassistant/components/overseerr/__init__.py index e4ac712e053..597d44f66cf 100644 --- a/homeassistant/components/overseerr/__init__.py +++ b/homeassistant/components/overseerr/__init__.py @@ -116,15 +116,13 @@ class OverseerrWebhookManager: allowed_methods=[METH_POST], ) if not await self.check_need_change(): + self.entry.runtime_data.push = True return for url in self.webhook_urls: if await self.test_and_set_webhook(url): return LOGGER.info("Failed to register Overseerr webhook") - if ( - cloud.async_active_subscription(self.hass) - and CONF_CLOUDHOOK_URL not in self.entry.data - ): + if cloud.async_active_subscription(self.hass): LOGGER.info("Trying to register a cloudhook URL") url = await _async_cloudhook_generate_url(self.hass, self.entry) if await self.test_and_set_webhook(url): @@ -151,6 +149,7 @@ class OverseerrWebhookManager: webhook_url=url, json_payload=JSON_PAYLOAD, ) + self.entry.runtime_data.push = True return True return False diff --git a/homeassistant/components/overseerr/coordinator.py b/homeassistant/components/overseerr/coordinator.py index 56002ddf558..2149dcbec7c 100644 --- a/homeassistant/components/overseerr/coordinator.py +++ b/homeassistant/components/overseerr/coordinator.py @@ -47,6 +47,7 @@ class OverseerrCoordinator(DataUpdateCoordinator[RequestCount]): session=async_get_clientsession(hass), ) self.url = URL.build(host=host, port=port, scheme="https" if ssl else "http") + self.push = False async def _async_update_data(self) -> RequestCount: """Fetch data from API endpoint.""" diff --git a/homeassistant/components/overseerr/event.py b/homeassistant/components/overseerr/event.py index 9dbfe37080b..589a80c5404 100644 --- a/homeassistant/components/overseerr/event.py +++ b/homeassistant/components/overseerr/event.py @@ -4,11 +4,13 @@ from dataclasses import dataclass from typing import Any from homeassistant.components.event import EventEntity, EventEntityDescription +from homeassistant.const import Platform from homeassistant.core import HomeAssistant, callback +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity_platform import AddEntitiesCallback -from . import EVENT_KEY +from . import DOMAIN, EVENT_KEY from .coordinator import OverseerrConfigEntry, OverseerrCoordinator from .entity import OverseerrEntity @@ -47,10 +49,17 @@ async def async_setup_entry( """Set up Overseerr sensor entities based on a config entry.""" coordinator = entry.runtime_data - async_add_entities( - OverseerrEvent(coordinator, description) for description in EVENTS + ent_reg = er.async_get(hass) + + event_entities_setup_before = ent_reg.async_get_entity_id( + Platform.EVENT, DOMAIN, f"{entry.entry_id}-media" ) + if coordinator.push or event_entities_setup_before: + async_add_entities( + OverseerrEvent(coordinator, description) for description in EVENTS + ) + class OverseerrEvent(OverseerrEntity, EventEntity): """Defines a Overseerr event entity.""" @@ -94,7 +103,7 @@ class OverseerrEvent(OverseerrEntity, EventEntity): @property def available(self) -> bool: """Return True if entity is available.""" - return self._attr_available + return self._attr_available and self.coordinator.push def parse_event(event: dict[str, Any], nullable_fields: list[str]) -> dict[str, Any]: diff --git a/tests/components/overseerr/test_event.py b/tests/components/overseerr/test_event.py index 7ad6b53c7ed..3866ccc09ca 100644 --- a/tests/components/overseerr/test_event.py +++ b/tests/components/overseerr/test_event.py @@ -107,3 +107,64 @@ async def test_event_goes_unavailable( assert ( hass.states.get("event.overseerr_last_media_event").state == STATE_UNAVAILABLE ) + + +async def test_not_push_based( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_overseerr_client_needs_change: AsyncMock, +) -> None: + """Test event entities aren't created if not push based.""" + + mock_overseerr_client_needs_change.test_webhook_notification_config.return_value = ( + False + ) + + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("event.overseerr_last_media_event") is None + + +async def test_cant_fetch_webhook_config( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_overseerr_client: AsyncMock, +) -> None: + """Test event entities aren't created if not push based.""" + + mock_overseerr_client.get_webhook_notification_config.side_effect = ( + OverseerrConnectionError("Boom") + ) + + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("event.overseerr_last_media_event") is None + + +async def test_not_push_based_but_was_before( + hass: HomeAssistant, + mock_config_entry: MockConfigEntry, + mock_overseerr_client_needs_change: AsyncMock, + entity_registry: er.EntityRegistry, +) -> None: + """Test event entities are created if push based in the past.""" + + entity_registry.async_get_or_create( + Platform.EVENT, + DOMAIN, + f"{mock_config_entry.entry_id}-media", + suggested_object_id="overseerr_last_media_event", + disabled_by=None, + ) + + mock_overseerr_client_needs_change.test_webhook_notification_config.return_value = ( + False + ) + + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("event.overseerr_last_media_event") is not None + + assert ( + hass.states.get("event.overseerr_last_media_event").state == STATE_UNAVAILABLE + ) diff --git a/tests/components/overseerr/test_init.py b/tests/components/overseerr/test_init.py index 27c9f3fb3e9..6418e2103db 100644 --- a/tests/components/overseerr/test_init.py +++ b/tests/components/overseerr/test_init.py @@ -9,6 +9,7 @@ from python_overseerr.models import WebhookNotificationOptions from syrupy import SnapshotAssertion from homeassistant.components import cloud +from homeassistant.components.cloud import CloudNotAvailable from homeassistant.components.overseerr import ( CONF_CLOUDHOOK_URL, JSON_PAYLOAD, @@ -362,10 +363,50 @@ async def test_cloudhook_not_connecting( len( mock_overseerr_client_needs_change.test_webhook_notification_config.mock_calls ) - == 2 + == 3 ) mock_overseerr_client_needs_change.set_webhook_notification_config.assert_not_called() assert hass.config_entries.async_entries(DOMAIN) fake_create_cloudhook.assert_not_called() + + +async def test_removing_entry_with_cloud_unavailable( + hass: HomeAssistant, + mock_cloudhook_config_entry: MockConfigEntry, + mock_overseerr_client: AsyncMock, +) -> None: + """Test handling cloud unavailable when deleting entry.""" + + await mock_cloud(hass) + await hass.async_block_till_done() + + with ( + patch("homeassistant.components.cloud.async_is_logged_in", return_value=True), + patch("homeassistant.components.cloud.async_is_connected", return_value=True), + patch.object(cloud, "async_active_subscription", return_value=True), + patch( + "homeassistant.components.cloud.async_create_cloudhook", + return_value="https://hooks.nabu.casa/ABCD", + ), + patch( + "homeassistant.helpers.config_entry_oauth2_flow.async_get_config_entry_implementation", + ), + patch( + "homeassistant.components.cloud.async_delete_cloudhook", + side_effect=CloudNotAvailable(), + ), + ): + await setup_integration(hass, mock_cloudhook_config_entry) + + assert cloud.async_active_subscription(hass) is True + + await hass.async_block_till_done() + assert hass.config_entries.async_entries(DOMAIN) + + for config_entry in hass.config_entries.async_entries(DOMAIN): + await hass.config_entries.async_remove(config_entry.entry_id) + + await hass.async_block_till_done() + assert not hass.config_entries.async_entries(DOMAIN)