diff --git a/tests/components/ring/conftest.py b/tests/components/ring/conftest.py index 82526d87b22..58e77184f55 100644 --- a/tests/components/ring/conftest.py +++ b/tests/components/ring/conftest.py @@ -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 diff --git a/tests/components/ring/device_mocks.py b/tests/components/ring/device_mocks.py new file mode 100644 index 00000000000..f43370c918d --- /dev/null +++ b/tests/components/ring/device_mocks.py @@ -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 diff --git a/tests/components/ring/fixtures/chime_devices.json b/tests/components/ring/fixtures/chime_devices.json deleted file mode 100644 index 5c3e60ec655..00000000000 --- a/tests/components/ring/fixtures/chime_devices.json +++ /dev/null @@ -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": [] -} diff --git a/tests/components/ring/fixtures/devices.json b/tests/components/ring/fixtures/devices.json index 8deee7ec413..fc708115500 100644 --- a/tests/components/ring/fixtures/devices.json +++ b/tests/components/ring/fixtures/devices.json @@ -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, diff --git a/tests/components/ring/fixtures/devices_updated.json b/tests/components/ring/fixtures/devices_updated.json deleted file mode 100644 index 01ea2ca25f5..00000000000 --- a/tests/components/ring/fixtures/devices_updated.json +++ /dev/null @@ -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" - } - ] -} diff --git a/tests/components/ring/fixtures/ding_active.json b/tests/components/ring/fixtures/ding_active.json index b367369fcff..1d089ab454e 100644 --- a/tests/components/ring/fixtures/ding_active.json +++ b/tests/components/ring/fixtures/ding_active.json @@ -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 } ] diff --git a/tests/components/ring/fixtures/doorboot_health_attrs.json b/tests/components/ring/fixtures/doorbot_health_attrs.json similarity index 100% rename from tests/components/ring/fixtures/doorboot_health_attrs.json rename to tests/components/ring/fixtures/doorbot_health_attrs.json diff --git a/tests/components/ring/fixtures/doorbot_history.json b/tests/components/ring/fixtures/doorbot_history.json index 2f6b44318bb..1c4c97e51c7 100644 --- a/tests/components/ring/fixtures/doorbot_history.json +++ b/tests/components/ring/fixtures/doorbot_history.json @@ -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", diff --git a/tests/components/ring/fixtures/doorbot_siren_on_response.json b/tests/components/ring/fixtures/doorbot_siren_on_response.json deleted file mode 100644 index 288800ed5fa..00000000000 --- a/tests/components/ring/fixtures/doorbot_siren_on_response.json +++ /dev/null @@ -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 -} diff --git a/tests/components/ring/fixtures/groups.json b/tests/components/ring/fixtures/groups.json deleted file mode 100644 index 399aaac1641..00000000000 --- a/tests/components/ring/fixtures/groups.json +++ /dev/null @@ -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" - } - ] -} diff --git a/tests/components/ring/fixtures/oauth.json b/tests/components/ring/fixtures/oauth.json deleted file mode 100644 index 902e40a4110..00000000000 --- a/tests/components/ring/fixtures/oauth.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "access_token": "eyJ0eWfvEQwqfJNKyQ9999", - "token_type": "bearer", - "expires_in": 3600, - "refresh_token": "67695a26bdefc1ac8999", - "scope": "client", - "created_at": 1529099870 -} diff --git a/tests/components/ring/fixtures/session.json b/tests/components/ring/fixtures/session.json deleted file mode 100644 index 62c8efa1d8f..00000000000 --- a/tests/components/ring/fixtures/session.json +++ /dev/null @@ -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" - } -} diff --git a/tests/components/ring/test_binary_sensor.py b/tests/components/ring/test_binary_sensor.py index ba73de05c9b..16bc6e872c1 100644 --- a/tests/components/ring/test_binary_sensor.py +++ b/tests/components/ring/test_binary_sensor.py @@ -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 diff --git a/tests/components/ring/test_button.py b/tests/components/ring/test_button.py index 6b2200b2bf3..6fef3295159 100644 --- a/tests/components/ring/test_button.py +++ b/tests/components/ring/test_button.py @@ -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() diff --git a/tests/components/ring/test_camera.py b/tests/components/ring/test_camera.py index 1b7023f931b..20a9ed5f0c9 100644 --- a/tests/components/ring/test_camera.py +++ b/tests/components/ring/test_camera.py @@ -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 diff --git a/tests/components/ring/test_config_flow.py b/tests/components/ring/test_config_flow.py index bedb4604814..2420bb9cc50 100644 --- a/tests/components/ring/test_config_flow.py +++ b/tests/components/ring/test_config_flow.py @@ -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.""" diff --git a/tests/components/ring/test_diagnostics.py b/tests/components/ring/test_diagnostics.py index 269446c3ad5..7d6eb8a7f76 100644 --- a/tests/components/ring/test_diagnostics.py +++ b/tests/components/ring/test_diagnostics.py @@ -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.""" diff --git a/tests/components/ring/test_init.py b/tests/components/ring/test_init.py index f4958f8e497..feb2485303a 100644 --- a/tests/components/ring/test_init.py +++ b/tests/components/ring/test_init.py @@ -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" diff --git a/tests/components/ring/test_light.py b/tests/components/ring/test_light.py index 1dcafadd86d..c2d21a22951 100644 --- a/tests/components/ring/test_light.py +++ b/tests/components/ring/test_light.py @@ -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 diff --git a/tests/components/ring/test_sensor.py b/tests/components/ring/test_sensor.py index c7c2d64e892..1f05c120251 100644 --- a/tests/components/ring/test_sensor.py +++ b/tests/components/ring/test_sensor.py @@ -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) diff --git a/tests/components/ring/test_siren.py b/tests/components/ring/test_siren.py index 8206f0c4ad3..7d3f673b61f 100644 --- a/tests/components/ring/test_siren.py +++ b/tests/components/ring/test_siren.py @@ -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 diff --git a/tests/components/ring/test_switch.py b/tests/components/ring/test_switch.py index 8e49a815a0b..405f20420b7 100644 --- a/tests/components/ring/test_switch.py +++ b/tests/components/ring/test_switch.py @@ -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