mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 03:07:37 +00:00
Convert ring integration to the async ring-doorbell api (#124365)
* Bump ring-doorbell to 0.9.0 * Convert ring integration to async ring-doorbell api * Use mock auth fixture class to get token_updater * Fix typo in fixture name
This commit is contained in:
parent
7ae8f4c9d0
commit
e26d363b5e
@ -3,7 +3,6 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from functools import partial
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any, cast
|
from typing import Any, cast
|
||||||
|
|
||||||
@ -13,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
|
|||||||
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__
|
from homeassistant.const import APPLICATION_NAME, CONF_TOKEN, __version__
|
||||||
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
from homeassistant.core import HomeAssistant, ServiceCall, callback
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||||
|
|
||||||
from .const import DOMAIN, PLATFORMS
|
from .const import DOMAIN, PLATFORMS
|
||||||
@ -35,17 +35,17 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
"""Set up a config entry."""
|
"""Set up a config entry."""
|
||||||
|
|
||||||
def token_updater(token: dict[str, Any]) -> None:
|
def token_updater(token: dict[str, Any]) -> None:
|
||||||
"""Handle from sync context when token is updated."""
|
"""Handle from async context when token is updated."""
|
||||||
hass.loop.call_soon_threadsafe(
|
hass.config_entries.async_update_entry(
|
||||||
partial(
|
|
||||||
hass.config_entries.async_update_entry,
|
|
||||||
entry,
|
entry,
|
||||||
data={**entry.data, CONF_TOKEN: token},
|
data={**entry.data, CONF_TOKEN: token},
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
auth = Auth(
|
auth = Auth(
|
||||||
f"{APPLICATION_NAME}/{__version__}", entry.data[CONF_TOKEN], token_updater
|
f"{APPLICATION_NAME}/{__version__}",
|
||||||
|
entry.data[CONF_TOKEN],
|
||||||
|
token_updater,
|
||||||
|
http_client_session=async_get_clientsession(hass),
|
||||||
)
|
)
|
||||||
ring = Ring(auth)
|
ring = Ring(auth)
|
||||||
|
|
||||||
|
@ -53,6 +53,6 @@ class RingDoorButton(RingEntity[RingOther], ButtonEntity):
|
|||||||
self._attr_unique_id = f"{device.id}-{description.key}"
|
self._attr_unique_id = f"{device.id}-{description.key}"
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def press(self) -> None:
|
async def async_press(self) -> None:
|
||||||
"""Open the door."""
|
"""Open the door."""
|
||||||
self._device.open_door()
|
await self._device.async_open_door()
|
||||||
|
@ -159,36 +159,36 @@ class RingCam(RingEntity[RingDoorBell], Camera):
|
|||||||
if self._last_video_id != self._last_event["id"]:
|
if self._last_video_id != self._last_event["id"]:
|
||||||
self._image = None
|
self._image = None
|
||||||
|
|
||||||
self._video_url = await self.hass.async_add_executor_job(self._get_video)
|
self._video_url = await self._async_get_video()
|
||||||
|
|
||||||
self._last_video_id = self._last_event["id"]
|
self._last_video_id = self._last_event["id"]
|
||||||
self._expires_at = FORCE_REFRESH_INTERVAL + utcnow
|
self._expires_at = FORCE_REFRESH_INTERVAL + utcnow
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def _get_video(self) -> str | None:
|
async def _async_get_video(self) -> str | None:
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
# _last_event is set before calling update so will never be None
|
# _last_event is set before calling update so will never be None
|
||||||
assert self._last_event
|
assert self._last_event
|
||||||
event_id = self._last_event.get("id")
|
event_id = self._last_event.get("id")
|
||||||
assert event_id and isinstance(event_id, int)
|
assert event_id and isinstance(event_id, int)
|
||||||
return self._device.recording_url(event_id)
|
return await self._device.async_recording_url(event_id)
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def _set_motion_detection_enabled(self, new_state: bool) -> None:
|
async def _async_set_motion_detection_enabled(self, new_state: bool) -> None:
|
||||||
if not self._device.has_capability(MOTION_DETECTION_CAPABILITY):
|
if not self._device.has_capability(MOTION_DETECTION_CAPABILITY):
|
||||||
_LOGGER.error(
|
_LOGGER.error(
|
||||||
"Entity %s does not have motion detection capability", self.entity_id
|
"Entity %s does not have motion detection capability", self.entity_id
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
self._device.motion_detection = new_state
|
await self._device.async_set_motion_detection(new_state)
|
||||||
self._attr_motion_detection_enabled = new_state
|
self._attr_motion_detection_enabled = new_state
|
||||||
self.schedule_update_ha_state(False)
|
self.async_schedule_update_ha_state(False)
|
||||||
|
|
||||||
def enable_motion_detection(self) -> None:
|
async def async_enable_motion_detection(self) -> None:
|
||||||
"""Enable motion detection in the camera."""
|
"""Enable motion detection in the camera."""
|
||||||
self._set_motion_detection_enabled(True)
|
await self._async_set_motion_detection_enabled(True)
|
||||||
|
|
||||||
def disable_motion_detection(self) -> None:
|
async def async_disable_motion_detection(self) -> None:
|
||||||
"""Disable motion detection in camera."""
|
"""Disable motion detection in camera."""
|
||||||
self._set_motion_detection_enabled(False)
|
await self._async_set_motion_detection_enabled(False)
|
||||||
|
@ -34,8 +34,7 @@ async def validate_input(hass: HomeAssistant, data: dict[str, str]) -> dict[str,
|
|||||||
auth = Auth(f"{APPLICATION_NAME}/{ha_version}")
|
auth = Auth(f"{APPLICATION_NAME}/{ha_version}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
token = await hass.async_add_executor_job(
|
token = await auth.async_fetch_token(
|
||||||
auth.fetch_token,
|
|
||||||
data[CONF_USERNAME],
|
data[CONF_USERNAME],
|
||||||
data[CONF_PASSWORD],
|
data[CONF_PASSWORD],
|
||||||
data.get(CONF_2FA),
|
data.get(CONF_2FA),
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
"""Data coordinators for the ring integration."""
|
"""Data coordinators for the ring integration."""
|
||||||
|
|
||||||
from asyncio import TaskGroup
|
from asyncio import TaskGroup
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Coroutine
|
||||||
import logging
|
import logging
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from ring_doorbell import AuthenticationError, Ring, RingDevices, RingError, RingTimeout
|
from ring_doorbell import AuthenticationError, Ring, RingDevices, RingError, RingTimeout
|
||||||
|
|
||||||
@ -16,10 +17,13 @@ _LOGGER = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
async def _call_api[*_Ts, _R](
|
async def _call_api[*_Ts, _R](
|
||||||
hass: HomeAssistant, target: Callable[[*_Ts], _R], *args: *_Ts, msg_suffix: str = ""
|
hass: HomeAssistant,
|
||||||
|
target: Callable[[*_Ts], Coroutine[Any, Any, _R]],
|
||||||
|
*args: *_Ts,
|
||||||
|
msg_suffix: str = "",
|
||||||
) -> _R:
|
) -> _R:
|
||||||
try:
|
try:
|
||||||
return await hass.async_add_executor_job(target, *args)
|
return await target(*args)
|
||||||
except AuthenticationError as err:
|
except AuthenticationError as err:
|
||||||
# Raising ConfigEntryAuthFailed will cancel future updates
|
# Raising ConfigEntryAuthFailed will cancel future updates
|
||||||
# and start a config flow with SOURCE_REAUTH (async_step_reauth)
|
# and start a config flow with SOURCE_REAUTH (async_step_reauth)
|
||||||
@ -52,7 +56,9 @@ class RingDataCoordinator(DataUpdateCoordinator[RingDevices]):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> RingDevices:
|
async def _async_update_data(self) -> RingDevices:
|
||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
update_method: str = "update_data" if self.first_call else "update_devices"
|
update_method: str = (
|
||||||
|
"async_update_data" if self.first_call else "async_update_devices"
|
||||||
|
)
|
||||||
await _call_api(self.hass, getattr(self.ring_api, update_method))
|
await _call_api(self.hass, getattr(self.ring_api, update_method))
|
||||||
self.first_call = False
|
self.first_call = False
|
||||||
devices: RingDevices = self.ring_api.devices()
|
devices: RingDevices = self.ring_api.devices()
|
||||||
@ -67,7 +73,7 @@ class RingDataCoordinator(DataUpdateCoordinator[RingDevices]):
|
|||||||
tg.create_task(
|
tg.create_task(
|
||||||
_call_api(
|
_call_api(
|
||||||
self.hass,
|
self.hass,
|
||||||
lambda device: device.history(limit=10),
|
lambda device: device.async_history(limit=10),
|
||||||
device,
|
device,
|
||||||
msg_suffix=f" for device {device.name}", # device_id is the mac
|
msg_suffix=f" for device {device.name}", # device_id is the mac
|
||||||
)
|
)
|
||||||
@ -75,7 +81,7 @@ class RingDataCoordinator(DataUpdateCoordinator[RingDevices]):
|
|||||||
tg.create_task(
|
tg.create_task(
|
||||||
_call_api(
|
_call_api(
|
||||||
self.hass,
|
self.hass,
|
||||||
device.update_health_data,
|
device.async_update_health_data,
|
||||||
msg_suffix=f" for device {device.name}",
|
msg_suffix=f" for device {device.name}",
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -100,4 +106,4 @@ class RingNotificationsCoordinator(DataUpdateCoordinator[None]):
|
|||||||
|
|
||||||
async def _async_update_data(self) -> None:
|
async def _async_update_data(self) -> None:
|
||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
await _call_api(self.hass, self.ring_api.update_dings)
|
await _call_api(self.hass, self.ring_api.async_update_dings)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""Base class for Ring entity."""
|
"""Base class for Ring entity."""
|
||||||
|
|
||||||
from collections.abc import Callable
|
from collections.abc import Callable, Coroutine
|
||||||
from typing import Any, Concatenate, Generic, cast
|
from typing import Any, Concatenate, Generic, cast
|
||||||
|
|
||||||
from ring_doorbell import (
|
from ring_doorbell import (
|
||||||
@ -29,25 +29,23 @@ _RingCoordinatorT = TypeVar(
|
|||||||
|
|
||||||
|
|
||||||
def exception_wrap[_RingBaseEntityT: RingBaseEntity[Any, Any], **_P, _R](
|
def exception_wrap[_RingBaseEntityT: RingBaseEntity[Any, Any], **_P, _R](
|
||||||
func: Callable[Concatenate[_RingBaseEntityT, _P], _R],
|
async_func: Callable[Concatenate[_RingBaseEntityT, _P], Coroutine[Any, Any, _R]],
|
||||||
) -> Callable[Concatenate[_RingBaseEntityT, _P], _R]:
|
) -> Callable[Concatenate[_RingBaseEntityT, _P], Coroutine[Any, Any, _R]]:
|
||||||
"""Define a wrapper to catch exceptions and raise HomeAssistant errors."""
|
"""Define a wrapper to catch exceptions and raise HomeAssistant errors."""
|
||||||
|
|
||||||
def _wrap(self: _RingBaseEntityT, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
async def _wrap(self: _RingBaseEntityT, *args: _P.args, **kwargs: _P.kwargs) -> _R:
|
||||||
try:
|
try:
|
||||||
return func(self, *args, **kwargs)
|
return await async_func(self, *args, **kwargs)
|
||||||
except AuthenticationError as err:
|
except AuthenticationError as err:
|
||||||
self.hass.loop.call_soon_threadsafe(
|
self.coordinator.config_entry.async_start_reauth(self.hass)
|
||||||
self.coordinator.config_entry.async_start_reauth, self.hass
|
|
||||||
)
|
|
||||||
raise HomeAssistantError(err) from err
|
raise HomeAssistantError(err) from err
|
||||||
except RingTimeout as err:
|
except RingTimeout as err:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Timeout communicating with API {func}: {err}"
|
f"Timeout communicating with API {async_func}: {err}"
|
||||||
) from err
|
) from err
|
||||||
except RingError as err:
|
except RingError as err:
|
||||||
raise HomeAssistantError(
|
raise HomeAssistantError(
|
||||||
f"Error communicating with API{func}: {err}"
|
f"Error communicating with API{async_func}: {err}"
|
||||||
) from err
|
) from err
|
||||||
|
|
||||||
return _wrap
|
return _wrap
|
||||||
|
@ -80,18 +80,18 @@ class RingLight(RingEntity[RingStickUpCam], LightEntity):
|
|||||||
super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def _set_light(self, new_state: OnOffState) -> None:
|
async def _async_set_light(self, new_state: OnOffState) -> None:
|
||||||
"""Update light state, and causes Home Assistant to correctly update."""
|
"""Update light state, and causes Home Assistant to correctly update."""
|
||||||
self._device.lights = new_state
|
await self._device.async_set_lights(new_state)
|
||||||
|
|
||||||
self._attr_is_on = new_state == OnOffState.ON
|
self._attr_is_on = new_state == OnOffState.ON
|
||||||
self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY
|
self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY
|
||||||
self.schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the light on for 30 seconds."""
|
"""Turn the light on for 30 seconds."""
|
||||||
self._set_light(OnOffState.ON)
|
await self._async_set_light(OnOffState.ON)
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the light off."""
|
"""Turn the light off."""
|
||||||
self._set_light(OnOffState.OFF)
|
await self._async_set_light(OnOffState.OFF)
|
||||||
|
@ -14,5 +14,5 @@
|
|||||||
"iot_class": "cloud_polling",
|
"iot_class": "cloud_polling",
|
||||||
"loggers": ["ring_doorbell"],
|
"loggers": ["ring_doorbell"],
|
||||||
"quality_scale": "silver",
|
"quality_scale": "silver",
|
||||||
"requirements": ["ring-doorbell[listen]==0.8.12"]
|
"requirements": ["ring-doorbell[listen]==0.9.0"]
|
||||||
}
|
}
|
||||||
|
@ -47,8 +47,8 @@ class RingChimeSiren(RingEntity[RingChime], SirenEntity):
|
|||||||
self._attr_unique_id = f"{self._device.id}-siren"
|
self._attr_unique_id = f"{self._device.id}-siren"
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Play the test sound on a Ring Chime device."""
|
"""Play the test sound on a Ring Chime device."""
|
||||||
tone = kwargs.get(ATTR_TONE) or RingEventKind.DING.value
|
tone = kwargs.get(ATTR_TONE) or RingEventKind.DING.value
|
||||||
|
|
||||||
self._device.test_sound(kind=tone)
|
await self._device.async_test_sound(kind=tone)
|
||||||
|
@ -81,18 +81,18 @@ class SirenSwitch(BaseRingSwitch):
|
|||||||
super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
@exception_wrap
|
@exception_wrap
|
||||||
def _set_switch(self, new_state: int) -> None:
|
async def _async_set_switch(self, new_state: int) -> None:
|
||||||
"""Update switch state, and causes Home Assistant to correctly update."""
|
"""Update switch state, and causes Home Assistant to correctly update."""
|
||||||
self._device.siren = new_state
|
await self._device.async_set_siren(new_state)
|
||||||
|
|
||||||
self._attr_is_on = new_state > 0
|
self._attr_is_on = new_state > 0
|
||||||
self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY
|
self._no_updates_until = dt_util.utcnow() + SKIP_UPDATES_DELAY
|
||||||
self.schedule_update_ha_state()
|
self.async_schedule_update_ha_state()
|
||||||
|
|
||||||
def turn_on(self, **kwargs: Any) -> None:
|
async def async_turn_on(self, **kwargs: Any) -> None:
|
||||||
"""Turn the siren on for 30 seconds."""
|
"""Turn the siren on for 30 seconds."""
|
||||||
self._set_switch(1)
|
await self._async_set_switch(1)
|
||||||
|
|
||||||
def turn_off(self, **kwargs: Any) -> None:
|
async def async_turn_off(self, **kwargs: Any) -> None:
|
||||||
"""Turn the siren off."""
|
"""Turn the siren off."""
|
||||||
self._set_switch(0)
|
await self._async_set_switch(0)
|
||||||
|
@ -2507,7 +2507,7 @@ rfk101py==0.0.1
|
|||||||
rflink==0.0.66
|
rflink==0.0.66
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring-doorbell[listen]==0.8.12
|
ring-doorbell[listen]==0.9.0
|
||||||
|
|
||||||
# homeassistant.components.fleetgo
|
# homeassistant.components.fleetgo
|
||||||
ritassist==0.9.2
|
ritassist==0.9.2
|
||||||
|
@ -1986,7 +1986,7 @@ reolink-aio==0.9.7
|
|||||||
rflink==0.0.66
|
rflink==0.0.66
|
||||||
|
|
||||||
# homeassistant.components.ring
|
# homeassistant.components.ring
|
||||||
ring-doorbell[listen]==0.8.12
|
ring-doorbell[listen]==0.9.0
|
||||||
|
|
||||||
# homeassistant.components.roku
|
# homeassistant.components.roku
|
||||||
rokuecp==0.19.3
|
rokuecp==0.19.3
|
||||||
|
@ -26,13 +26,23 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
|||||||
yield mock_setup_entry
|
yield mock_setup_entry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_ring_init_auth_class():
|
||||||
|
"""Mock ring_doorbell.Auth in init and return the mock class."""
|
||||||
|
with patch("homeassistant.components.ring.Auth", autospec=True) as mock_ring_auth:
|
||||||
|
mock_ring_auth.return_value.async_fetch_token.return_value = {
|
||||||
|
"access_token": "mock-token"
|
||||||
|
}
|
||||||
|
yield mock_ring_auth
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def mock_ring_auth():
|
def mock_ring_auth():
|
||||||
"""Mock ring_doorbell.Auth."""
|
"""Mock ring_doorbell.Auth."""
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.ring.config_flow.Auth", autospec=True
|
"homeassistant.components.ring.config_flow.Auth", autospec=True
|
||||||
) as mock_ring_auth:
|
) as mock_ring_auth:
|
||||||
mock_ring_auth.return_value.fetch_token.return_value = {
|
mock_ring_auth.return_value.async_fetch_token.return_value = {
|
||||||
"access_token": "mock-token"
|
"access_token": "mock-token"
|
||||||
}
|
}
|
||||||
yield mock_ring_auth.return_value
|
yield mock_ring_auth.return_value
|
||||||
|
@ -10,7 +10,7 @@ Mocks the api calls on the devices such as history() and health().
|
|||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import AsyncMock, MagicMock
|
||||||
|
|
||||||
from ring_doorbell import (
|
from ring_doorbell import (
|
||||||
RingCapability,
|
RingCapability,
|
||||||
@ -132,18 +132,18 @@ def _mocked_ring_device(device_dict, device_family, device_class, capabilities):
|
|||||||
|
|
||||||
# Configure common methods
|
# Configure common methods
|
||||||
mock_device.has_capability.side_effect = has_capability
|
mock_device.has_capability.side_effect = has_capability
|
||||||
mock_device.update_health_data.side_effect = lambda: update_health_data(
|
mock_device.async_update_health_data.side_effect = lambda: update_health_data(
|
||||||
DOORBOT_HEALTH if device_family != "chimes" else CHIME_HEALTH
|
DOORBOT_HEALTH if device_family != "chimes" else CHIME_HEALTH
|
||||||
)
|
)
|
||||||
# Configure methods based on capability
|
# Configure methods based on capability
|
||||||
if has_capability(RingCapability.HISTORY):
|
if has_capability(RingCapability.HISTORY):
|
||||||
mock_device.configure_mock(last_history=[])
|
mock_device.configure_mock(last_history=[])
|
||||||
mock_device.history.side_effect = lambda *_, **__: update_history_data(
|
mock_device.async_history.side_effect = lambda *_, **__: update_history_data(
|
||||||
DOORBOT_HISTORY if device_family != "other" else INTERCOM_HISTORY
|
DOORBOT_HISTORY if device_family != "other" else INTERCOM_HISTORY
|
||||||
)
|
)
|
||||||
|
|
||||||
if has_capability(RingCapability.VIDEO):
|
if has_capability(RingCapability.VIDEO):
|
||||||
mock_device.recording_url = MagicMock(return_value="http://dummy.url")
|
mock_device.async_recording_url = AsyncMock(return_value="http://dummy.url")
|
||||||
|
|
||||||
if has_capability(RingCapability.MOTION_DETECTION):
|
if has_capability(RingCapability.MOTION_DETECTION):
|
||||||
mock_device.configure_mock(
|
mock_device.configure_mock(
|
||||||
|
@ -28,11 +28,11 @@ async def test_button_opens_door(
|
|||||||
await setup_platform(hass, Platform.BUTTON)
|
await setup_platform(hass, Platform.BUTTON)
|
||||||
|
|
||||||
mock_intercom = mock_ring_devices.get_device(185036587)
|
mock_intercom = mock_ring_devices.get_device(185036587)
|
||||||
mock_intercom.open_door.assert_not_called()
|
mock_intercom.async_open_door.assert_not_called()
|
||||||
|
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"button", "press", {"entity_id": "button.ingress_open_door"}, blocking=True
|
"button", "press", {"entity_id": "button.ingress_open_door"}, blocking=True
|
||||||
)
|
)
|
||||||
|
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
mock_intercom.open_door.assert_called_once()
|
mock_intercom.async_open_door.assert_called_once()
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
"""The tests for the Ring switch platform."""
|
"""The tests for the Ring switch platform."""
|
||||||
|
|
||||||
from unittest.mock import AsyncMock, MagicMock, PropertyMock, patch
|
from unittest.mock import AsyncMock, patch
|
||||||
|
|
||||||
from aiohttp.test_utils import make_mocked_request
|
from aiohttp.test_utils import make_mocked_request
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
@ -180,8 +180,7 @@ async def test_motion_detection_errors_when_turned_on(
|
|||||||
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
front_camera_mock = mock_ring_devices.get_device(765432)
|
front_camera_mock = mock_ring_devices.get_device(765432)
|
||||||
p = PropertyMock(side_effect=exception_type)
|
front_camera_mock.async_set_motion_detection.side_effect = exception_type
|
||||||
type(front_camera_mock).motion_detection = p
|
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -191,7 +190,7 @@ async def test_motion_detection_errors_when_turned_on(
|
|||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
p.assert_called_once()
|
front_camera_mock.async_set_motion_detection.assert_called_once()
|
||||||
assert (
|
assert (
|
||||||
any(
|
any(
|
||||||
flow
|
flow
|
||||||
@ -212,7 +211,7 @@ async def test_camera_handle_mjpeg_stream(
|
|||||||
await setup_platform(hass, Platform.CAMERA)
|
await setup_platform(hass, Platform.CAMERA)
|
||||||
|
|
||||||
front_camera_mock = mock_ring_devices.get_device(765432)
|
front_camera_mock = mock_ring_devices.get_device(765432)
|
||||||
front_camera_mock.recording_url.return_value = None
|
front_camera_mock.async_recording_url.return_value = None
|
||||||
|
|
||||||
state = hass.states.get("camera.front")
|
state = hass.states.get("camera.front")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
@ -220,8 +219,8 @@ async def test_camera_handle_mjpeg_stream(
|
|||||||
mock_request = make_mocked_request("GET", "/", headers={"token": "x"})
|
mock_request = make_mocked_request("GET", "/", headers={"token": "x"})
|
||||||
|
|
||||||
# history not updated yet
|
# history not updated yet
|
||||||
front_camera_mock.history.assert_not_called()
|
front_camera_mock.async_history.assert_not_called()
|
||||||
front_camera_mock.recording_url.assert_not_called()
|
front_camera_mock.async_recording_url.assert_not_called()
|
||||||
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
||||||
assert stream is None
|
assert stream is None
|
||||||
|
|
||||||
@ -229,30 +228,30 @@ async def test_camera_handle_mjpeg_stream(
|
|||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
front_camera_mock.history.assert_called_once()
|
front_camera_mock.async_history.assert_called_once()
|
||||||
front_camera_mock.recording_url.assert_called_once()
|
front_camera_mock.async_recording_url.assert_called_once()
|
||||||
|
|
||||||
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
||||||
assert stream is None
|
assert stream is None
|
||||||
|
|
||||||
# Stop the history updating so we can update the values manually
|
# Stop the history updating so we can update the values manually
|
||||||
front_camera_mock.history = MagicMock()
|
front_camera_mock.async_history = AsyncMock()
|
||||||
front_camera_mock.last_history[0]["recording"]["status"] = "not ready"
|
front_camera_mock.last_history[0]["recording"]["status"] = "not ready"
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
front_camera_mock.recording_url.assert_called_once()
|
front_camera_mock.async_recording_url.assert_called_once()
|
||||||
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
||||||
assert stream is None
|
assert stream is None
|
||||||
|
|
||||||
# If the history id hasn't changed the camera will not check again for the video url
|
# If the history id hasn't changed the camera will not check again for the video url
|
||||||
# until the FORCE_REFRESH_INTERVAL has passed
|
# until the FORCE_REFRESH_INTERVAL has passed
|
||||||
front_camera_mock.last_history[0]["recording"]["status"] = "ready"
|
front_camera_mock.last_history[0]["recording"]["status"] = "ready"
|
||||||
front_camera_mock.recording_url = MagicMock(return_value="http://dummy.url")
|
front_camera_mock.async_recording_url = AsyncMock(return_value="http://dummy.url")
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
front_camera_mock.recording_url.assert_not_called()
|
front_camera_mock.async_recording_url.assert_not_called()
|
||||||
|
|
||||||
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
stream = await camera.async_get_mjpeg_stream(hass, mock_request, "camera.front")
|
||||||
assert stream is None
|
assert stream is None
|
||||||
@ -260,7 +259,7 @@ async def test_camera_handle_mjpeg_stream(
|
|||||||
freezer.tick(FORCE_REFRESH_INTERVAL)
|
freezer.tick(FORCE_REFRESH_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
front_camera_mock.recording_url.assert_called_once()
|
front_camera_mock.async_recording_url.assert_called_once()
|
||||||
|
|
||||||
# Now the stream should be returned
|
# Now the stream should be returned
|
||||||
stream_reader = MockStreamReader(SMALLEST_VALID_JPEG_BYTES)
|
stream_reader = MockStreamReader(SMALLEST_VALID_JPEG_BYTES)
|
||||||
@ -290,8 +289,8 @@ async def test_camera_image(
|
|||||||
assert state is not None
|
assert state is not None
|
||||||
|
|
||||||
# history not updated yet
|
# history not updated yet
|
||||||
front_camera_mock.history.assert_not_called()
|
front_camera_mock.async_history.assert_not_called()
|
||||||
front_camera_mock.recording_url.assert_not_called()
|
front_camera_mock.async_recording_url.assert_not_called()
|
||||||
with (
|
with (
|
||||||
patch(
|
patch(
|
||||||
"homeassistant.components.ring.camera.ffmpeg.async_get_image",
|
"homeassistant.components.ring.camera.ffmpeg.async_get_image",
|
||||||
@ -305,8 +304,8 @@ async def test_camera_image(
|
|||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
# history updated so image available
|
# history updated so image available
|
||||||
front_camera_mock.history.assert_called_once()
|
front_camera_mock.async_history.assert_called_once()
|
||||||
front_camera_mock.recording_url.assert_called_once()
|
front_camera_mock.async_recording_url.assert_called_once()
|
||||||
|
|
||||||
with patch(
|
with patch(
|
||||||
"homeassistant.components.ring.camera.ffmpeg.async_get_image",
|
"homeassistant.components.ring.camera.ffmpeg.async_get_image",
|
||||||
|
@ -57,7 +57,7 @@ async def test_form_error(
|
|||||||
result = await hass.config_entries.flow.async_init(
|
result = await hass.config_entries.flow.async_init(
|
||||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||||
)
|
)
|
||||||
mock_ring_auth.fetch_token.side_effect = error_type
|
mock_ring_auth.async_fetch_token.side_effect = error_type
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{"username": "hello@home-assistant.io", "password": "test-password"},
|
{"username": "hello@home-assistant.io", "password": "test-password"},
|
||||||
@ -79,7 +79,7 @@ async def test_form_2fa(
|
|||||||
assert result["type"] is FlowResultType.FORM
|
assert result["type"] is FlowResultType.FORM
|
||||||
assert result["errors"] == {}
|
assert result["errors"] == {}
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.side_effect = ring_doorbell.Requires2FAError
|
mock_ring_auth.async_fetch_token.side_effect = ring_doorbell.Requires2FAError
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
{
|
{
|
||||||
@ -88,20 +88,20 @@ async def test_form_2fa(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "fake-password", None
|
"foo@bar.com", "fake-password", None
|
||||||
)
|
)
|
||||||
|
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "2fa"
|
assert result2["step_id"] == "2fa"
|
||||||
mock_ring_auth.fetch_token.reset_mock(side_effect=True)
|
mock_ring_auth.async_fetch_token.reset_mock(side_effect=True)
|
||||||
mock_ring_auth.fetch_token.return_value = "new-foobar"
|
mock_ring_auth.async_fetch_token.return_value = "new-foobar"
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={"2fa": "123456"},
|
user_input={"2fa": "123456"},
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "fake-password", "123456"
|
"foo@bar.com", "fake-password", "123456"
|
||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
assert result3["type"] is FlowResultType.CREATE_ENTRY
|
||||||
@ -128,7 +128,7 @@ async def test_reauth(
|
|||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.side_effect = ring_doorbell.Requires2FAError
|
mock_ring_auth.async_fetch_token.side_effect = ring_doorbell.Requires2FAError
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -136,19 +136,19 @@ async def test_reauth(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "other_fake_password", None
|
"foo@bar.com", "other_fake_password", None
|
||||||
)
|
)
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["step_id"] == "2fa"
|
assert result2["step_id"] == "2fa"
|
||||||
mock_ring_auth.fetch_token.reset_mock(side_effect=True)
|
mock_ring_auth.async_fetch_token.reset_mock(side_effect=True)
|
||||||
mock_ring_auth.fetch_token.return_value = "new-foobar"
|
mock_ring_auth.async_fetch_token.return_value = "new-foobar"
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={"2fa": "123456"},
|
user_input={"2fa": "123456"},
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "other_fake_password", "123456"
|
"foo@bar.com", "other_fake_password", "123456"
|
||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.ABORT
|
assert result3["type"] is FlowResultType.ABORT
|
||||||
@ -185,7 +185,7 @@ async def test_reauth_error(
|
|||||||
[result] = flows
|
[result] = flows
|
||||||
assert result["step_id"] == "reauth_confirm"
|
assert result["step_id"] == "reauth_confirm"
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.side_effect = error_type
|
mock_ring_auth.async_fetch_token.side_effect = error_type
|
||||||
result2 = await hass.config_entries.flow.async_configure(
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
result["flow_id"],
|
result["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -194,15 +194,15 @@ async def test_reauth_error(
|
|||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "error_fake_password", None
|
"foo@bar.com", "error_fake_password", None
|
||||||
)
|
)
|
||||||
assert result2["type"] is FlowResultType.FORM
|
assert result2["type"] is FlowResultType.FORM
|
||||||
assert result2["errors"] == {"base": errors_msg}
|
assert result2["errors"] == {"base": errors_msg}
|
||||||
|
|
||||||
# Now test reauth can go on to succeed
|
# Now test reauth can go on to succeed
|
||||||
mock_ring_auth.fetch_token.reset_mock(side_effect=True)
|
mock_ring_auth.async_fetch_token.reset_mock(side_effect=True)
|
||||||
mock_ring_auth.fetch_token.return_value = "new-foobar"
|
mock_ring_auth.async_fetch_token.return_value = "new-foobar"
|
||||||
result3 = await hass.config_entries.flow.async_configure(
|
result3 = await hass.config_entries.flow.async_configure(
|
||||||
result2["flow_id"],
|
result2["flow_id"],
|
||||||
user_input={
|
user_input={
|
||||||
@ -210,7 +210,7 @@ async def test_reauth_error(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
mock_ring_auth.fetch_token.assert_called_once_with(
|
mock_ring_auth.async_fetch_token.assert_called_once_with(
|
||||||
"foo@bar.com", "other_fake_password", None
|
"foo@bar.com", "other_fake_password", None
|
||||||
)
|
)
|
||||||
assert result3["type"] is FlowResultType.ABORT
|
assert result3["type"] is FlowResultType.ABORT
|
||||||
|
@ -10,7 +10,7 @@ from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
|
|||||||
from homeassistant.components.ring import DOMAIN
|
from homeassistant.components.ring import DOMAIN
|
||||||
from homeassistant.components.ring.const import SCAN_INTERVAL
|
from homeassistant.components.ring.const import SCAN_INTERVAL
|
||||||
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
|
||||||
from homeassistant.const import CONF_USERNAME
|
from homeassistant.const import CONF_TOKEN, CONF_USERNAME
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
@ -42,11 +42,11 @@ async def test_setup_entry_device_update(
|
|||||||
"""Test devices are updating after setup entry."""
|
"""Test devices are updating after setup entry."""
|
||||||
|
|
||||||
front_door_doorbell = mock_ring_devices.get_device(987654)
|
front_door_doorbell = mock_ring_devices.get_device(987654)
|
||||||
front_door_doorbell.history.assert_not_called()
|
front_door_doorbell.async_history.assert_not_called()
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
await hass.async_block_till_done(wait_background_tasks=True)
|
await hass.async_block_till_done(wait_background_tasks=True)
|
||||||
front_door_doorbell.history.assert_called_once()
|
front_door_doorbell.async_history.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_auth_failed_on_setup(
|
async def test_auth_failed_on_setup(
|
||||||
@ -56,7 +56,7 @@ async def test_auth_failed_on_setup(
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Test auth failure on setup entry."""
|
"""Test auth failure on setup entry."""
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
mock_ring_client.update_data.side_effect = AuthenticationError
|
mock_ring_client.async_update_data.side_effect = AuthenticationError
|
||||||
|
|
||||||
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
@ -90,7 +90,7 @@ async def test_error_on_setup(
|
|||||||
"""Test non-auth errors on setup entry."""
|
"""Test non-auth errors on setup entry."""
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
mock_ring_client.update_data.side_effect = error_type
|
mock_ring_client.async_update_data.side_effect = error_type
|
||||||
|
|
||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
@ -113,7 +113,7 @@ async def test_auth_failure_on_global_update(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
mock_ring_client.update_devices.side_effect = AuthenticationError
|
mock_ring_client.async_update_devices.side_effect = AuthenticationError
|
||||||
|
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
@ -139,7 +139,7 @@ async def test_auth_failure_on_device_update(
|
|||||||
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
front_door_doorbell = mock_ring_devices.get_device(987654)
|
front_door_doorbell = mock_ring_devices.get_device(987654)
|
||||||
front_door_doorbell.history.side_effect = AuthenticationError
|
front_door_doorbell.async_history.side_effect = AuthenticationError
|
||||||
|
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
@ -178,7 +178,7 @@ async def test_error_on_global_update(
|
|||||||
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
mock_ring_client.update_devices.side_effect = error_type
|
mock_ring_client.async_update_devices.side_effect = error_type
|
||||||
|
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
@ -219,7 +219,7 @@ async def test_error_on_device_update(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
front_door_doorbell = mock_ring_devices.get_device(765432)
|
front_door_doorbell = mock_ring_devices.get_device(765432)
|
||||||
front_door_doorbell.history.side_effect = error_type
|
front_door_doorbell.async_history.side_effect = error_type
|
||||||
|
|
||||||
freezer.tick(SCAN_INTERVAL)
|
freezer.tick(SCAN_INTERVAL)
|
||||||
async_fire_time_changed(hass)
|
async_fire_time_changed(hass)
|
||||||
@ -386,3 +386,30 @@ async def test_update_unique_id_no_update(
|
|||||||
assert entity_migrated
|
assert entity_migrated
|
||||||
assert entity_migrated.unique_id == correct_unique_id
|
assert entity_migrated.unique_id == correct_unique_id
|
||||||
assert "Fixing non string unique id" not in caplog.text
|
assert "Fixing non string unique id" not in caplog.text
|
||||||
|
|
||||||
|
|
||||||
|
async def test_token_updated(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_ring_client,
|
||||||
|
mock_ring_init_auth_class,
|
||||||
|
) -> None:
|
||||||
|
"""Test that the token value is updated in the config entry.
|
||||||
|
|
||||||
|
This simulates the api calling the callback.
|
||||||
|
"""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
assert mock_ring_init_auth_class.call_count == 1
|
||||||
|
token_updater = mock_ring_init_auth_class.call_args.args[2]
|
||||||
|
assert mock_config_entry.data[CONF_TOKEN] == {"access_token": "mock-token"}
|
||||||
|
|
||||||
|
mock_ring_client.async_update_devices.side_effect = lambda: token_updater(
|
||||||
|
{"access_token": "new-mock-token"}
|
||||||
|
)
|
||||||
|
freezer.tick(SCAN_INTERVAL)
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
assert mock_config_entry.data[CONF_TOKEN] == {"access_token": "new-mock-token"}
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""The tests for the Ring light platform."""
|
"""The tests for the Ring light platform."""
|
||||||
|
|
||||||
from unittest.mock import PropertyMock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import ring_doorbell
|
import ring_doorbell
|
||||||
|
|
||||||
@ -109,15 +107,14 @@ async def test_light_errors_when_turned_on(
|
|||||||
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
front_light_mock = mock_ring_devices.get_device(765432)
|
front_light_mock = mock_ring_devices.get_device(765432)
|
||||||
p = PropertyMock(side_effect=exception_type)
|
front_light_mock.async_set_lights.side_effect = exception_type
|
||||||
type(front_light_mock).lights = p
|
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"light", "turn_on", {"entity_id": "light.front_light"}, blocking=True
|
"light", "turn_on", {"entity_id": "light.front_light"}, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
p.assert_called_once()
|
front_light_mock.async_set_lights.assert_called_once()
|
||||||
|
|
||||||
assert (
|
assert (
|
||||||
any(
|
any(
|
||||||
|
@ -49,7 +49,7 @@ async def test_default_ding_chime_can_be_played(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
||||||
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
|
downstairs_chime_mock.async_test_sound.assert_called_once_with(kind="ding")
|
||||||
|
|
||||||
state = hass.states.get("siren.downstairs_siren")
|
state = hass.states.get("siren.downstairs_siren")
|
||||||
assert state.state == "unknown"
|
assert state.state == "unknown"
|
||||||
@ -71,7 +71,7 @@ async def test_turn_on_plays_default_chime(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
||||||
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
|
downstairs_chime_mock.async_test_sound.assert_called_once_with(kind="ding")
|
||||||
|
|
||||||
state = hass.states.get("siren.downstairs_siren")
|
state = hass.states.get("siren.downstairs_siren")
|
||||||
assert state.state == "unknown"
|
assert state.state == "unknown"
|
||||||
@ -95,7 +95,7 @@ async def test_explicit_ding_chime_can_be_played(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
||||||
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
|
downstairs_chime_mock.async_test_sound.assert_called_once_with(kind="ding")
|
||||||
|
|
||||||
state = hass.states.get("siren.downstairs_siren")
|
state = hass.states.get("siren.downstairs_siren")
|
||||||
assert state.state == "unknown"
|
assert state.state == "unknown"
|
||||||
@ -117,7 +117,7 @@ async def test_motion_chime_can_be_played(
|
|||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
||||||
downstairs_chime_mock.test_sound.assert_called_once_with(kind="motion")
|
downstairs_chime_mock.async_test_sound.assert_called_once_with(kind="motion")
|
||||||
|
|
||||||
state = hass.states.get("siren.downstairs_siren")
|
state = hass.states.get("siren.downstairs_siren")
|
||||||
assert state.state == "unknown"
|
assert state.state == "unknown"
|
||||||
@ -146,7 +146,7 @@ async def test_siren_errors_when_turned_on(
|
|||||||
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
downstairs_chime_mock = mock_ring_devices.get_device(123456)
|
||||||
downstairs_chime_mock.test_sound.side_effect = exception_type
|
downstairs_chime_mock.async_test_sound.side_effect = exception_type
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
@ -155,7 +155,8 @@ async def test_siren_errors_when_turned_on(
|
|||||||
{"entity_id": "siren.downstairs_siren", "tone": "motion"},
|
{"entity_id": "siren.downstairs_siren", "tone": "motion"},
|
||||||
blocking=True,
|
blocking=True,
|
||||||
)
|
)
|
||||||
downstairs_chime_mock.test_sound.assert_called_once_with(kind="motion")
|
downstairs_chime_mock.async_test_sound.assert_called_once_with(kind="motion")
|
||||||
|
await hass.async_block_till_done()
|
||||||
assert (
|
assert (
|
||||||
any(
|
any(
|
||||||
flow
|
flow
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
"""The tests for the Ring switch platform."""
|
"""The tests for the Ring switch platform."""
|
||||||
|
|
||||||
from unittest.mock import PropertyMock
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import ring_doorbell
|
import ring_doorbell
|
||||||
|
|
||||||
@ -116,15 +114,14 @@ async def test_switch_errors_when_turned_on(
|
|||||||
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
|
||||||
|
|
||||||
front_siren_mock = mock_ring_devices.get_device(765432)
|
front_siren_mock = mock_ring_devices.get_device(765432)
|
||||||
p = PropertyMock(side_effect=exception_type)
|
front_siren_mock.async_set_siren.side_effect = exception_type
|
||||||
type(front_siren_mock).siren = p
|
|
||||||
|
|
||||||
with pytest.raises(HomeAssistantError):
|
with pytest.raises(HomeAssistantError):
|
||||||
await hass.services.async_call(
|
await hass.services.async_call(
|
||||||
"switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True
|
"switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True
|
||||||
)
|
)
|
||||||
await hass.async_block_till_done()
|
await hass.async_block_till_done()
|
||||||
p.assert_called_once()
|
front_siren_mock.async_set_siren.assert_called_once()
|
||||||
assert (
|
assert (
|
||||||
any(
|
any(
|
||||||
flow
|
flow
|
||||||
|
Loading…
x
Reference in New Issue
Block a user