Add calendar entity to Radarr (#79077)
* Add calendar entity to Radarr * address feedback/add tests * black * uno mas * rework to coordinator * uno mas * move release atttribute writing * fix calendar items and attributes
This commit is contained in:
parent
3bcc6194ef
commit
651df6b698
10 changed files with 348 additions and 5 deletions
|
@ -22,6 +22,7 @@ from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||||
|
|
||||||
from .const import DEFAULT_NAME, DOMAIN
|
from .const import DEFAULT_NAME, DOMAIN
|
||||||
from .coordinator import (
|
from .coordinator import (
|
||||||
|
CalendarUpdateCoordinator,
|
||||||
DiskSpaceDataUpdateCoordinator,
|
DiskSpaceDataUpdateCoordinator,
|
||||||
HealthDataUpdateCoordinator,
|
HealthDataUpdateCoordinator,
|
||||||
MoviesDataUpdateCoordinator,
|
MoviesDataUpdateCoordinator,
|
||||||
|
@ -31,7 +32,7 @@ from .coordinator import (
|
||||||
T,
|
T,
|
||||||
)
|
)
|
||||||
|
|
||||||
PLATFORMS = [Platform.BINARY_SENSOR, Platform.SENSOR]
|
PLATFORMS = [Platform.BINARY_SENSOR, Platform.CALENDAR, Platform.SENSOR]
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
@ -46,6 +47,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
session=async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]),
|
session=async_get_clientsession(hass, entry.data[CONF_VERIFY_SSL]),
|
||||||
)
|
)
|
||||||
coordinators: dict[str, RadarrDataUpdateCoordinator[Any]] = {
|
coordinators: dict[str, RadarrDataUpdateCoordinator[Any]] = {
|
||||||
|
"calendar": CalendarUpdateCoordinator(hass, host_configuration, radarr),
|
||||||
"disk_space": DiskSpaceDataUpdateCoordinator(hass, host_configuration, radarr),
|
"disk_space": DiskSpaceDataUpdateCoordinator(hass, host_configuration, radarr),
|
||||||
"health": HealthDataUpdateCoordinator(hass, host_configuration, radarr),
|
"health": HealthDataUpdateCoordinator(hass, host_configuration, radarr),
|
||||||
"movie": MoviesDataUpdateCoordinator(hass, host_configuration, radarr),
|
"movie": MoviesDataUpdateCoordinator(hass, host_configuration, radarr),
|
||||||
|
|
63
homeassistant/components/radarr/calendar.py
Normal file
63
homeassistant/components/radarr/calendar.py
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
"""Support for Radarr calendar items."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from homeassistant.components.calendar import CalendarEntity, CalendarEvent
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant, callback
|
||||||
|
from homeassistant.helpers.entity import EntityDescription
|
||||||
|
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
|
|
||||||
|
from . import RadarrEntity
|
||||||
|
from .const import DOMAIN
|
||||||
|
from .coordinator import CalendarUpdateCoordinator, RadarrEvent
|
||||||
|
|
||||||
|
CALENDAR_TYPE = EntityDescription(
|
||||||
|
key="calendar",
|
||||||
|
name=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
|
||||||
|
) -> None:
|
||||||
|
"""Set up the Radarr calendar entity."""
|
||||||
|
coordinator = hass.data[DOMAIN][entry.entry_id]["calendar"]
|
||||||
|
async_add_entities([RadarrCalendarEntity(coordinator, CALENDAR_TYPE)])
|
||||||
|
|
||||||
|
|
||||||
|
class RadarrCalendarEntity(RadarrEntity, CalendarEntity):
|
||||||
|
"""A Radarr calendar entity."""
|
||||||
|
|
||||||
|
coordinator: CalendarUpdateCoordinator
|
||||||
|
|
||||||
|
@property
|
||||||
|
def event(self) -> CalendarEvent | None:
|
||||||
|
"""Return the next upcoming event."""
|
||||||
|
if not self.coordinator.event:
|
||||||
|
return None
|
||||||
|
return CalendarEvent(
|
||||||
|
summary=self.coordinator.event.summary,
|
||||||
|
start=self.coordinator.event.start,
|
||||||
|
end=self.coordinator.event.end,
|
||||||
|
description=self.coordinator.event.description,
|
||||||
|
)
|
||||||
|
|
||||||
|
# pylint: disable-next=hass-return-type
|
||||||
|
async def async_get_events( # type: ignore[override]
|
||||||
|
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
|
||||||
|
) -> list[RadarrEvent]:
|
||||||
|
"""Get all events in a specific time frame."""
|
||||||
|
return await self.coordinator.async_get_events(start_date, end_date)
|
||||||
|
|
||||||
|
@callback
|
||||||
|
def async_write_ha_state(self) -> None:
|
||||||
|
"""Write the state to the state machine."""
|
||||||
|
if self.coordinator.event:
|
||||||
|
self._attr_extra_state_attributes = {
|
||||||
|
"release_type": self.coordinator.event.release_type
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
self._attr_extra_state_attributes = {}
|
||||||
|
super().async_write_ha_state()
|
|
@ -2,13 +2,23 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from datetime import timedelta
|
import asyncio
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import date, datetime, timedelta
|
||||||
from typing import Generic, TypeVar, cast
|
from typing import Generic, TypeVar, cast
|
||||||
|
|
||||||
from aiopyarr import Health, RadarrMovie, RootFolder, SystemStatus, exceptions
|
from aiopyarr import (
|
||||||
|
Health,
|
||||||
|
RadarrCalendarItem,
|
||||||
|
RadarrMovie,
|
||||||
|
RootFolder,
|
||||||
|
SystemStatus,
|
||||||
|
exceptions,
|
||||||
|
)
|
||||||
from aiopyarr.models.host_configuration import PyArrHostConfiguration
|
from aiopyarr.models.host_configuration import PyArrHostConfiguration
|
||||||
from aiopyarr.radarr_client import RadarrClient
|
from aiopyarr.radarr_client import RadarrClient
|
||||||
|
|
||||||
|
from homeassistant.components.calendar import CalendarEvent
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
|
@ -16,13 +26,26 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
|
||||||
|
|
||||||
from .const import DEFAULT_MAX_RECORDS, DOMAIN, LOGGER
|
from .const import DEFAULT_MAX_RECORDS, DOMAIN, LOGGER
|
||||||
|
|
||||||
T = TypeVar("T", bound=SystemStatus | list[RootFolder] | list[Health] | int)
|
T = TypeVar("T", bound=SystemStatus | list[RootFolder] | list[Health] | int | None)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RadarrEventMixIn:
|
||||||
|
"""Mixin for Radarr calendar event."""
|
||||||
|
|
||||||
|
release_type: str
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RadarrEvent(CalendarEvent, RadarrEventMixIn):
|
||||||
|
"""A class to describe a Radarr calendar event."""
|
||||||
|
|
||||||
|
|
||||||
class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
|
class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
|
||||||
"""Data update coordinator for the Radarr integration."""
|
"""Data update coordinator for the Radarr integration."""
|
||||||
|
|
||||||
config_entry: ConfigEntry
|
config_entry: ConfigEntry
|
||||||
|
update_interval = timedelta(seconds=30)
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -35,7 +58,7 @@ class RadarrDataUpdateCoordinator(DataUpdateCoordinator[T], Generic[T], ABC):
|
||||||
hass=hass,
|
hass=hass,
|
||||||
logger=LOGGER,
|
logger=LOGGER,
|
||||||
name=DOMAIN,
|
name=DOMAIN,
|
||||||
update_interval=timedelta(seconds=30),
|
update_interval=self.update_interval,
|
||||||
)
|
)
|
||||||
self.api_client = api_client
|
self.api_client = api_client
|
||||||
self.host_configuration = host_configuration
|
self.host_configuration = host_configuration
|
||||||
|
@ -101,3 +124,77 @@ class QueueDataUpdateCoordinator(RadarrDataUpdateCoordinator):
|
||||||
return (
|
return (
|
||||||
await self.api_client.async_get_queue(page_size=DEFAULT_MAX_RECORDS)
|
await self.api_client.async_get_queue(page_size=DEFAULT_MAX_RECORDS)
|
||||||
).totalRecords
|
).totalRecords
|
||||||
|
|
||||||
|
|
||||||
|
class CalendarUpdateCoordinator(RadarrDataUpdateCoordinator[None]):
|
||||||
|
"""Calendar update coordinator."""
|
||||||
|
|
||||||
|
update_interval = timedelta(hours=1)
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
hass: HomeAssistant,
|
||||||
|
host_configuration: PyArrHostConfiguration,
|
||||||
|
api_client: RadarrClient,
|
||||||
|
) -> None:
|
||||||
|
"""Initialize."""
|
||||||
|
super().__init__(hass, host_configuration, api_client)
|
||||||
|
self.event: RadarrEvent | None = None
|
||||||
|
self._events: list[RadarrEvent] = []
|
||||||
|
|
||||||
|
async def _fetch_data(self) -> None:
|
||||||
|
"""Fetch the calendar."""
|
||||||
|
self.event = None
|
||||||
|
_date = datetime.today()
|
||||||
|
while self.event is None:
|
||||||
|
await self.async_get_events(_date, _date + timedelta(days=1))
|
||||||
|
for event in self._events:
|
||||||
|
if event.start >= _date.date():
|
||||||
|
self.event = event
|
||||||
|
break
|
||||||
|
# Prevent infinite loop in case there is nothing recent in the calendar
|
||||||
|
if (_date - datetime.today()).days > 45:
|
||||||
|
break
|
||||||
|
_date = _date + timedelta(days=1)
|
||||||
|
|
||||||
|
async def async_get_events(
|
||||||
|
self, start_date: datetime, end_date: datetime
|
||||||
|
) -> list[RadarrEvent]:
|
||||||
|
"""Get cached events and request missing dates."""
|
||||||
|
# remove older events to prevent memory leak
|
||||||
|
self._events = [
|
||||||
|
e
|
||||||
|
for e in self._events
|
||||||
|
if e.start >= datetime.now().date() - timedelta(days=30)
|
||||||
|
]
|
||||||
|
_days = (end_date - start_date).days
|
||||||
|
await asyncio.gather(
|
||||||
|
*(
|
||||||
|
self._async_get_events(d)
|
||||||
|
for d in ((start_date + timedelta(days=x)).date() for x in range(_days))
|
||||||
|
if d not in (event.start for event in self._events)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return self._events
|
||||||
|
|
||||||
|
async def _async_get_events(self, _date: date) -> None:
|
||||||
|
"""Return events from specified date."""
|
||||||
|
self._events.extend(
|
||||||
|
_get_calendar_event(evt)
|
||||||
|
for evt in await self.api_client.async_get_calendar(
|
||||||
|
start_date=_date, end_date=_date + timedelta(days=1)
|
||||||
|
)
|
||||||
|
if evt.title not in (e.summary for e in self._events)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _get_calendar_event(event: RadarrCalendarItem) -> RadarrEvent:
|
||||||
|
"""Return a RadarrEvent from an API event."""
|
||||||
|
_date, _type = event.releaseDateType()
|
||||||
|
return RadarrEvent(
|
||||||
|
summary=event.title,
|
||||||
|
start=_date - timedelta(days=1),
|
||||||
|
end=_date,
|
||||||
|
description=event.overview.replace(":", ";"),
|
||||||
|
release_type=_type,
|
||||||
|
)
|
||||||
|
|
|
@ -102,6 +102,18 @@ def mock_connection(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def mock_calendar(
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
url: str = URL,
|
||||||
|
) -> None:
|
||||||
|
"""Mock radarr connection."""
|
||||||
|
aioclient_mock.get(
|
||||||
|
f"{url}/api/v3/calendar",
|
||||||
|
text=load_fixture("radarr/calendar.json"),
|
||||||
|
headers={"Content-Type": CONTENT_TYPE_JSON},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def mock_connection_error(
|
def mock_connection_error(
|
||||||
aioclient_mock: AiohttpClientMocker,
|
aioclient_mock: AiohttpClientMocker,
|
||||||
url: str = URL,
|
url: str = URL,
|
||||||
|
@ -120,6 +132,7 @@ def mock_connection_invalid_auth(
|
||||||
aioclient_mock.get(f"{url}/api/v3/queue", status=HTTPStatus.UNAUTHORIZED)
|
aioclient_mock.get(f"{url}/api/v3/queue", status=HTTPStatus.UNAUTHORIZED)
|
||||||
aioclient_mock.get(f"{url}/api/v3/rootfolder", status=HTTPStatus.UNAUTHORIZED)
|
aioclient_mock.get(f"{url}/api/v3/rootfolder", status=HTTPStatus.UNAUTHORIZED)
|
||||||
aioclient_mock.get(f"{url}/api/v3/system/status", status=HTTPStatus.UNAUTHORIZED)
|
aioclient_mock.get(f"{url}/api/v3/system/status", status=HTTPStatus.UNAUTHORIZED)
|
||||||
|
aioclient_mock.get(f"{url}/api/v3/calendar", status=HTTPStatus.UNAUTHORIZED)
|
||||||
|
|
||||||
|
|
||||||
def mock_connection_server_error(
|
def mock_connection_server_error(
|
||||||
|
@ -136,6 +149,9 @@ def mock_connection_server_error(
|
||||||
aioclient_mock.get(
|
aioclient_mock.get(
|
||||||
f"{url}/api/v3/system/status", status=HTTPStatus.INTERNAL_SERVER_ERROR
|
f"{url}/api/v3/system/status", status=HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
)
|
)
|
||||||
|
aioclient_mock.get(
|
||||||
|
f"{url}/api/v3/calendar", status=HTTPStatus.INTERNAL_SERVER_ERROR
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
async def setup_integration(
|
async def setup_integration(
|
||||||
|
@ -172,6 +188,8 @@ async def setup_integration(
|
||||||
single_return=single_return,
|
single_return=single_return,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
mock_calendar(aioclient_mock, url)
|
||||||
|
|
||||||
if not skip_entry_setup:
|
if not skip_entry_setup:
|
||||||
await hass.config_entries.async_setup(entry.entry_id)
|
await hass.config_entries.async_setup(entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
111
tests/components/radarr/fixtures/calendar.json
Normal file
111
tests/components/radarr/fixtures/calendar.json
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"title": "test",
|
||||||
|
"originalTitle": "string",
|
||||||
|
"alternateTitles": [],
|
||||||
|
"secondaryYearSourceId": 0,
|
||||||
|
"sortTitle": "string",
|
||||||
|
"sizeOnDisk": 0,
|
||||||
|
"status": "string",
|
||||||
|
"overview": "test2",
|
||||||
|
"physicalRelease": "2021-12-03T00:00:00Z",
|
||||||
|
"digitalRelease": "2020-08-11T00:00:00Z",
|
||||||
|
"images": [
|
||||||
|
{
|
||||||
|
"coverType": "poster",
|
||||||
|
"url": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"website": "string",
|
||||||
|
"year": 0,
|
||||||
|
"hasFile": true,
|
||||||
|
"youTubeTrailerId": "string",
|
||||||
|
"studio": "string",
|
||||||
|
"path": "string",
|
||||||
|
"qualityProfileId": 0,
|
||||||
|
"monitored": true,
|
||||||
|
"minimumAvailability": "string",
|
||||||
|
"isAvailable": true,
|
||||||
|
"folderName": "string",
|
||||||
|
"runtime": 0,
|
||||||
|
"cleanTitle": "string",
|
||||||
|
"imdbId": "string",
|
||||||
|
"tmdbId": 0,
|
||||||
|
"titleSlug": "0",
|
||||||
|
"genres": ["string"],
|
||||||
|
"tags": [],
|
||||||
|
"added": "2020-07-16T13:25:37Z",
|
||||||
|
"ratings": {
|
||||||
|
"imdb": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0.0,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"tmdb": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0.0,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"metacritic": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"rottenTomatoes": {
|
||||||
|
"votes": 0,
|
||||||
|
"value": 0,
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"movieFile": {
|
||||||
|
"movieId": 0,
|
||||||
|
"relativePath": "string",
|
||||||
|
"path": "string",
|
||||||
|
"size": 0,
|
||||||
|
"dateAdded": "2021-06-01T04:08:20Z",
|
||||||
|
"sceneName": "string",
|
||||||
|
"indexerFlags": 0,
|
||||||
|
"quality": {
|
||||||
|
"quality": {
|
||||||
|
"id": 0,
|
||||||
|
"name": "string",
|
||||||
|
"source": "string",
|
||||||
|
"resolution": 0,
|
||||||
|
"modifier": "string"
|
||||||
|
},
|
||||||
|
"revision": {
|
||||||
|
"version": 0,
|
||||||
|
"real": 0,
|
||||||
|
"isRepack": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"mediaInfo": {
|
||||||
|
"audioBitrate": 0,
|
||||||
|
"audioChannels": 0.0,
|
||||||
|
"audioCodec": "string",
|
||||||
|
"audioLanguages": "string",
|
||||||
|
"audioStreamCount": 0,
|
||||||
|
"videoBitDepth": 0,
|
||||||
|
"videoBitrate": 0,
|
||||||
|
"videoCodec": "string",
|
||||||
|
"videoFps": 0.0,
|
||||||
|
"resolution": "string",
|
||||||
|
"runTime": "00:00:00",
|
||||||
|
"scanType": "string",
|
||||||
|
"subtitles": "string"
|
||||||
|
},
|
||||||
|
"originalFilePath": "string",
|
||||||
|
"qualityCutoffNotMet": false,
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": 0,
|
||||||
|
"name": "string"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"releaseGroup": "string",
|
||||||
|
"edition": "string",
|
||||||
|
"id": 0
|
||||||
|
},
|
||||||
|
"id": 0
|
||||||
|
}
|
||||||
|
]
|
|
@ -1,4 +1,6 @@
|
||||||
"""The tests for Radarr binary sensor platform."""
|
"""The tests for Radarr binary sensor platform."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
from homeassistant.components.binary_sensor import BinarySensorDeviceClass
|
||||||
from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON
|
from homeassistant.const import ATTR_DEVICE_CLASS, STATE_ON
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -8,6 +10,7 @@ from . import setup_integration
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
async def test_binary_sensors(
|
async def test_binary_sensors(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
41
tests/components/radarr/test_calendar.py
Normal file
41
tests/components/radarr/test_calendar.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"""The tests for Radarr calendar platform."""
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
|
||||||
|
from homeassistant.components.radarr.const import DOMAIN
|
||||||
|
from homeassistant.const import STATE_OFF, STATE_ON
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
|
|
||||||
|
from . import setup_integration
|
||||||
|
|
||||||
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
async def test_calendar(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
aioclient_mock: AiohttpClientMocker,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Test for successfully setting up the Radarr platform."""
|
||||||
|
freezer.move_to("2021-12-02 00:00:00-08:00")
|
||||||
|
entry = await setup_integration(hass, aioclient_mock)
|
||||||
|
coordinator: DataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]["calendar"]
|
||||||
|
|
||||||
|
state = hass.states.get("calendar.mock_title")
|
||||||
|
assert state.state == STATE_ON
|
||||||
|
assert state.attributes.get("all_day") is True
|
||||||
|
assert state.attributes.get("description") == "test2"
|
||||||
|
assert state.attributes.get("end_time") == "2021-12-03 00:00:00"
|
||||||
|
assert state.attributes.get("message") == "test"
|
||||||
|
assert state.attributes.get("release_type") == "physicalRelease"
|
||||||
|
assert state.attributes.get("start_time") == "2021-12-02 00:00:00"
|
||||||
|
|
||||||
|
freezer.tick(timedelta(hours=16))
|
||||||
|
await coordinator.async_refresh()
|
||||||
|
|
||||||
|
state = hass.states.get("calendar.mock_title")
|
||||||
|
assert state.state == STATE_OFF
|
||||||
|
assert len(state.attributes) == 1
|
||||||
|
assert state.attributes.get("release_type") is None
|
|
@ -2,6 +2,7 @@
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
from aiopyarr import exceptions
|
from aiopyarr import exceptions
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.radarr.const import DEFAULT_NAME, DOMAIN
|
from homeassistant.components.radarr.const import DEFAULT_NAME, DOMAIN
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
|
from homeassistant.config_entries import SOURCE_REAUTH, SOURCE_USER
|
||||||
|
@ -135,6 +136,7 @@ async def test_zero_conf(hass: HomeAssistant) -> None:
|
||||||
assert result["data"] == CONF_DATA
|
assert result["data"] == CONF_DATA
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
async def test_full_reauth_flow_implementation(
|
async def test_full_reauth_flow_implementation(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
"""Test Radarr integration."""
|
"""Test Radarr integration."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.components.radarr.const import DEFAULT_NAME, DOMAIN
|
from homeassistant.components.radarr.const import DEFAULT_NAME, DOMAIN
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
@ -9,6 +11,7 @@ from . import create_entry, mock_connection_invalid_auth, setup_integration
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
async def test_setup(hass: HomeAssistant, aioclient_mock: AiohttpClientMocker) -> None:
|
||||||
"""Test unload."""
|
"""Test unload."""
|
||||||
entry = await setup_integration(hass, aioclient_mock)
|
entry = await setup_integration(hass, aioclient_mock)
|
||||||
|
@ -43,6 +46,7 @@ async def test_async_setup_entry_auth_failed(
|
||||||
assert not hass.data.get(DOMAIN)
|
assert not hass.data.get(DOMAIN)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
async def test_device_info(
|
async def test_device_info(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
|
@ -14,6 +14,7 @@ from . import setup_integration
|
||||||
from tests.test_util.aiohttp import AiohttpClientMocker
|
from tests.test_util.aiohttp import AiohttpClientMocker
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
("windows", "single", "root_folder"),
|
("windows", "single", "root_folder"),
|
||||||
[
|
[
|
||||||
|
@ -65,6 +66,7 @@ async def test_sensors(
|
||||||
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL
|
assert state.attributes.get(ATTR_STATE_CLASS) is SensorStateClass.TOTAL
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.freeze_time("2021-12-03 00:00:00+00:00")
|
||||||
async def test_windows(
|
async def test_windows(
|
||||||
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
hass: HomeAssistant, aioclient_mock: AiohttpClientMocker
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue