1
0
mirror of https://github.com/home-assistant/core.git synced 2025-12-20 02:48:57 +00:00

Add issue sensors to Overseerr integration (#158888)

This commit is contained in:
Anthony Garera
2025-12-16 11:11:28 -05:00
committed by GitHub
parent 7e141533bb
commit 3f57b46756
14 changed files with 515 additions and 39 deletions

View File

@@ -159,7 +159,8 @@ class OverseerrWebhookManager:
"""Handle webhook.""" """Handle webhook."""
data = await request.json() data = await request.json()
LOGGER.debug("Received webhook payload: %s", data) 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() await self.entry.runtime_data.async_refresh()
async_dispatcher_send(hass, EVENT_KEY, data) async_dispatcher_send(hass, EVENT_KEY, data)
return HomeAssistantView.json({"message": "ok"}) return HomeAssistantView.json({"message": "ok"})

View File

@@ -22,6 +22,10 @@ REGISTERED_NOTIFICATIONS = (
| NotificationType.REQUEST_AVAILABLE | NotificationType.REQUEST_AVAILABLE
| NotificationType.REQUEST_PROCESSING_FAILED | NotificationType.REQUEST_PROCESSING_FAILED
| NotificationType.REQUEST_AUTOMATICALLY_APPROVED | NotificationType.REQUEST_AUTOMATICALLY_APPROVED
| NotificationType.ISSUE_REPORTED
| NotificationType.ISSUE_COMMENTED
| NotificationType.ISSUE_RESOLVED
| NotificationType.ISSUE_REOPENED
) )
JSON_PAYLOAD = ( JSON_PAYLOAD = (
'"{\\"notification_type\\":\\"{{notification_type}}\\",\\"subject\\":\\"{{subject}' '"{\\"notification_type\\":\\"{{notification_type}}\\",\\"subject\\":\\"{{subject}'

View File

@@ -6,7 +6,6 @@ from python_overseerr import (
OverseerrAuthenticationError, OverseerrAuthenticationError,
OverseerrClient, OverseerrClient,
OverseerrConnectionError, OverseerrConnectionError,
RequestCount,
) )
from yarl import URL 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 homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER
from .models import OverseerrData
type OverseerrConfigEntry = ConfigEntry[OverseerrCoordinator] type OverseerrConfigEntry = ConfigEntry[OverseerrCoordinator]
class OverseerrCoordinator(DataUpdateCoordinator[RequestCount]): class OverseerrCoordinator(DataUpdateCoordinator[OverseerrData]):
"""Class to manage fetching Overseerr data.""" """Class to manage fetching Overseerr data."""
config_entry: OverseerrConfigEntry 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.url = URL.build(host=host, port=port, scheme="https" if ssl else "http")
self.push = False self.push = False
async def _async_update_data(self) -> RequestCount: async def _async_update_data(self) -> OverseerrData:
"""Fetch data from API endpoint.""" """Fetch data from API endpoint."""
try: 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: except OverseerrAuthenticationError as err:
raise ConfigEntryAuthFailed( raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_domain=DOMAIN,

View File

@@ -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

View File

@@ -3,8 +3,6 @@
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from python_overseerr import RequestCount
from homeassistant.components.sensor import ( from homeassistant.components.sensor import (
SensorEntity, SensorEntity,
SensorEntityDescription, SensorEntityDescription,
@@ -16,6 +14,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import REQUESTS from .const import REQUESTS
from .coordinator import OverseerrConfigEntry, OverseerrCoordinator from .coordinator import OverseerrConfigEntry, OverseerrCoordinator
from .entity import OverseerrEntity from .entity import OverseerrEntity
from .models import OverseerrData
PARALLEL_UPDATES = 0 PARALLEL_UPDATES = 0
@@ -24,7 +23,7 @@ PARALLEL_UPDATES = 0
class OverseerrSensorEntityDescription(SensorEntityDescription): class OverseerrSensorEntityDescription(SensorEntityDescription):
"""Describes Overseerr config sensor entity.""" """Describes Overseerr config sensor entity."""
value_fn: Callable[[RequestCount], int] value_fn: Callable[[OverseerrData], int]
SENSORS: tuple[OverseerrSensorEntityDescription, ...] = ( SENSORS: tuple[OverseerrSensorEntityDescription, ...] = (
@@ -32,43 +31,73 @@ SENSORS: tuple[OverseerrSensorEntityDescription, ...] = (
key="total_requests", key="total_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.total, value_fn=lambda data: data.requests.total,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="movie_requests", key="movie_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.movie, value_fn=lambda data: data.requests.movie,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="tv_requests", key="tv_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.tv, value_fn=lambda data: data.requests.tv,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="pending_requests", key="pending_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.pending, value_fn=lambda data: data.requests.pending,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="declined_requests", key="declined_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.declined, value_fn=lambda data: data.requests.declined,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="processing_requests", key="processing_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, state_class=SensorStateClass.TOTAL,
value_fn=lambda count: count.processing, value_fn=lambda data: data.requests.processing,
), ),
OverseerrSensorEntityDescription( OverseerrSensorEntityDescription(
key="available_requests", key="available_requests",
native_unit_of_measurement=REQUESTS, native_unit_of_measurement=REQUESTS,
state_class=SensorStateClass.TOTAL, 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,
), ),
) )

View File

@@ -50,26 +50,62 @@
} }
}, },
"sensor": { "sensor": {
"audio_issues": {
"name": "Audio issues",
"state": {
"measurement": "issues"
}
},
"available_requests": { "available_requests": {
"name": "Available requests" "name": "Available requests"
}, },
"closed_issues": {
"name": "Closed issues",
"state": {
"measurement": "issues"
}
},
"declined_requests": { "declined_requests": {
"name": "Declined requests" "name": "Declined requests"
}, },
"movie_requests": { "movie_requests": {
"name": "Movie requests" "name": "Movie requests"
}, },
"open_issues": {
"name": "Open issues",
"state": {
"measurement": "issues"
}
},
"pending_requests": { "pending_requests": {
"name": "Pending requests" "name": "Pending requests"
}, },
"processing_requests": { "processing_requests": {
"name": "Processing requests" "name": "Processing requests"
}, },
"subtitle_issues": {
"name": "Subtitle issues",
"state": {
"measurement": "issues"
}
},
"total_issues": {
"name": "Total issues",
"state": {
"measurement": "issues"
}
},
"total_requests": { "total_requests": {
"name": "Total requests" "name": "Total requests"
}, },
"tv_requests": { "tv_requests": {
"name": "TV requests" "name": "TV requests"
},
"video_issues": {
"name": "Video issues",
"state": {
"measurement": "issues"
}
} }
} }
}, },

View File

@@ -4,7 +4,7 @@ from collections.abc import Generator
from unittest.mock import AsyncMock, patch from unittest.mock import AsyncMock, patch
import pytest 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 python_overseerr.models import TVDetails, WebhookNotificationConfig
from homeassistant.components.overseerr import CONF_CLOUDHOOK_URL 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( client.get_request_count.return_value = RequestCount.from_json(
load_fixture("request_count.json", DOMAIN) 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 = ( client.get_webhook_notification_config.return_value = (
WebhookNotificationConfig.from_json( WebhookNotificationConfig.from_json(
load_fixture("webhook_config.json", DOMAIN) load_fixture("webhook_config.json", DOMAIN)

View File

@@ -0,0 +1,9 @@
{
"total": 15,
"video": 6,
"audio": 4,
"subtitles": 3,
"others": 2,
"open": 10,
"closed": 5
}

View File

@@ -1,6 +1,6 @@
{ {
"enabled": true, "enabled": true,
"types": 222, "types": 4062,
"options": { "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}}\"}}", "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" "webhookUrl": "http://10.10.10.10:8123/api/webhook/test-webhook-id"

View File

@@ -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": ""
}
}

View File

@@ -2,6 +2,16 @@
# name: test_diagnostics_polling_instance # name: test_diagnostics_polling_instance
dict({ dict({
'coordinator_data': dict({ 'coordinator_data': dict({
'issues': dict({
'audio': 4,
'closed': 5,
'open': 10,
'others': 2,
'subtitles': 3,
'total': 15,
'video': 6,
}),
'requests': dict({
'approved': 11, 'approved': 11,
'available': 8, 'available': 8,
'declined': 0, 'declined': 0,
@@ -11,12 +21,23 @@
'total': 11, 'total': 11,
'tv': 2, 'tv': 2,
}), }),
}),
'has_cloudhooks': False, 'has_cloudhooks': False,
}) })
# --- # ---
# name: test_diagnostics_webhook_instance # name: test_diagnostics_webhook_instance
dict({ dict({
'coordinator_data': dict({ 'coordinator_data': dict({
'issues': dict({
'audio': 4,
'closed': 5,
'open': 10,
'others': 2,
'subtitles': 3,
'total': 15,
'video': 6,
}),
'requests': dict({
'approved': 11, 'approved': 11,
'available': 8, 'available': 8,
'declined': 0, 'declined': 0,
@@ -26,6 +47,7 @@
'total': 11, 'total': 11,
'tv': 2, 'tv': 2,
}), }),
}),
'has_cloudhooks': True, 'has_cloudhooks': True,
}) })
# --- # ---

View File

@@ -1,4 +1,55 @@
# serializer version: 1 # serializer version: 1
# name: test_all_entities[sensor.overseerr_audio_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_audio_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_audio_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '4',
})
# ---
# name: test_all_entities[sensor.overseerr_available_requests-entry] # name: test_all_entities[sensor.overseerr_available_requests-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@@ -51,6 +102,57 @@
'state': '8', 'state': '8',
}) })
# --- # ---
# name: test_all_entities[sensor.overseerr_closed_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_closed_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_closed_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '5',
})
# ---
# name: test_all_entities[sensor.overseerr_declined_requests-entry] # name: test_all_entities[sensor.overseerr_declined_requests-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@@ -155,6 +257,57 @@
'state': '9', 'state': '9',
}) })
# --- # ---
# name: test_all_entities[sensor.overseerr_open_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_open_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_open_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '10',
})
# ---
# name: test_all_entities[sensor.overseerr_pending_requests-entry] # name: test_all_entities[sensor.overseerr_pending_requests-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@@ -259,6 +412,108 @@
'state': '3', 'state': '3',
}) })
# --- # ---
# name: test_all_entities[sensor.overseerr_subtitle_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_subtitle_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_subtitle_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '3',
})
# ---
# name: test_all_entities[sensor.overseerr_total_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_total_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_total_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '15',
})
# ---
# name: test_all_entities[sensor.overseerr_total_requests-entry] # name: test_all_entities[sensor.overseerr_total_requests-entry]
EntityRegistryEntrySnapshot({ EntityRegistryEntrySnapshot({
'aliases': set({ 'aliases': set({
@@ -363,3 +618,54 @@
'state': '2', 'state': '2',
}) })
# --- # ---
# name: test_all_entities[sensor.overseerr_video_issues-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.TOTAL: 'total'>,
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.overseerr_video_issues',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'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': <SensorStateClass.TOTAL: 'total'>,
}),
'context': <ANY>,
'entity_id': 'sensor.overseerr_video_issues',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '6',
})
# ---

View File

@@ -72,7 +72,7 @@ async def test_proper_webhook_configuration(
"""Test the webhook configuration.""" """Test the webhook configuration."""
await setup_integration(hass, mock_config_entry) 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.test_webhook_notification_config.assert_not_called()
mock_overseerr_client.set_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.enabled": False},
{"return_value.types": 4}, {"return_value.types": 4},
{"return_value.types": 4062},
{ {
"return_value.options": WebhookNotificationOptions( "return_value.options": WebhookNotificationOptions(
webhook_url="http://example.com", json_payload=JSON_PAYLOAD webhook_url="http://example.com", json_payload=JSON_PAYLOAD
@@ -99,7 +98,6 @@ async def test_proper_webhook_configuration(
ids=[ ids=[
"Disabled", "Disabled",
"Smaller scope", "Smaller scope",
"Bigger scope",
"Webhook URL", "Webhook URL",
"JSON Payload", "JSON Payload",
], ],
@@ -124,7 +122,6 @@ async def test_webhook_configuration_need_update(
[ [
{"return_value.enabled": False}, {"return_value.enabled": False},
{"return_value.types": 4}, {"return_value.types": 4},
{"return_value.types": 4062},
{ {
"return_value.options": WebhookNotificationOptions( "return_value.options": WebhookNotificationOptions(
webhook_url="http://example.com", json_payload=JSON_PAYLOAD webhook_url="http://example.com", json_payload=JSON_PAYLOAD
@@ -140,7 +137,6 @@ async def test_webhook_configuration_need_update(
ids=[ ids=[
"Disabled", "Disabled",
"Smaller scope", "Smaller scope",
"Bigger scope",
"Webhook URL", "Webhook URL",
"JSON Payload", "JSON Payload",
], ],

View File

@@ -39,7 +39,7 @@ async def test_webhook_trigger_update(
mock_config_entry: MockConfigEntry, mock_config_entry: MockConfigEntry,
hass_client_no_auth: ClientSessionGenerator, hass_client_no_auth: ClientSessionGenerator,
) -> None: ) -> None:
"""Test all entities.""" """Test webhook triggers coordinator update for request sensors."""
await setup_integration(hass, mock_config_entry) await setup_integration(hass, mock_config_entry)
assert hass.states.get("sensor.overseerr_available_requests").state == "8" 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() await hass.async_block_till_done()
assert hass.states.get("sensor.overseerr_available_requests").state == "7" 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"