From 3f57b467567dba81188c585a758314378c1912eb Mon Sep 17 00:00:00 2001 From: Anthony Garera Date: Tue, 16 Dec 2025 11:11:28 -0500 Subject: [PATCH] Add issue sensors to Overseerr integration (#158888) --- .../components/overseerr/__init__.py | 3 +- homeassistant/components/overseerr/const.py | 4 + .../components/overseerr/coordinator.py | 10 +- homeassistant/components/overseerr/models.py | 13 + homeassistant/components/overseerr/sensor.py | 49 ++- .../components/overseerr/strings.json | 36 +++ tests/components/overseerr/conftest.py | 5 +- .../overseerr/fixtures/issue_count.json | 9 + .../overseerr/fixtures/webhook_config.json | 2 +- .../fixtures/webhook_issue_reported.json | 23 ++ .../overseerr/snapshots/test_diagnostics.ambr | 54 +++- .../overseerr/snapshots/test_sensor.ambr | 306 ++++++++++++++++++ tests/components/overseerr/test_init.py | 6 +- tests/components/overseerr/test_sensor.py | 34 +- 14 files changed, 515 insertions(+), 39 deletions(-) create mode 100644 homeassistant/components/overseerr/models.py create mode 100644 tests/components/overseerr/fixtures/issue_count.json create mode 100644 tests/components/overseerr/fixtures/webhook_issue_reported.json diff --git a/homeassistant/components/overseerr/__init__.py b/homeassistant/components/overseerr/__init__.py index 3e7b5f32272..66cec82fb61 100644 --- a/homeassistant/components/overseerr/__init__.py +++ b/homeassistant/components/overseerr/__init__.py @@ -159,7 +159,8 @@ class OverseerrWebhookManager: """Handle webhook.""" data = await request.json() LOGGER.debug("Received webhook payload: %s", data) - if data["notification_type"].startswith("MEDIA"): + notification_type = data["notification_type"] + if notification_type.startswith(("REQUEST_", "ISSUE_", "MEDIA_")): await self.entry.runtime_data.async_refresh() async_dispatcher_send(hass, EVENT_KEY, data) return HomeAssistantView.json({"message": "ok"}) diff --git a/homeassistant/components/overseerr/const.py b/homeassistant/components/overseerr/const.py index da1fc051608..b955d2a50a4 100644 --- a/homeassistant/components/overseerr/const.py +++ b/homeassistant/components/overseerr/const.py @@ -22,6 +22,10 @@ REGISTERED_NOTIFICATIONS = ( | NotificationType.REQUEST_AVAILABLE | NotificationType.REQUEST_PROCESSING_FAILED | NotificationType.REQUEST_AUTOMATICALLY_APPROVED + | NotificationType.ISSUE_REPORTED + | NotificationType.ISSUE_COMMENTED + | NotificationType.ISSUE_RESOLVED + | NotificationType.ISSUE_REOPENED ) JSON_PAYLOAD = ( '"{\\"notification_type\\":\\"{{notification_type}}\\",\\"subject\\":\\"{{subject}' diff --git a/homeassistant/components/overseerr/coordinator.py b/homeassistant/components/overseerr/coordinator.py index 2149dcbec7c..af6c3c30945 100644 --- a/homeassistant/components/overseerr/coordinator.py +++ b/homeassistant/components/overseerr/coordinator.py @@ -6,7 +6,6 @@ from python_overseerr import ( OverseerrAuthenticationError, OverseerrClient, OverseerrConnectionError, - RequestCount, ) from yarl import URL @@ -18,11 +17,12 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import DOMAIN, LOGGER +from .models import OverseerrData type OverseerrConfigEntry = ConfigEntry[OverseerrCoordinator] -class OverseerrCoordinator(DataUpdateCoordinator[RequestCount]): +class OverseerrCoordinator(DataUpdateCoordinator[OverseerrData]): """Class to manage fetching Overseerr data.""" config_entry: OverseerrConfigEntry @@ -49,10 +49,12 @@ class OverseerrCoordinator(DataUpdateCoordinator[RequestCount]): self.url = URL.build(host=host, port=port, scheme="https" if ssl else "http") self.push = False - async def _async_update_data(self) -> RequestCount: + async def _async_update_data(self) -> OverseerrData: """Fetch data from API endpoint.""" try: - return await self.client.get_request_count() + requests = await self.client.get_request_count() + issues = await self.client.get_issue_count() + return OverseerrData(requests=requests, issues=issues) except OverseerrAuthenticationError as err: raise ConfigEntryAuthFailed( translation_domain=DOMAIN, diff --git a/homeassistant/components/overseerr/models.py b/homeassistant/components/overseerr/models.py new file mode 100644 index 00000000000..8e040a06fb8 --- /dev/null +++ b/homeassistant/components/overseerr/models.py @@ -0,0 +1,13 @@ +"""Data models for Overseerr integration.""" + +from dataclasses import dataclass + +from python_overseerr import IssueCount, RequestCount + + +@dataclass +class OverseerrData: + """Data model for Overseerr coordinator.""" + + requests: RequestCount + issues: IssueCount diff --git a/homeassistant/components/overseerr/sensor.py b/homeassistant/components/overseerr/sensor.py index 8f0cf93b7ce..dbac8d94914 100644 --- a/homeassistant/components/overseerr/sensor.py +++ b/homeassistant/components/overseerr/sensor.py @@ -3,8 +3,6 @@ from collections.abc import Callable from dataclasses import dataclass -from python_overseerr import RequestCount - from homeassistant.components.sensor import ( SensorEntity, SensorEntityDescription, @@ -16,6 +14,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from .const import REQUESTS from .coordinator import OverseerrConfigEntry, OverseerrCoordinator from .entity import OverseerrEntity +from .models import OverseerrData PARALLEL_UPDATES = 0 @@ -24,7 +23,7 @@ PARALLEL_UPDATES = 0 class OverseerrSensorEntityDescription(SensorEntityDescription): """Describes Overseerr config sensor entity.""" - value_fn: Callable[[RequestCount], int] + value_fn: Callable[[OverseerrData], int] SENSORS: tuple[OverseerrSensorEntityDescription, ...] = ( @@ -32,43 +31,73 @@ SENSORS: tuple[OverseerrSensorEntityDescription, ...] = ( key="total_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.total, + value_fn=lambda data: data.requests.total, ), OverseerrSensorEntityDescription( key="movie_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.movie, + value_fn=lambda data: data.requests.movie, ), OverseerrSensorEntityDescription( key="tv_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.tv, + value_fn=lambda data: data.requests.tv, ), OverseerrSensorEntityDescription( key="pending_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.pending, + value_fn=lambda data: data.requests.pending, ), OverseerrSensorEntityDescription( key="declined_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.declined, + value_fn=lambda data: data.requests.declined, ), OverseerrSensorEntityDescription( key="processing_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.processing, + value_fn=lambda data: data.requests.processing, ), OverseerrSensorEntityDescription( key="available_requests", native_unit_of_measurement=REQUESTS, state_class=SensorStateClass.TOTAL, - value_fn=lambda count: count.available, + value_fn=lambda data: data.requests.available, + ), + OverseerrSensorEntityDescription( + key="total_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.total, + ), + OverseerrSensorEntityDescription( + key="open_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.open, + ), + OverseerrSensorEntityDescription( + key="closed_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.closed, + ), + OverseerrSensorEntityDescription( + key="video_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.video, + ), + OverseerrSensorEntityDescription( + key="audio_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.audio, + ), + OverseerrSensorEntityDescription( + key="subtitle_issues", + state_class=SensorStateClass.TOTAL, + value_fn=lambda data: data.issues.subtitles, ), ) diff --git a/homeassistant/components/overseerr/strings.json b/homeassistant/components/overseerr/strings.json index b9a706d2539..4e8829f269f 100644 --- a/homeassistant/components/overseerr/strings.json +++ b/homeassistant/components/overseerr/strings.json @@ -50,26 +50,62 @@ } }, "sensor": { + "audio_issues": { + "name": "Audio issues", + "state": { + "measurement": "issues" + } + }, "available_requests": { "name": "Available requests" }, + "closed_issues": { + "name": "Closed issues", + "state": { + "measurement": "issues" + } + }, "declined_requests": { "name": "Declined requests" }, "movie_requests": { "name": "Movie requests" }, + "open_issues": { + "name": "Open issues", + "state": { + "measurement": "issues" + } + }, "pending_requests": { "name": "Pending requests" }, "processing_requests": { "name": "Processing requests" }, + "subtitle_issues": { + "name": "Subtitle issues", + "state": { + "measurement": "issues" + } + }, + "total_issues": { + "name": "Total issues", + "state": { + "measurement": "issues" + } + }, "total_requests": { "name": "Total requests" }, "tv_requests": { "name": "TV requests" + }, + "video_issues": { + "name": "Video issues", + "state": { + "measurement": "issues" + } } } }, diff --git a/tests/components/overseerr/conftest.py b/tests/components/overseerr/conftest.py index 9ae6be407ec..d23ae55e77e 100644 --- a/tests/components/overseerr/conftest.py +++ b/tests/components/overseerr/conftest.py @@ -4,7 +4,7 @@ from collections.abc import Generator from unittest.mock import AsyncMock, patch import pytest -from python_overseerr import MovieDetails, RequestCount, RequestResponse +from python_overseerr import IssueCount, MovieDetails, RequestCount, RequestResponse from python_overseerr.models import TVDetails, WebhookNotificationConfig from homeassistant.components.overseerr import CONF_CLOUDHOOK_URL @@ -49,6 +49,9 @@ def mock_overseerr_client() -> Generator[AsyncMock]: client.get_request_count.return_value = RequestCount.from_json( load_fixture("request_count.json", DOMAIN) ) + client.get_issue_count.return_value = IssueCount.from_json( + load_fixture("issue_count.json", DOMAIN) + ) client.get_webhook_notification_config.return_value = ( WebhookNotificationConfig.from_json( load_fixture("webhook_config.json", DOMAIN) diff --git a/tests/components/overseerr/fixtures/issue_count.json b/tests/components/overseerr/fixtures/issue_count.json new file mode 100644 index 00000000000..8cfb6eabda3 --- /dev/null +++ b/tests/components/overseerr/fixtures/issue_count.json @@ -0,0 +1,9 @@ +{ + "total": 15, + "video": 6, + "audio": 4, + "subtitles": 3, + "others": 2, + "open": 10, + "closed": 5 +} diff --git a/tests/components/overseerr/fixtures/webhook_config.json b/tests/components/overseerr/fixtures/webhook_config.json index 2b3388444d2..48619a41ba7 100644 --- a/tests/components/overseerr/fixtures/webhook_config.json +++ b/tests/components/overseerr/fixtures/webhook_config.json @@ -1,6 +1,6 @@ { "enabled": true, - "types": 222, + "types": 4062, "options": { "jsonPayload": "{\"notification_type\":\"{{notification_type}}\",\"subject\":\"{{subject}}\",\"message\":\"{{message}}\",\"image\":\"{{image}}\",\"{{media}}\":{\"media_type\":\"{{media_type}}\",\"tmdb_id\":\"{{media_tmdbid}}\",\"tvdb_id\":\"{{media_tvdbid}}\",\"status\":\"{{media_status}}\",\"status4k\":\"{{media_status4k}}\"},\"{{request}}\":{\"request_id\":\"{{request_id}}\",\"requested_by_email\":\"{{requestedBy_email}}\",\"requested_by_username\":\"{{requestedBy_username}}\",\"requested_by_avatar\":\"{{requestedBy_avatar}}\",\"requested_by_settings_discord_id\":\"{{requestedBy_settings_discordId}}\",\"requested_by_settings_telegram_chat_id\":\"{{requestedBy_settings_telegramChatId}}\"},\"{{issue}}\":{\"issue_id\":\"{{issue_id}}\",\"issue_type\":\"{{issue_type}}\",\"issue_status\":\"{{issue_status}}\",\"reported_by_email\":\"{{reportedBy_email}}\",\"reported_by_username\":\"{{reportedBy_username}}\",\"reported_by_avatar\":\"{{reportedBy_avatar}}\",\"reported_by_settings_discord_id\":\"{{reportedBy_settings_discordId}}\",\"reported_by_settings_telegram_chat_id\":\"{{reportedBy_settings_telegramChatId}}\"},\"{{comment}}\":{\"comment_message\":\"{{comment_message}}\",\"commented_by_email\":\"{{commentedBy_email}}\",\"commented_by_username\":\"{{commentedBy_username}}\",\"commented_by_avatar\":\"{{commentedBy_avatar}}\",\"commented_by_settings_discord_id\":\"{{commentedBy_settings_discordId}}\",\"commented_by_settings_telegram_chat_id\":\"{{commentedBy_settings_telegramChatId}}\"}}", "webhookUrl": "http://10.10.10.10:8123/api/webhook/test-webhook-id" diff --git a/tests/components/overseerr/fixtures/webhook_issue_reported.json b/tests/components/overseerr/fixtures/webhook_issue_reported.json new file mode 100644 index 00000000000..425e2752942 --- /dev/null +++ b/tests/components/overseerr/fixtures/webhook_issue_reported.json @@ -0,0 +1,23 @@ +{ + "notification_type": "ISSUE_REPORTED", + "subject": "New Issue Reported", + "message": "A new video issue has been reported for Interstellar", + "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/gEU2QniE6E77NI6lCU6MxlNBvIx.jpg", + "media": { + "media_type": "movie", + "tmdb_id": "157336", + "tvdb_id": "", + "status": "available", + "status4k": "unknown" + }, + "issue": { + "issue_id": "1", + "issue_type": "video", + "issue_status": "open", + "reported_by_email": "user@example.com", + "reported_by_username": "testuser", + "reported_by_avatar": "/os_logo_square.png", + "reported_by_settings_discord_id": "", + "reported_by_settings_telegram_chat_id": "" + } +} diff --git a/tests/components/overseerr/snapshots/test_diagnostics.ambr b/tests/components/overseerr/snapshots/test_diagnostics.ambr index 164257bb9f1..4ed2abb89d5 100644 --- a/tests/components/overseerr/snapshots/test_diagnostics.ambr +++ b/tests/components/overseerr/snapshots/test_diagnostics.ambr @@ -2,14 +2,25 @@ # name: test_diagnostics_polling_instance dict({ 'coordinator_data': dict({ - 'approved': 11, - 'available': 8, - 'declined': 0, - 'movie': 9, - 'pending': 0, - 'processing': 3, - 'total': 11, - 'tv': 2, + 'issues': dict({ + 'audio': 4, + 'closed': 5, + 'open': 10, + 'others': 2, + 'subtitles': 3, + 'total': 15, + 'video': 6, + }), + 'requests': dict({ + 'approved': 11, + 'available': 8, + 'declined': 0, + 'movie': 9, + 'pending': 0, + 'processing': 3, + 'total': 11, + 'tv': 2, + }), }), 'has_cloudhooks': False, }) @@ -17,14 +28,25 @@ # name: test_diagnostics_webhook_instance dict({ 'coordinator_data': dict({ - 'approved': 11, - 'available': 8, - 'declined': 0, - 'movie': 9, - 'pending': 0, - 'processing': 3, - 'total': 11, - 'tv': 2, + 'issues': dict({ + 'audio': 4, + 'closed': 5, + 'open': 10, + 'others': 2, + 'subtitles': 3, + 'total': 15, + 'video': 6, + }), + 'requests': dict({ + 'approved': 11, + 'available': 8, + 'declined': 0, + 'movie': 9, + 'pending': 0, + 'processing': 3, + 'total': 11, + 'tv': 2, + }), }), 'has_cloudhooks': True, }) diff --git a/tests/components/overseerr/snapshots/test_sensor.ambr b/tests/components/overseerr/snapshots/test_sensor.ambr index 44613d6117c..2ea19617e8e 100644 --- a/tests/components/overseerr/snapshots/test_sensor.ambr +++ b/tests/components/overseerr/snapshots/test_sensor.ambr @@ -1,4 +1,55 @@ # serializer version: 1 +# name: test_all_entities[sensor.overseerr_audio_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_audio_issues', + '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': 'Audio issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'audio_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-audio_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_audio_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Audio issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_audio_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '4', + }) +# --- # name: test_all_entities[sensor.overseerr_available_requests-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -51,6 +102,57 @@ 'state': '8', }) # --- +# name: test_all_entities[sensor.overseerr_closed_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_closed_issues', + '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': 'Closed issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'closed_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-closed_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_closed_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Closed issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_closed_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '5', + }) +# --- # name: test_all_entities[sensor.overseerr_declined_requests-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -155,6 +257,57 @@ 'state': '9', }) # --- +# name: test_all_entities[sensor.overseerr_open_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_open_issues', + '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': 'Open issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'open_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-open_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_open_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Open issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_open_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '10', + }) +# --- # name: test_all_entities[sensor.overseerr_pending_requests-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -259,6 +412,108 @@ 'state': '3', }) # --- +# name: test_all_entities[sensor.overseerr_subtitle_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_subtitle_issues', + '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': 'Subtitle issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'subtitle_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-subtitle_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_subtitle_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Subtitle issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_subtitle_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '3', + }) +# --- +# name: test_all_entities[sensor.overseerr_total_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_total_issues', + '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': 'Total issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'total_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-total_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_total_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Total issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_total_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '15', + }) +# --- # name: test_all_entities[sensor.overseerr_total_requests-entry] EntityRegistryEntrySnapshot({ 'aliases': set({ @@ -363,3 +618,54 @@ 'state': '2', }) # --- +# name: test_all_entities[sensor.overseerr_video_issues-entry] + EntityRegistryEntrySnapshot({ + 'aliases': set({ + }), + 'area_id': None, + 'capabilities': dict({ + 'state_class': , + }), + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'sensor', + 'entity_category': None, + 'entity_id': 'sensor.overseerr_video_issues', + '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': 'Video issues', + 'platform': 'overseerr', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': 0, + 'translation_key': 'video_issues', + 'unique_id': '01JG00V55WEVTJ0CJHM0GAD7PC-video_issues', + 'unit_of_measurement': None, + }) +# --- +# name: test_all_entities[sensor.overseerr_video_issues-state] + StateSnapshot({ + 'attributes': ReadOnlyDict({ + 'friendly_name': 'Overseerr Video issues', + 'state_class': , + }), + 'context': , + 'entity_id': 'sensor.overseerr_video_issues', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': '6', + }) +# --- diff --git a/tests/components/overseerr/test_init.py b/tests/components/overseerr/test_init.py index 66e6a5c134c..670622cfc8c 100644 --- a/tests/components/overseerr/test_init.py +++ b/tests/components/overseerr/test_init.py @@ -72,7 +72,7 @@ async def test_proper_webhook_configuration( """Test the webhook configuration.""" await setup_integration(hass, mock_config_entry) - assert REGISTERED_NOTIFICATIONS == 222 + assert REGISTERED_NOTIFICATIONS == 4062 mock_overseerr_client.test_webhook_notification_config.assert_not_called() mock_overseerr_client.set_webhook_notification_config.assert_not_called() @@ -83,7 +83,6 @@ async def test_proper_webhook_configuration( [ {"return_value.enabled": False}, {"return_value.types": 4}, - {"return_value.types": 4062}, { "return_value.options": WebhookNotificationOptions( webhook_url="http://example.com", json_payload=JSON_PAYLOAD @@ -99,7 +98,6 @@ async def test_proper_webhook_configuration( ids=[ "Disabled", "Smaller scope", - "Bigger scope", "Webhook URL", "JSON Payload", ], @@ -124,7 +122,6 @@ async def test_webhook_configuration_need_update( [ {"return_value.enabled": False}, {"return_value.types": 4}, - {"return_value.types": 4062}, { "return_value.options": WebhookNotificationOptions( webhook_url="http://example.com", json_payload=JSON_PAYLOAD @@ -140,7 +137,6 @@ async def test_webhook_configuration_need_update( ids=[ "Disabled", "Smaller scope", - "Bigger scope", "Webhook URL", "JSON Payload", ], diff --git a/tests/components/overseerr/test_sensor.py b/tests/components/overseerr/test_sensor.py index 7ce605e0413..19c10af30b9 100644 --- a/tests/components/overseerr/test_sensor.py +++ b/tests/components/overseerr/test_sensor.py @@ -39,7 +39,7 @@ async def test_webhook_trigger_update( mock_config_entry: MockConfigEntry, hass_client_no_auth: ClientSessionGenerator, ) -> None: - """Test all entities.""" + """Test webhook triggers coordinator update for request sensors.""" await setup_integration(hass, mock_config_entry) assert hass.states.get("sensor.overseerr_available_requests").state == "8" @@ -57,3 +57,35 @@ async def test_webhook_trigger_update( await hass.async_block_till_done() assert hass.states.get("sensor.overseerr_available_requests").state == "7" + + +async def test_webhook_issue_trigger_update( + hass: HomeAssistant, + mock_overseerr_client: AsyncMock, + mock_config_entry: MockConfigEntry, + hass_client_no_auth: ClientSessionGenerator, +) -> None: + """Test webhook triggers coordinator update for issue sensors.""" + await setup_integration(hass, mock_config_entry) + + assert hass.states.get("sensor.overseerr_total_issues").state == "15" + assert hass.states.get("sensor.overseerr_open_issues").state == "10" + assert hass.states.get("sensor.overseerr_video_issues").state == "6" + + mock_overseerr_client.get_issue_count.return_value.total = 16 + mock_overseerr_client.get_issue_count.return_value.open = 11 + mock_overseerr_client.get_issue_count.return_value.video = 7 + client = await hass_client_no_auth() + + await call_webhook( + hass, + await async_load_json_object_fixture( + hass, "webhook_issue_reported.json", DOMAIN + ), + client, + ) + await hass.async_block_till_done() + + assert hass.states.get("sensor.overseerr_total_issues").state == "16" + assert hass.states.get("sensor.overseerr_open_issues").state == "11" + assert hass.states.get("sensor.overseerr_video_issues").state == "7"