Fully mock the ring_doorbell api and remove requests_mock (#113140)

* Fully mock ring_doorbell library

* Add comments and docstrings

* Simplify devices_mocks and fake RingDevices

* Update post review

* Consolidate device filtering in conftest

* Fix ruff lambda assignment failure

* Fix ruff check fail

* Update post review
pull/118921/head
Steven B 2024-06-06 19:13:19 +01:00 committed by GitHub
parent 99b85e16d1
commit 333ac56904
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 570 additions and 923 deletions

View File

@ -1,17 +1,19 @@
"""Configuration for Ring tests."""
import re
from unittest.mock import AsyncMock, Mock, patch
from itertools import chain
from unittest.mock import AsyncMock, Mock, create_autospec, patch
import pytest
import requests_mock
import ring_doorbell
from typing_extensions import Generator
from homeassistant.components.ring import DOMAIN
from homeassistant.const import CONF_USERNAME
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry, load_fixture
from .device_mocks import get_active_alerts, get_devices_data, get_mock_devices
from tests.common import MockConfigEntry
from tests.components.light.conftest import mock_light_profiles # noqa: F401
@ -36,6 +38,67 @@ def mock_ring_auth():
yield mock_ring_auth.return_value
@pytest.fixture
def mock_ring_devices():
"""Mock Ring devices."""
devices = get_mock_devices()
device_list = list(chain.from_iterable(devices.values()))
def filter_devices(device_api_ai: int, device_family: set | None = None):
return next(
iter(
[
device
for device in device_list
if device.id == device_api_ai
and (not device_family or device.family in device_family)
]
)
)
class FakeRingDevices:
"""Class fakes the RingDevices class."""
all_devices = device_list
video_devices = (
devices["stickup_cams"]
+ devices["doorbots"]
+ devices["authorized_doorbots"]
)
stickup_cams = devices["stickup_cams"]
other = devices["other"]
chimes = devices["chimes"]
def get_device(self, id):
return filter_devices(id)
def get_video_device(self, id):
return filter_devices(
id, {"stickup_cams", "doorbots", "authorized_doorbots"}
)
def get_stickup_cam(self, id):
return filter_devices(id, {"stickup_cams"})
def get_other(self, id):
return filter_devices(id, {"other"})
return FakeRingDevices()
@pytest.fixture
def mock_ring_client(mock_ring_auth, mock_ring_devices):
"""Mock ring client api."""
mock_client = create_autospec(ring_doorbell.Ring)
mock_client.return_value.devices_data = get_devices_data()
mock_client.return_value.devices.return_value = mock_ring_devices
mock_client.return_value.active_alerts.side_effect = get_active_alerts
with patch("homeassistant.components.ring.Ring", new=mock_client):
yield mock_client.return_value
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock ConfigEntry."""
@ -55,91 +118,10 @@ async def mock_added_config_entry(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_ring_auth: Mock,
mock_ring_client: Mock,
) -> MockConfigEntry:
"""Mock ConfigEntry that's been added to HA."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
assert await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert DOMAIN in hass.config_entries.async_domains()
return mock_config_entry
@pytest.fixture(name="requests_mock")
def requests_mock_fixture():
"""Fixture to provide a requests mocker."""
with requests_mock.mock() as mock:
# Note all devices have an id of 987652, but a different device_id.
# the device_id is used as our unique_id, but the id is what is sent
# to the APIs, which is why every mock uses that id.
# Mocks the response for authenticating
mock.post(
"https://oauth.ring.com/oauth/token",
text=load_fixture("oauth.json", "ring"),
)
# Mocks the response for getting the login session
mock.post(
"https://api.ring.com/clients_api/session",
text=load_fixture("session.json", "ring"),
)
# Mocks the response for getting all the devices
mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("devices.json", "ring"),
)
mock.get(
"https://api.ring.com/clients_api/dings/active",
text=load_fixture("ding_active.json", "ring"),
)
# Mocks the response for getting the history of a device
mock.get(
re.compile(
r"https:\/\/api\.ring\.com\/clients_api\/doorbots\/\d+\/history"
),
text=load_fixture("doorbot_history.json", "ring"),
)
# Mocks the response for getting the health of a device
mock.get(
re.compile(r"https:\/\/api\.ring\.com\/clients_api\/doorbots\/\d+\/health"),
text=load_fixture("doorboot_health_attrs.json", "ring"),
)
# Mocks the response for getting a chimes health
mock.get(
re.compile(r"https:\/\/api\.ring\.com\/clients_api\/chimes\/\d+\/health"),
text=load_fixture("chime_health_attrs.json", "ring"),
)
mock.get(
re.compile(
r"https:\/\/api\.ring\.com\/clients_api\/dings\/\d+\/share/play"
),
status_code=200,
json={"url": "http://127.0.0.1/foo"},
)
mock.get(
"https://api.ring.com/groups/v1/locations/mock-location-id/groups",
text=load_fixture("groups.json", "ring"),
)
# Mocks the response for getting the history of the intercom
mock.get(
"https://api.ring.com/clients_api/doorbots/185036587/history",
text=load_fixture("intercom_history.json", "ring"),
)
# Mocks the response for setting properties in settings (i.e. motion_detection)
mock.patch(
re.compile(
r"https:\/\/api\.ring\.com\/devices\/v1\/devices\/\d+\/settings"
),
text="ok",
)
# Mocks the open door command for intercom devices
mock.put(
"https://api.ring.com/commands/v1/devices/185036587/device_rpc",
status_code=200,
text="{}",
)
# Mocks the response for getting the history of the intercom
mock.get(
"https://api.ring.com/clients_api/doorbots/185036587/history",
text=load_fixture("intercom_history.json", "ring"),
)
yield mock

View File

@ -0,0 +1,179 @@
"""Module for ring device mocks.
Creates a MagicMock for all device families, i.e. chimes, doorbells, stickup_cams and other.
Each device entry in the devices.json will have a MagicMock instead of the RingObject.
Mocks the api calls on the devices such as history() and health().
"""
from copy import deepcopy
from datetime import datetime
from time import time
from unittest.mock import MagicMock
from ring_doorbell import (
RingCapability,
RingChime,
RingDoorBell,
RingOther,
RingStickUpCam,
)
from homeassistant.components.ring.const import DOMAIN
from homeassistant.util import dt as dt_util
from tests.common import load_json_value_fixture
DEVICES_FIXTURE = load_json_value_fixture("devices.json", DOMAIN)
DOORBOT_HISTORY = load_json_value_fixture("doorbot_history.json", DOMAIN)
INTERCOM_HISTORY = load_json_value_fixture("intercom_history.json", DOMAIN)
DOORBOT_HEALTH = load_json_value_fixture("doorbot_health_attrs.json", DOMAIN)
CHIME_HEALTH = load_json_value_fixture("chime_health_attrs.json", DOMAIN)
DEVICE_ALERTS = load_json_value_fixture("ding_active.json", DOMAIN)
def get_mock_devices():
"""Return list of mock devices keyed by device_type."""
devices = {}
for device_family, device_class in DEVICE_TYPES.items():
devices[device_family] = [
_mocked_ring_device(
device, device_family, device_class, DEVICE_CAPABILITIES[device_class]
)
for device in DEVICES_FIXTURE[device_family]
]
return devices
def get_devices_data():
"""Return devices raw json used by the diagnostics module."""
return {
device_type: {obj["id"]: obj for obj in devices}
for device_type, devices in DEVICES_FIXTURE.items()
}
def get_active_alerts():
"""Return active alerts set to now."""
dings_fixture = deepcopy(DEVICE_ALERTS)
for ding in dings_fixture:
ding["now"] = time()
return dings_fixture
DEVICE_TYPES = {
"doorbots": RingDoorBell,
"authorized_doorbots": RingDoorBell,
"stickup_cams": RingStickUpCam,
"chimes": RingChime,
"other": RingOther,
}
DEVICE_CAPABILITIES = {
RingDoorBell: [
RingCapability.BATTERY,
RingCapability.VOLUME,
RingCapability.MOTION_DETECTION,
RingCapability.VIDEO,
RingCapability.HISTORY,
],
RingStickUpCam: [
RingCapability.BATTERY,
RingCapability.VOLUME,
RingCapability.MOTION_DETECTION,
RingCapability.VIDEO,
RingCapability.HISTORY,
RingCapability.SIREN,
RingCapability.LIGHT,
],
RingChime: [RingCapability.VOLUME],
RingOther: [RingCapability.OPEN, RingCapability.HISTORY],
}
def _mocked_ring_device(device_dict, device_family, device_class, capabilities):
"""Return a mocked device."""
mock_device = MagicMock(spec=device_class, name=f"Mocked {device_family!s}")
def has_capability(capability):
return (
capability in capabilities
if isinstance(capability, RingCapability)
else RingCapability.from_name(capability) in capabilities
)
def update_health_data(fixture):
mock_device.configure_mock(
wifi_signal_category=fixture["device_health"].get("latest_signal_category"),
wifi_signal_strength=fixture["device_health"].get("latest_signal_strength"),
)
def update_history_data(fixture):
for entry in fixture: # Mimic the api date parsing
if isinstance(entry["created_at"], str):
dt_at = datetime.strptime(entry["created_at"], "%Y-%m-%dT%H:%M:%S.%f%z")
entry["created_at"] = dt_util.as_utc(dt_at)
mock_device.configure_mock(last_history=fixture) # Set last_history
return fixture
# Configure the device attributes
mock_device.configure_mock(**device_dict)
# Configure the Properties on the device
mock_device.configure_mock(
model=device_family,
device_api_id=device_dict["id"],
name=device_dict["description"],
wifi_signal_category=None,
wifi_signal_strength=None,
family=device_family,
)
# Configure common methods
mock_device.has_capability.side_effect = has_capability
mock_device.update_health_data.side_effect = lambda: update_health_data(
DOORBOT_HEALTH if device_family != "chimes" else CHIME_HEALTH
)
# Configure methods based on capability
if has_capability(RingCapability.HISTORY):
mock_device.configure_mock(last_history=[])
mock_device.history.side_effect = lambda *_, **__: update_history_data(
DOORBOT_HISTORY if device_family != "other" else INTERCOM_HISTORY
)
if has_capability(RingCapability.MOTION_DETECTION):
mock_device.configure_mock(
motion_detection=device_dict["settings"].get("motion_detection_enabled"),
)
if has_capability(RingCapability.LIGHT):
mock_device.configure_mock(lights=device_dict.get("led_status"))
if has_capability(RingCapability.VOLUME):
mock_device.configure_mock(
volume=device_dict["settings"].get(
"doorbell_volume", device_dict["settings"].get("volume")
)
)
if has_capability(RingCapability.SIREN):
mock_device.configure_mock(
siren=device_dict["siren_status"].get("seconds_remaining")
)
if has_capability(RingCapability.BATTERY):
mock_device.configure_mock(
battery_life=min(
100, device_dict.get("battery_life", device_dict.get("battery_life2"))
)
)
if device_family == "other":
mock_device.configure_mock(
doorbell_volume=device_dict["settings"].get("doorbell_volume"),
mic_volume=device_dict["settings"].get("mic_volume"),
voice_volume=device_dict["settings"].get("voice_volume"),
)
return mock_device

View File

@ -1,35 +0,0 @@
{
"authorized_doorbots": [],
"chimes": [
{
"address": "123 Main St",
"alerts": { "connection": "online" },
"description": "Downstairs",
"device_id": "abcdef123",
"do_not_disturb": { "seconds_left": 0 },
"features": { "ringtones_enabled": true },
"firmware_version": "1.2.3",
"id": 123456,
"kind": "chime",
"latitude": 12.0,
"longitude": -70.12345,
"owned": true,
"owner": {
"email": "foo@bar.org",
"first_name": "Marcelo",
"id": 999999,
"last_name": "Assistant"
},
"settings": {
"ding_audio_id": null,
"ding_audio_user_id": null,
"motion_audio_id": null,
"motion_audio_user_id": null,
"volume": 2
},
"time_zone": "America/New_York"
}
],
"doorbots": [],
"stickup_cams": []
}

View File

@ -5,7 +5,7 @@
"address": "123 Main St",
"alerts": { "connection": "online" },
"description": "Downstairs",
"device_id": "abcdef123",
"device_id": "abcdef123456",
"do_not_disturb": { "seconds_left": 0 },
"features": { "ringtones_enabled": true },
"firmware_version": "1.2.3",
@ -36,7 +36,7 @@
"alerts": { "connection": "online" },
"battery_life": 4081,
"description": "Front Door",
"device_id": "aacdef123",
"device_id": "aacdef987654",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
@ -85,7 +85,7 @@
"alerts": { "connection": "online" },
"battery_life": 80,
"description": "Front",
"device_id": "aacdef123",
"device_id": "aacdef765432",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
@ -234,7 +234,7 @@
"alerts": { "connection": "online" },
"battery_life": 80,
"description": "Internal",
"device_id": "aacdef124",
"device_id": "aacdef345678",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
@ -395,7 +395,7 @@
"last_name": "",
"email": ""
},
"device_id": "124ba1b3fe1a",
"device_id": "abcdef185036587",
"time_zone": "Europe/Rome",
"firmware_version": "Up to Date",
"owned": true,

View File

@ -1,382 +0,0 @@
{
"authorized_doorbots": [],
"chimes": [
{
"address": "123 Main St",
"alerts": { "connection": "online" },
"description": "Downstairs",
"device_id": "abcdef123",
"do_not_disturb": { "seconds_left": 0 },
"features": { "ringtones_enabled": true },
"firmware_version": "1.2.3",
"id": 123456,
"kind": "chime",
"latitude": 12.0,
"longitude": -70.12345,
"owned": true,
"owner": {
"email": "foo@bar.org",
"first_name": "Marcelo",
"id": 999999,
"last_name": "Assistant"
},
"settings": {
"ding_audio_id": null,
"ding_audio_user_id": null,
"motion_audio_id": null,
"motion_audio_user_id": null,
"volume": 2
},
"time_zone": "America/New_York"
}
],
"doorbots": [
{
"address": "123 Main St",
"alerts": { "connection": "online" },
"battery_life": 4081,
"description": "Front Door",
"device_id": "aacdef123",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
"motion_message_enabled": false,
"motions_enabled": true,
"people_only_enabled": false,
"shadow_correction_enabled": false,
"show_recordings": true
},
"firmware_version": "1.4.26",
"id": 987654,
"kind": "lpd_v1",
"latitude": 12.0,
"longitude": -70.12345,
"motion_snooze": null,
"owned": true,
"owner": {
"email": "foo@bar.org",
"first_name": "Home",
"id": 999999,
"last_name": "Assistant"
},
"settings": {
"chime_settings": {
"duration": 3,
"enable": true,
"type": 0
},
"doorbell_volume": 1,
"enable_vod": true,
"live_view_preset_profile": "highest",
"live_view_presets": ["low", "middle", "high", "highest"],
"motion_detection_enabled": true,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": ["null", "low", "medium", "high"]
},
"subscribed": true,
"subscribed_motions": true,
"time_zone": "America/New_York"
}
],
"stickup_cams": [
{
"address": "123 Main St",
"alerts": { "connection": "online" },
"battery_life": 80,
"description": "Front",
"device_id": "aacdef123",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
"motion_message_enabled": false,
"motions_enabled": true,
"night_vision_enabled": false,
"people_only_enabled": false,
"shadow_correction_enabled": false,
"show_recordings": true
},
"firmware_version": "1.9.3",
"id": 765432,
"kind": "hp_cam_v1",
"latitude": 12.0,
"led_status": "on",
"location_id": null,
"longitude": -70.12345,
"motion_snooze": { "scheduled": true },
"night_mode_status": "false",
"owned": true,
"owner": {
"email": "foo@bar.org",
"first_name": "Foo",
"id": 999999,
"last_name": "Bar"
},
"ring_cam_light_installed": "false",
"ring_id": null,
"settings": {
"chime_settings": {
"duration": 10,
"enable": true,
"type": 0
},
"doorbell_volume": 11,
"enable_vod": true,
"floodlight_settings": {
"duration": 30,
"priority": 0
},
"light_schedule_settings": {
"end_hour": 0,
"end_minute": 0,
"start_hour": 0,
"start_minute": 0
},
"live_view_preset_profile": "highest",
"live_view_presets": ["low", "middle", "high", "highest"],
"motion_detection_enabled": true,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": ["none", "low", "medium", "high"],
"motion_zones": {
"active_motion_filter": 1,
"advanced_object_settings": {
"human_detection_confidence": {
"day": 0.7,
"night": 0.7
},
"motion_zone_overlap": {
"day": 0.1,
"night": 0.2
},
"object_size_maximum": {
"day": 0.8,
"night": 0.8
},
"object_size_minimum": {
"day": 0.03,
"night": 0.05
},
"object_time_overlap": {
"day": 0.1,
"night": 0.6
}
},
"enable_audio": false,
"pir_settings": {
"sensitivity1": 1,
"sensitivity2": 1,
"sensitivity3": 1,
"zone_mask": 6
},
"sensitivity": 5,
"zone1": {
"name": "Zone 1",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
},
"zone2": {
"name": "Zone 2",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
},
"zone3": {
"name": "Zone 3",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
}
},
"pir_motion_zones": [0, 1, 1],
"pir_settings": {
"sensitivity1": 1,
"sensitivity2": 1,
"sensitivity3": 1,
"zone_mask": 6
},
"stream_setting": 0,
"video_settings": {
"ae_level": 0,
"birton": null,
"brightness": 0,
"contrast": 64,
"saturation": 80
}
},
"siren_status": { "seconds_remaining": 30 },
"stolen": false,
"subscribed": true,
"subscribed_motions": true,
"time_zone": "America/New_York"
},
{
"address": "123 Main St",
"alerts": { "connection": "online" },
"battery_life": 80,
"description": "Internal",
"device_id": "aacdef124",
"external_connection": false,
"features": {
"advanced_motion_enabled": false,
"motion_message_enabled": false,
"motions_enabled": true,
"night_vision_enabled": false,
"people_only_enabled": false,
"shadow_correction_enabled": false,
"show_recordings": true
},
"firmware_version": "1.9.3",
"id": 345678,
"kind": "hp_cam_v1",
"latitude": 12.0,
"led_status": "off",
"location_id": null,
"longitude": -70.12345,
"motion_snooze": { "scheduled": true },
"night_mode_status": "false",
"owned": true,
"owner": {
"email": "foo@bar.org",
"first_name": "Foo",
"id": 999999,
"last_name": "Bar"
},
"ring_cam_light_installed": "false",
"ring_id": null,
"settings": {
"chime_settings": {
"duration": 10,
"enable": true,
"type": 0
},
"doorbell_volume": 11,
"enable_vod": true,
"floodlight_settings": {
"duration": 30,
"priority": 0
},
"light_schedule_settings": {
"end_hour": 0,
"end_minute": 0,
"start_hour": 0,
"start_minute": 0
},
"live_view_preset_profile": "highest",
"live_view_presets": ["low", "middle", "high", "highest"],
"motion_detection_enabled": false,
"motion_announcement": false,
"motion_snooze_preset_profile": "low",
"motion_snooze_presets": ["none", "low", "medium", "high"],
"motion_zones": {
"active_motion_filter": 1,
"advanced_object_settings": {
"human_detection_confidence": {
"day": 0.7,
"night": 0.7
},
"motion_zone_overlap": {
"day": 0.1,
"night": 0.2
},
"object_size_maximum": {
"day": 0.8,
"night": 0.8
},
"object_size_minimum": {
"day": 0.03,
"night": 0.05
},
"object_time_overlap": {
"day": 0.1,
"night": 0.6
}
},
"enable_audio": false,
"pir_settings": {
"sensitivity1": 1,
"sensitivity2": 1,
"sensitivity3": 1,
"zone_mask": 6
},
"sensitivity": 5,
"zone1": {
"name": "Zone 1",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
},
"zone2": {
"name": "Zone 2",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
},
"zone3": {
"name": "Zone 3",
"state": 2,
"vertex1": { "x": 0.0, "y": 0.0 },
"vertex2": { "x": 0.0, "y": 0.0 },
"vertex3": { "x": 0.0, "y": 0.0 },
"vertex4": { "x": 0.0, "y": 0.0 },
"vertex5": { "x": 0.0, "y": 0.0 },
"vertex6": { "x": 0.0, "y": 0.0 },
"vertex7": { "x": 0.0, "y": 0.0 },
"vertex8": { "x": 0.0, "y": 0.0 }
}
},
"pir_motion_zones": [0, 1, 1],
"pir_settings": {
"sensitivity1": 1,
"sensitivity2": 1,
"sensitivity3": 1,
"zone_mask": 6
},
"stream_setting": 0,
"video_settings": {
"ae_level": 0,
"birton": null,
"brightness": 0,
"contrast": 64,
"saturation": 80
}
},
"siren_status": { "seconds_remaining": 30 },
"stolen": false,
"subscribed": true,
"subscribed_motions": true,
"time_zone": "America/New_York"
}
]
}

View File

@ -24,5 +24,12 @@
"snapshot_url": "",
"state": "ringing",
"video_jitter_buffer_ms": 0
},
{
"kind": "motion",
"doorbot_id": 987654,
"state": "ringing",
"now": 1490949469.5498993,
"expires_in": 180
}
]

View File

@ -1,4 +1,14 @@
[
{
"answered": false,
"created_at": "2018-03-05T15:03:40.000Z",
"events": [],
"favorite": false,
"id": 987654321,
"kind": "ding",
"recording": { "status": "ready" },
"snapshot_url": ""
},
{
"answered": false,
"created_at": "2017-03-05T15:03:40.000Z",

View File

@ -1,6 +0,0 @@
{
"started_at": "2019-07-28T16:58:27.593+00:00",
"duration": 30,
"ends_at": "2019-07-28T16:58:57.593+00:00",
"seconds_remaining": 30
}

View File

@ -1,24 +0,0 @@
{
"device_groups": [
{
"device_group_id": "mock-group-id",
"location_id": "mock-location-id",
"name": "Landscape",
"devices": [
{
"doorbot_id": 12345678,
"location_id": "mock-location-id",
"type": "beams_ct200_transformer",
"mac_address": null,
"hardware_id": "1234567890",
"name": "Mock Transformer",
"deleted_at": null
}
],
"created_at": "2020-11-03T22:07:05Z",
"updated_at": "2020-11-19T03:52:59Z",
"deleted_at": null,
"external_id": "12345678-1234-5678-90ab-1234567890ab"
}
]
}

View File

@ -1,8 +0,0 @@
{
"access_token": "eyJ0eWfvEQwqfJNKyQ9999",
"token_type": "bearer",
"expires_in": 3600,
"refresh_token": "67695a26bdefc1ac8999",
"scope": "client",
"created_at": 1529099870
}

View File

@ -1,38 +0,0 @@
{
"profile": {
"authentication_token": "12345678910",
"email": "foo@bar.org",
"features": {
"chime_dnd_enabled": false,
"chime_pro_enabled": true,
"delete_all_enabled": true,
"delete_all_settings_enabled": false,
"device_health_alerts_enabled": true,
"floodlight_cam_enabled": true,
"live_view_settings_enabled": true,
"lpd_enabled": true,
"lpd_motion_announcement_enabled": false,
"multiple_calls_enabled": true,
"multiple_delete_enabled": true,
"nw_enabled": true,
"nw_larger_area_enabled": false,
"nw_user_activated": false,
"owner_proactive_snoozing_enabled": true,
"power_cable_enabled": false,
"proactive_snoozing_enabled": false,
"reactive_snoozing_enabled": false,
"remote_logging_format_storing": false,
"remote_logging_level": 1,
"ringplus_enabled": true,
"starred_events_enabled": true,
"stickupcam_setup_enabled": true,
"subscriptions_enabled": true,
"ujet_enabled": false,
"video_search_enabled": false,
"vod_enabled": false
},
"first_name": "Home",
"id": 999999,
"last_name": "Assistant"
}
}

View File

@ -1,32 +1,14 @@
"""The tests for the Ring binary sensor platform."""
from time import time
from unittest.mock import patch
import requests_mock
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .common import setup_platform
async def test_binary_sensor(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
) -> None:
async def test_binary_sensor(hass: HomeAssistant, mock_ring_client) -> None:
"""Test the Ring binary sensors."""
with patch(
"ring_doorbell.Ring.active_alerts",
return_value=[
{
"kind": "motion",
"doorbot_id": 987654,
"state": "ringing",
"now": time(),
"expires_in": 180,
}
],
):
await setup_platform(hass, "binary_sensor")
await setup_platform(hass, Platform.BINARY_SENSOR)
motion_state = hass.states.get("binary_sensor.front_door_motion")
assert motion_state is not None

View File

@ -1,7 +1,5 @@
"""The tests for the Ring button platform."""
import requests_mock
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
@ -11,7 +9,7 @@ from .common import setup_platform
async def test_entity_registry(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
entity_registry: er.EntityRegistry,
) -> None:
"""Tests that the devices are registered in the entity registry."""
@ -22,21 +20,19 @@ async def test_entity_registry(
async def test_button_opens_door(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant,
mock_ring_client,
mock_ring_devices,
) -> None:
"""Tests the door open button works correctly."""
await setup_platform(hass, Platform.BUTTON)
# Mocks the response for opening door
mock = requests_mock.put(
"https://api.ring.com/commands/v1/devices/185036587/device_rpc",
status_code=200,
text="{}",
)
mock_intercom = mock_ring_devices.get_device(185036587)
mock_intercom.open_door.assert_not_called()
await hass.services.async_call(
"button", "press", {"entity_id": "button.ingress_open_door"}, blocking=True
)
await hass.async_block_till_done()
assert mock.call_count == 1
await hass.async_block_till_done(wait_background_tasks=True)
mock_intercom.open_door.assert_called_once()

View File

@ -1,9 +1,8 @@
"""The tests for the Ring switch platform."""
from unittest.mock import PropertyMock, patch
from unittest.mock import PropertyMock
import pytest
import requests_mock
import ring_doorbell
from homeassistant.config_entries import SOURCE_REAUTH
@ -14,13 +13,11 @@ from homeassistant.helpers import entity_registry as er
from .common import setup_platform
from tests.common import load_fixture
async def test_entity_registry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Tests that the devices are registered in the entity registry."""
await setup_platform(hass, Platform.CAMERA)
@ -42,7 +39,7 @@ async def test_entity_registry(
)
async def test_camera_motion_detection_state_reports_correctly(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
entity_name,
expected_state,
friendly_name,
@ -56,7 +53,7 @@ async def test_camera_motion_detection_state_reports_correctly(
async def test_camera_motion_detection_can_be_turned_on(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client
) -> None:
"""Tests the siren turns on correctly."""
await setup_platform(hass, Platform.CAMERA)
@ -78,17 +75,15 @@ async def test_camera_motion_detection_can_be_turned_on(
async def test_updates_work(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the update service works correctly."""
await setup_platform(hass, Platform.CAMERA)
state = hass.states.get("camera.internal")
assert state.attributes.get("motion_detection") is True
# Changes the return to indicate that the switch is now on.
requests_mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("devices_updated.json", "ring"),
)
internal_camera_mock = mock_ring_devices.get_device(345678)
internal_camera_mock.motion_detection = False
await hass.services.async_call("ring", "update", {}, blocking=True)
@ -109,7 +104,8 @@ async def test_updates_work(
)
async def test_motion_detection_errors_when_turned_on(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
exception_type,
reauth_expected,
) -> None:
@ -119,19 +115,19 @@ async def test_motion_detection_errors_when_turned_on(
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch.object(
ring_doorbell.RingDoorBell, "motion_detection", new_callable=PropertyMock
) as mock_motion_detection:
mock_motion_detection.side_effect = exception_type
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"camera",
"enable_motion_detection",
{"entity_id": "camera.front"},
blocking=True,
)
await hass.async_block_till_done()
assert mock_motion_detection.call_count == 1
front_camera_mock = mock_ring_devices.get_device(765432)
p = PropertyMock(side_effect=exception_type)
type(front_camera_mock).motion_detection = p
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"camera",
"enable_motion_detection",
{"entity_id": "camera.front"},
blocking=True,
)
await hass.async_block_till_done()
p.assert_called_once()
assert (
any(
flow

View File

@ -17,7 +17,7 @@ from tests.common import MockConfigEntry
async def test_form(
hass: HomeAssistant,
mock_setup_entry: AsyncMock,
mock_ring_auth: Mock,
mock_ring_client: Mock,
) -> None:
"""Test we get the form."""

View File

@ -1,6 +1,5 @@
"""Test Ring diagnostics."""
import requests_mock
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
@ -14,7 +13,7 @@ async def test_entry_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
requests_mock: requests_mock.Mocker,
mock_ring_client,
snapshot: SnapshotAssertion,
) -> None:
"""Test Ring diagnostics."""

View File

@ -1,67 +1,69 @@
"""The tests for the Ring component."""
from datetime import timedelta
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
import requests_mock
from ring_doorbell import AuthenticationError, RingError, RingTimeout
from homeassistant.components import ring
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.components.light import DOMAIN as LIGHT_DOMAIN
from homeassistant.components.ring import DOMAIN
from homeassistant.components.ring.const import SCAN_INTERVAL
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er, issue_registry as ir
from homeassistant.setup import async_setup_component
from homeassistant.util import dt as dt_util
from tests.common import MockConfigEntry, async_fire_time_changed, load_fixture
from tests.common import MockConfigEntry, async_fire_time_changed
async def test_setup(hass: HomeAssistant, requests_mock: requests_mock.Mocker) -> None:
async def test_setup(hass: HomeAssistant, mock_ring_client) -> None:
"""Test the setup."""
await async_setup_component(hass, ring.DOMAIN, {})
requests_mock.post(
"https://oauth.ring.com/oauth/token", text=load_fixture("oauth.json", "ring")
)
requests_mock.post(
"https://api.ring.com/clients_api/session",
text=load_fixture("session.json", "ring"),
)
requests_mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("devices.json", "ring"),
)
requests_mock.get(
"https://api.ring.com/clients_api/chimes/999999/health",
text=load_fixture("chime_health_attrs.json", "ring"),
)
requests_mock.get(
"https://api.ring.com/clients_api/doorbots/987652/health",
text=load_fixture("doorboot_health_attrs.json", "ring"),
)
async def test_setup_entry(
hass: HomeAssistant,
mock_ring_client,
mock_added_config_entry: MockConfigEntry,
) -> None:
"""Test setup entry."""
assert mock_added_config_entry.state is ConfigEntryState.LOADED
async def test_setup_entry_device_update(
hass: HomeAssistant,
mock_ring_client,
mock_ring_devices,
freezer: FrozenDateTimeFactory,
mock_added_config_entry: MockConfigEntry,
caplog,
) -> None:
"""Test devices are updating after setup entry."""
front_door_doorbell = mock_ring_devices.get_device(987654)
front_door_doorbell.history.assert_not_called()
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
front_door_doorbell.history.assert_called_once()
async def test_auth_failed_on_setup(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test auth failure on setup entry."""
mock_config_entry.add_to_hass(hass)
with patch(
"ring_doorbell.Ring.update_data",
side_effect=AuthenticationError,
):
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.async_block_till_done()
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
mock_ring_client.update_data.side_effect = AuthenticationError
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.async_block_till_done()
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
assert mock_config_entry.state is ConfigEntryState.SETUP_ERROR
@pytest.mark.parametrize(
@ -80,37 +82,30 @@ async def test_auth_failed_on_setup(
)
async def test_error_on_setup(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
error_type,
log_msg,
) -> None:
"""Test auth failure on setup entry."""
"""Test non-auth errors on setup entry."""
mock_config_entry.add_to_hass(hass)
with patch(
"ring_doorbell.Ring.update_data",
side_effect=error_type,
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
mock_ring_client.update_data.side_effect = error_type
assert [
record.message
for record in caplog.records
if record.levelname == "DEBUG"
and record.name == "homeassistant.config_entries"
and log_msg in record.message
and DOMAIN in record.message
]
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
assert log_msg in caplog.text
async def test_auth_failure_on_global_update(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test authentication failure on global data update."""
@ -118,27 +113,24 @@ async def test_auth_failure_on_global_update(
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch(
"ring_doorbell.Ring.update_devices",
side_effect=AuthenticationError,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done()
assert "Authentication failed while fetching devices data: " in [
record.message
for record in caplog.records
if record.levelname == "ERROR"
and record.name == "homeassistant.components.ring.coordinator"
]
mock_ring_client.update_devices.side_effect = AuthenticationError
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert "Authentication failed while fetching devices data: " in caplog.text
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
async def test_auth_failure_on_device_update(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test authentication failure on device data update."""
@ -146,21 +138,17 @@ async def test_auth_failure_on_device_update(
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert not any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch(
"ring_doorbell.RingDoorBell.history",
side_effect=AuthenticationError,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done(wait_background_tasks=True)
assert "Authentication failed while fetching devices data: " in [
record.message
for record in caplog.records
if record.levelname == "ERROR"
and record.name == "homeassistant.components.ring.coordinator"
]
front_door_doorbell = mock_ring_devices.get_device(987654)
front_door_doorbell.history.side_effect = AuthenticationError
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert "Authentication failed while fetching devices data: " in caplog.text
assert any(mock_config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
@pytest.mark.parametrize(
@ -179,29 +167,27 @@ async def test_auth_failure_on_device_update(
)
async def test_error_on_global_update(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
error_type,
log_msg,
) -> None:
"""Test error on global data update."""
"""Test non-auth errors on global data update."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
with patch(
"ring_doorbell.Ring.update_devices",
side_effect=error_type,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done(wait_background_tasks=True)
mock_ring_client.update_devices.side_effect = error_type
assert log_msg in [
record.message for record in caplog.records if record.levelname == "ERROR"
]
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert mock_config_entry.entry_id in hass.data[DOMAIN]
assert log_msg in caplog.text
assert mock_config_entry.entry_id in hass.data[DOMAIN]
@pytest.mark.parametrize(
@ -220,35 +206,35 @@ async def test_error_on_global_update(
)
async def test_error_on_device_update(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
error_type,
log_msg,
) -> None:
"""Test auth failure on data update."""
"""Test non-auth errors on device update."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
with patch(
"ring_doorbell.RingDoorBell.history",
side_effect=error_type,
):
async_fire_time_changed(hass, dt_util.now() + timedelta(minutes=20))
await hass.async_block_till_done(wait_background_tasks=True)
front_door_doorbell = mock_ring_devices.get_device(765432)
front_door_doorbell.history.side_effect = error_type
assert log_msg in [
record.message for record in caplog.records if record.levelname == "ERROR"
]
assert mock_config_entry.entry_id in hass.data[DOMAIN]
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
assert log_msg in caplog.text
assert mock_config_entry.entry_id in hass.data[DOMAIN]
async def test_issue_deprecated_service_ring_update(
hass: HomeAssistant,
issue_registry: ir.IssueRegistry,
caplog: pytest.LogCaptureFixture,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the issue is raised on deprecated service ring.update."""
@ -288,7 +274,7 @@ async def test_update_unique_id(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
requests_mock: requests_mock.Mocker,
mock_ring_client,
domain: str,
old_unique_id: int | str,
) -> None:
@ -324,7 +310,7 @@ async def test_update_unique_id_existing(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Test unique_id update of integration."""
old_unique_id = 123456
@ -372,7 +358,7 @@ async def test_update_unique_id_no_update(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
caplog: pytest.LogCaptureFixture,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Test unique_id update of integration."""
correct_unique_id = "123456"

View File

@ -1,9 +1,8 @@
"""The tests for the Ring light platform."""
from unittest.mock import PropertyMock, patch
from unittest.mock import PropertyMock
import pytest
import requests_mock
import ring_doorbell
from homeassistant.config_entries import SOURCE_REAUTH
@ -14,13 +13,11 @@ from homeassistant.helpers import entity_registry as er
from .common import setup_platform
from tests.common import load_fixture
async def test_entity_registry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Tests that the devices are registered in the entity registry."""
await setup_platform(hass, Platform.LIGHT)
@ -33,7 +30,7 @@ async def test_entity_registry(
async def test_light_off_reports_correctly(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client
) -> None:
"""Tests that the initial state of a device that should be off is correct."""
await setup_platform(hass, Platform.LIGHT)
@ -44,7 +41,7 @@ async def test_light_off_reports_correctly(
async def test_light_on_reports_correctly(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client
) -> None:
"""Tests that the initial state of a device that should be on is correct."""
await setup_platform(hass, Platform.LIGHT)
@ -54,18 +51,10 @@ async def test_light_on_reports_correctly(
assert state.attributes.get("friendly_name") == "Internal Light"
async def test_light_can_be_turned_on(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
) -> None:
async def test_light_can_be_turned_on(hass: HomeAssistant, mock_ring_client) -> None:
"""Tests the light turns on correctly."""
await setup_platform(hass, Platform.LIGHT)
# Mocks the response for turning a light on
requests_mock.put(
"https://api.ring.com/clients_api/doorbots/765432/floodlight_light_on",
text=load_fixture("doorbot_siren_on_response.json", "ring"),
)
state = hass.states.get("light.front_light")
assert state.state == "off"
@ -79,17 +68,15 @@ async def test_light_can_be_turned_on(
async def test_updates_work(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the update service works correctly."""
await setup_platform(hass, Platform.LIGHT)
state = hass.states.get("light.front_light")
assert state.state == "off"
# Changes the return to indicate that the light is now on.
requests_mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("devices_updated.json", "ring"),
)
front_light_mock = mock_ring_devices.get_device(765432)
front_light_mock.lights = "on"
await hass.services.async_call("ring", "update", {}, blocking=True)
@ -110,7 +97,8 @@ async def test_updates_work(
)
async def test_light_errors_when_turned_on(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
exception_type,
reauth_expected,
) -> None:
@ -120,16 +108,17 @@ async def test_light_errors_when_turned_on(
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch.object(
ring_doorbell.RingStickUpCam, "lights", new_callable=PropertyMock
) as mock_lights:
mock_lights.side_effect = exception_type
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"light", "turn_on", {"entity_id": "light.front_light"}, blocking=True
)
await hass.async_block_till_done()
assert mock_lights.call_count == 1
front_light_mock = mock_ring_devices.get_device(765432)
p = PropertyMock(side_effect=exception_type)
type(front_light_mock).lights = p
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"light", "turn_on", {"entity_id": "light.front_light"}, blocking=True
)
await hass.async_block_till_done()
p.assert_called_once()
assert (
any(
flow

View File

@ -4,21 +4,19 @@ import logging
from freezegun.api import FrozenDateTimeFactory
import pytest
import requests_mock
from homeassistant.components.ring.const import SCAN_INTERVAL
from homeassistant.components.sensor import ATTR_STATE_CLASS, SensorStateClass
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import setup_platform
from tests.common import async_fire_time_changed, load_fixture
WIFI_ENABLED = False
from tests.common import async_fire_time_changed
async def test_sensor(hass: HomeAssistant, requests_mock: requests_mock.Mocker) -> None:
async def test_sensor(hass: HomeAssistant, mock_ring_client) -> None:
"""Test the Ring sensors."""
await setup_platform(hass, "sensor")
@ -41,10 +39,6 @@ async def test_sensor(hass: HomeAssistant, requests_mock: requests_mock.Mocker)
assert downstairs_volume_state is not None
assert downstairs_volume_state.state == "2"
downstairs_wifi_signal_strength_state = hass.states.get(
"sensor.downstairs_wifi_signal_strength"
)
ingress_mic_volume_state = hass.states.get("sensor.ingress_mic_volume")
assert ingress_mic_volume_state.state == "11"
@ -54,56 +48,118 @@ async def test_sensor(hass: HomeAssistant, requests_mock: requests_mock.Mocker)
ingress_voice_volume_state = hass.states.get("sensor.ingress_voice_volume")
assert ingress_voice_volume_state.state == "11"
if not WIFI_ENABLED:
return
assert downstairs_wifi_signal_strength_state is not None
assert downstairs_wifi_signal_strength_state.state == "-39"
front_door_wifi_signal_category_state = hass.states.get(
"sensor.front_door_wifi_signal_category"
)
assert front_door_wifi_signal_category_state is not None
assert front_door_wifi_signal_category_state.state == "good"
front_door_wifi_signal_strength_state = hass.states.get(
"sensor.front_door_wifi_signal_strength"
)
assert front_door_wifi_signal_strength_state is not None
assert front_door_wifi_signal_strength_state.state == "-58"
async def test_history(
@pytest.mark.parametrize(
("device_id", "device_name", "sensor_name", "expected_value"),
[
(987654, "front_door", "wifi_signal_category", "good"),
(987654, "front_door", "wifi_signal_strength", "-58"),
(123456, "downstairs", "wifi_signal_category", "good"),
(123456, "downstairs", "wifi_signal_strength", "-39"),
(765432, "front", "wifi_signal_category", "good"),
(765432, "front", "wifi_signal_strength", "-58"),
],
ids=[
"doorbell-category",
"doorbell-strength",
"chime-category",
"chime-strength",
"stickup_cam-category",
"stickup_cam-strength",
],
)
async def test_health_sensor(
hass: HomeAssistant,
mock_ring_client,
freezer: FrozenDateTimeFactory,
requests_mock: requests_mock.Mocker,
entity_registry: er.EntityRegistry,
device_id,
device_name,
sensor_name,
expected_value,
) -> None:
"""Test history derived sensors."""
await setup_platform(hass, Platform.SENSOR)
"""Test the Ring health sensors."""
entity_id = f"sensor.{device_name}_{sensor_name}"
# Enable the sensor as the health sensors are disabled by default
entity_entry = entity_registry.async_get_or_create(
"sensor",
"ring",
f"{device_id}-{sensor_name}",
suggested_object_id=f"{device_name}_{sensor_name}",
disabled_by=None,
)
assert entity_entry.disabled is False
assert entity_entry.entity_id == entity_id
await setup_platform(hass, "sensor")
await hass.async_block_till_done()
sensor_state = hass.states.get(entity_id)
assert sensor_state is not None
assert sensor_state.state == "unknown"
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(True)
await hass.async_block_till_done(wait_background_tasks=True)
sensor_state = hass.states.get(entity_id)
assert sensor_state is not None
assert sensor_state.state == expected_value
front_door_last_activity_state = hass.states.get("sensor.front_door_last_activity")
assert front_door_last_activity_state.state == "2017-03-05T15:03:40+00:00"
ingress_last_activity_state = hass.states.get("sensor.ingress_last_activity")
assert ingress_last_activity_state.state == "2024-02-02T11:21:24+00:00"
@pytest.mark.parametrize(
("device_name", "sensor_name", "expected_value"),
[
("front_door", "last_motion", "2017-03-05T15:03:40+00:00"),
("front_door", "last_ding", "2018-03-05T15:03:40+00:00"),
("front_door", "last_activity", "2018-03-05T15:03:40+00:00"),
("front", "last_motion", "2017-03-05T15:03:40+00:00"),
("ingress", "last_activity", "2024-02-02T11:21:24+00:00"),
],
ids=[
"doorbell-motion",
"doorbell-ding",
"doorbell-activity",
"stickup_cam-motion",
"other-activity",
],
)
async def test_history_sensor(
hass: HomeAssistant,
mock_ring_client,
freezer: FrozenDateTimeFactory,
device_name,
sensor_name,
expected_value,
) -> None:
"""Test the Ring sensors."""
await setup_platform(hass, "sensor")
entity_id = f"sensor.{device_name}_{sensor_name}"
sensor_state = hass.states.get(entity_id)
assert sensor_state is not None
assert sensor_state.state == "unknown"
freezer.tick(SCAN_INTERVAL)
async_fire_time_changed(hass)
await hass.async_block_till_done(wait_background_tasks=True)
sensor_state = hass.states.get(entity_id)
assert sensor_state is not None
assert sensor_state.state == expected_value
async def test_only_chime_devices(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Tests the update service works correctly if only chimes are returned."""
await hass.config.async_set_time_zone("UTC")
freezer.move_to("2021-01-09 12:00:00+00:00")
requests_mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("chime_devices.json", "ring"),
)
mock_ring_devices.all_devices = mock_ring_devices.chimes
await setup_platform(hass, Platform.SENSOR)
await hass.async_block_till_done()
caplog.set_level(logging.DEBUG)

View File

@ -1,9 +1,6 @@
"""The tests for the Ring button platform."""
from unittest.mock import patch
import pytest
import requests_mock
import ring_doorbell
from homeassistant.config_entries import SOURCE_REAUTH
@ -18,7 +15,7 @@ from .common import setup_platform
async def test_entity_registry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Tests that the devices are registered in the entity registry."""
await setup_platform(hass, Platform.SIREN)
@ -27,9 +24,7 @@ async def test_entity_registry(
assert entry.unique_id == "123456-siren"
async def test_sirens_report_correctly(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
) -> None:
async def test_sirens_report_correctly(hass: HomeAssistant, mock_ring_client) -> None:
"""Tests that the initial state of a device that should be on is correct."""
await setup_platform(hass, Platform.SIREN)
@ -39,16 +34,11 @@ async def test_sirens_report_correctly(
async def test_default_ding_chime_can_be_played(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the play chime request is sent correctly."""
await setup_platform(hass, Platform.SIREN)
# Mocks the response for playing a test sound
requests_mock.post(
"https://api.ring.com/clients_api/chimes/123456/play_sound",
text="SUCCESS",
)
await hass.services.async_call(
"siren",
"turn_on",
@ -58,26 +48,19 @@ async def test_default_ding_chime_can_be_played(
await hass.async_block_till_done()
assert requests_mock.request_history[-1].url.startswith(
"https://api.ring.com/clients_api/chimes/123456/play_sound?"
)
assert "kind=ding" in requests_mock.request_history[-1].url
downstairs_chime_mock = mock_ring_devices.get_device(123456)
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
state = hass.states.get("siren.downstairs_siren")
assert state.state == "unknown"
async def test_turn_on_plays_default_chime(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the play chime request is sent correctly when turned on."""
await setup_platform(hass, Platform.SIREN)
# Mocks the response for playing a test sound
requests_mock.post(
"https://api.ring.com/clients_api/chimes/123456/play_sound",
text="SUCCESS",
)
await hass.services.async_call(
"siren",
"turn_on",
@ -87,26 +70,21 @@ async def test_turn_on_plays_default_chime(
await hass.async_block_till_done()
assert requests_mock.request_history[-1].url.startswith(
"https://api.ring.com/clients_api/chimes/123456/play_sound?"
)
assert "kind=ding" in requests_mock.request_history[-1].url
downstairs_chime_mock = mock_ring_devices.get_device(123456)
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
state = hass.states.get("siren.downstairs_siren")
assert state.state == "unknown"
async def test_explicit_ding_chime_can_be_played(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant,
mock_ring_client,
mock_ring_devices,
) -> None:
"""Tests the play chime request is sent correctly."""
await setup_platform(hass, Platform.SIREN)
# Mocks the response for playing a test sound
requests_mock.post(
"https://api.ring.com/clients_api/chimes/123456/play_sound",
text="SUCCESS",
)
await hass.services.async_call(
"siren",
"turn_on",
@ -116,26 +94,19 @@ async def test_explicit_ding_chime_can_be_played(
await hass.async_block_till_done()
assert requests_mock.request_history[-1].url.startswith(
"https://api.ring.com/clients_api/chimes/123456/play_sound?"
)
assert "kind=ding" in requests_mock.request_history[-1].url
downstairs_chime_mock = mock_ring_devices.get_device(123456)
downstairs_chime_mock.test_sound.assert_called_once_with(kind="ding")
state = hass.states.get("siren.downstairs_siren")
assert state.state == "unknown"
async def test_motion_chime_can_be_played(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the play chime request is sent correctly."""
await setup_platform(hass, Platform.SIREN)
# Mocks the response for playing a test sound
requests_mock.post(
"https://api.ring.com/clients_api/chimes/123456/play_sound",
text="SUCCESS",
)
await hass.services.async_call(
"siren",
"turn_on",
@ -145,10 +116,8 @@ async def test_motion_chime_can_be_played(
await hass.async_block_till_done()
assert requests_mock.request_history[-1].url.startswith(
"https://api.ring.com/clients_api/chimes/123456/play_sound?"
)
assert "kind=motion" in requests_mock.request_history[-1].url
downstairs_chime_mock = mock_ring_devices.get_device(123456)
downstairs_chime_mock.test_sound.assert_called_once_with(kind="motion")
state = hass.states.get("siren.downstairs_siren")
assert state.state == "unknown"
@ -165,7 +134,8 @@ async def test_motion_chime_can_be_played(
)
async def test_siren_errors_when_turned_on(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
exception_type,
reauth_expected,
) -> None:
@ -175,18 +145,18 @@ async def test_siren_errors_when_turned_on(
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch.object(
ring_doorbell.RingChime, "test_sound", side_effect=exception_type
) as mock_siren:
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"siren",
"turn_on",
{"entity_id": "siren.downstairs_siren", "tone": "motion"},
blocking=True,
)
downstairs_chime_mock = mock_ring_devices.get_device(123456)
downstairs_chime_mock.test_sound.side_effect = exception_type
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"siren",
"turn_on",
{"entity_id": "siren.downstairs_siren", "tone": "motion"},
blocking=True,
)
await hass.async_block_till_done()
assert mock_siren.call_count == 1
downstairs_chime_mock.test_sound.assert_called_once_with(kind="motion")
assert (
any(
flow

View File

@ -1,9 +1,8 @@
"""The tests for the Ring switch platform."""
from unittest.mock import PropertyMock, patch
from unittest.mock import PropertyMock
import pytest
import requests_mock
import ring_doorbell
from homeassistant.config_entries import SOURCE_REAUTH
@ -15,13 +14,11 @@ from homeassistant.setup import async_setup_component
from .common import setup_platform
from tests.common import load_fixture
async def test_entity_registry(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
requests_mock: requests_mock.Mocker,
mock_ring_client,
) -> None:
"""Tests that the devices are registered in the entity registry."""
await setup_platform(hass, Platform.SWITCH)
@ -34,7 +31,7 @@ async def test_entity_registry(
async def test_siren_off_reports_correctly(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client
) -> None:
"""Tests that the initial state of a device that should be off is correct."""
await setup_platform(hass, Platform.SWITCH)
@ -45,7 +42,7 @@ async def test_siren_off_reports_correctly(
async def test_siren_on_reports_correctly(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client
) -> None:
"""Tests that the initial state of a device that should be on is correct."""
await setup_platform(hass, Platform.SWITCH)
@ -55,18 +52,10 @@ async def test_siren_on_reports_correctly(
assert state.attributes.get("friendly_name") == "Internal Siren"
async def test_siren_can_be_turned_on(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
) -> None:
async def test_siren_can_be_turned_on(hass: HomeAssistant, mock_ring_client) -> None:
"""Tests the siren turns on correctly."""
await setup_platform(hass, Platform.SWITCH)
# Mocks the response for turning a siren on
requests_mock.put(
"https://api.ring.com/clients_api/doorbots/765432/siren_on",
text=load_fixture("doorbot_siren_on_response.json", "ring"),
)
state = hass.states.get("switch.front_siren")
assert state.state == "off"
@ -80,17 +69,15 @@ async def test_siren_can_be_turned_on(
async def test_updates_work(
hass: HomeAssistant, requests_mock: requests_mock.Mocker
hass: HomeAssistant, mock_ring_client, mock_ring_devices
) -> None:
"""Tests the update service works correctly."""
await setup_platform(hass, Platform.SWITCH)
state = hass.states.get("switch.front_siren")
assert state.state == "off"
# Changes the return to indicate that the siren is now on.
requests_mock.get(
"https://api.ring.com/clients_api/ring_devices",
text=load_fixture("devices_updated.json", "ring"),
)
front_siren_mock = mock_ring_devices.get_device(765432)
front_siren_mock.siren = 20
await async_setup_component(hass, "homeassistant", {})
await hass.services.async_call(
@ -117,7 +104,8 @@ async def test_updates_work(
)
async def test_switch_errors_when_turned_on(
hass: HomeAssistant,
requests_mock: requests_mock.Mocker,
mock_ring_client,
mock_ring_devices,
exception_type,
reauth_expected,
) -> None:
@ -127,16 +115,16 @@ async def test_switch_errors_when_turned_on(
assert not any(config_entry.async_get_active_flows(hass, {SOURCE_REAUTH}))
with patch.object(
ring_doorbell.RingStickUpCam, "siren", new_callable=PropertyMock
) as mock_switch:
mock_switch.side_effect = exception_type
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True
)
await hass.async_block_till_done()
assert mock_switch.call_count == 1
front_siren_mock = mock_ring_devices.get_device(765432)
p = PropertyMock(side_effect=exception_type)
type(front_siren_mock).siren = p
with pytest.raises(HomeAssistantError):
await hass.services.async_call(
"switch", "turn_on", {"entity_id": "switch.front_siren"}, blocking=True
)
await hass.async_block_till_done()
p.assert_called_once()
assert (
any(
flow