diff --git a/.coveragerc b/.coveragerc index 24d6d3fe651..f74bf42a7e3 100644 --- a/.coveragerc +++ b/.coveragerc @@ -572,7 +572,18 @@ omit = homeassistant/components/neato/vacuum.py homeassistant/components/nederlandse_spoorwegen/sensor.py homeassistant/components/nello/lock.py - homeassistant/components/nest/* + homeassistant/components/nest/__init__.py + homeassistant/components/nest/api.py + homeassistant/components/nest/binary_sensor.py + homeassistant/components/nest/camera.py + homeassistant/components/nest/camera_legacy.py + homeassistant/components/nest/camera_sdm.py + homeassistant/components/nest/climate.py + homeassistant/components/nest/climate_legacy.py + homeassistant/components/nest/climate_sdm.py + homeassistant/components/nest/local_auth.py + homeassistant/components/nest/sensor.py + homeassistant/components/nest/sensor_legacy.py homeassistant/components/netatmo/__init__.py homeassistant/components/netatmo/api.py homeassistant/components/netatmo/camera.py diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index 7c2564f01bb..97c9da5794b 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -5,14 +5,7 @@ from datetime import datetime, timedelta import logging import threading -from google_nest_sdm.event import ( - AsyncEventCallback, - CameraMotionEvent, - CameraPersonEvent, - CameraSoundEvent, - DoorbellChimeEvent, - EventMessage, -) +from google_nest_sdm.event import AsyncEventCallback, EventMessage from google_nest_sdm.exceptions import GoogleNestException from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber from nest import Nest @@ -50,24 +43,19 @@ from . import api, config_flow, local_auth from .const import ( API_URL, DATA_SDM, + DATA_SUBSCRIBER, DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN, SIGNAL_NEST_UPDATE, ) +from .events import EVENT_NAME_MAP, NEST_EVENT _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) CONF_PROJECT_ID = "project_id" CONF_SUBSCRIBER_ID = "subscriber_id" -NEST_EVENT = "nest_event" -EVENT_TRAIT_MAP = { - DoorbellChimeEvent.NAME: "DoorbellChime", - CameraMotionEvent.NAME: "CameraMotion", - CameraPersonEvent.NAME: "CameraPerson", - CameraSoundEvent.NAME: "CameraSound", -} # Configuration for the legacy nest API @@ -206,11 +194,12 @@ class SignalUpdateCallback(AsyncEventCallback): _LOGGER.debug("Ignoring event for unregistered device '%s'", device_id) return for event in events: - if event not in EVENT_TRAIT_MAP: + event_type = EVENT_NAME_MAP.get(event) + if not event_type: continue message = { "device_id": device_entry.id, - "type": EVENT_TRAIT_MAP[event], + "type": event_type, } self._hass.bus.async_fire(NEST_EVENT, message) @@ -254,7 +243,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): subscriber.stop_async() raise ConfigEntryNotReady from err - hass.data[DOMAIN][entry.entry_id] = subscriber + hass.data[DOMAIN][DATA_SUBSCRIBER] = subscriber for component in PLATFORMS: hass.async_create_task( @@ -270,7 +259,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): # Legacy API return True - subscriber = hass.data[DOMAIN][entry.entry_id] + subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] subscriber.stop_async() unload_ok = all( await asyncio.gather( @@ -281,7 +270,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry): ) ) if unload_ok: - hass.data[DOMAIN].pop(entry.entry_id) + hass.data[DOMAIN].pop(DATA_SUBSCRIBER) return unload_ok diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index bd06fb0bd8d..cec35eeca29 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -18,7 +18,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow -from .const import DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE from .device_info import DeviceInfo _LOGGER = logging.getLogger(__name__) @@ -32,7 +32,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the cameras.""" - subscriber = hass.data[DOMAIN][entry.entry_id] + subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] try: device_manager = await subscriber.async_get_device_manager() except GoogleNestException as err: diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index f341b76c404..e56d35c1dff 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -39,7 +39,7 @@ from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE from .device_info import DeviceInfo # Mapping for sdm.devices.traits.ThermostatMode mode field @@ -81,7 +81,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the client entities.""" - subscriber = hass.data[DOMAIN][entry.entry_id] + subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] try: device_manager = await subscriber.async_get_device_manager() except GoogleNestException as err: diff --git a/homeassistant/components/nest/const.py b/homeassistant/components/nest/const.py index b418df97bba..3aba9ef5a7e 100644 --- a/homeassistant/components/nest/const.py +++ b/homeassistant/components/nest/const.py @@ -2,6 +2,7 @@ DOMAIN = "nest" DATA_SDM = "sdm" +DATA_SUBSCRIBER = "subscriber" SIGNAL_NEST_UPDATE = "nest_update" diff --git a/homeassistant/components/nest/device_trigger.py b/homeassistant/components/nest/device_trigger.py new file mode 100644 index 00000000000..199dcf425de --- /dev/null +++ b/homeassistant/components/nest/device_trigger.py @@ -0,0 +1,101 @@ +"""Provides device automations for Nest.""" +import logging +from typing import List + +import voluptuous as vol + +from homeassistant.components.automation import AutomationActionType +from homeassistant.components.device_automation import TRIGGER_BASE_SCHEMA +from homeassistant.components.device_automation.exceptions import ( + InvalidDeviceAutomationConfig, +) +from homeassistant.components.homeassistant.triggers import event as event_trigger +from homeassistant.const import CONF_DEVICE_ID, CONF_DOMAIN, CONF_PLATFORM, CONF_TYPE +from homeassistant.core import CALLBACK_TYPE, HomeAssistant +from homeassistant.helpers.typing import ConfigType + +from .const import DATA_SUBSCRIBER, DOMAIN +from .events import DEVICE_TRAIT_TRIGGER_MAP, NEST_EVENT + +_LOGGER = logging.getLogger(__name__) + +DEVICE = "device" + +TRIGGER_TYPES = set(DEVICE_TRAIT_TRIGGER_MAP.values()) + +TRIGGER_SCHEMA = TRIGGER_BASE_SCHEMA.extend( + { + vol.Required(CONF_TYPE): vol.In(TRIGGER_TYPES), + } +) + + +async def async_get_nest_device_id(hass: HomeAssistant, device_id: str) -> str: + """Get the nest API device_id from the HomeAssistant device_id.""" + device_registry = await hass.helpers.device_registry.async_get_registry() + device = device_registry.async_get(device_id) + for (domain, unique_id) in device.identifiers: + if domain == DOMAIN: + return unique_id + return None + + +async def async_get_device_trigger_types( + hass: HomeAssistant, nest_device_id: str +) -> List[str]: + """List event triggers supported for a Nest device.""" + # All devices should have already been loaded so any failures here are + # "shouldn't happen" cases + subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] + device_manager = await subscriber.async_get_device_manager() + nest_device = device_manager.devices.get(nest_device_id) + if not nest_device: + raise InvalidDeviceAutomationConfig(f"Nest device not found {nest_device_id}") + + # Determine the set of event types based on the supported device traits + trigger_types = [] + for trait in nest_device.traits.keys(): + trigger_type = DEVICE_TRAIT_TRIGGER_MAP.get(trait) + if trigger_type: + trigger_types.append(trigger_type) + return trigger_types + + +async def async_get_triggers(hass: HomeAssistant, device_id: str) -> List[dict]: + """List device triggers for a Nest device.""" + nest_device_id = await async_get_nest_device_id(hass, device_id) + if not nest_device_id: + raise InvalidDeviceAutomationConfig(f"Device not found {device_id}") + trigger_types = await async_get_device_trigger_types(hass, nest_device_id) + return [ + { + CONF_PLATFORM: DEVICE, + CONF_DEVICE_ID: device_id, + CONF_DOMAIN: DOMAIN, + CONF_TYPE: trigger_type, + } + for trigger_type in trigger_types + ] + + +async def async_attach_trigger( + hass: HomeAssistant, + config: ConfigType, + action: AutomationActionType, + automation_info: dict, +) -> CALLBACK_TYPE: + """Attach a trigger.""" + config = TRIGGER_SCHEMA(config) + event_config = event_trigger.TRIGGER_SCHEMA( + { + event_trigger.CONF_PLATFORM: "event", + event_trigger.CONF_EVENT_TYPE: NEST_EVENT, + event_trigger.CONF_EVENT_DATA: { + CONF_DEVICE_ID: config[CONF_DEVICE_ID], + CONF_TYPE: config[CONF_TYPE], + }, + } + ) + return await event_trigger.async_attach_trigger( + hass, event_config, action, automation_info, platform_type="device" + ) diff --git a/homeassistant/components/nest/events.py b/homeassistant/components/nest/events.py new file mode 100644 index 00000000000..abfe688c71c --- /dev/null +++ b/homeassistant/components/nest/events.py @@ -0,0 +1,49 @@ +"""Library from Pub/sub messages, events and device triggers.""" + +from google_nest_sdm.camera_traits import ( + CameraMotionTrait, + CameraPersonTrait, + CameraSoundTrait, +) +from google_nest_sdm.doorbell_traits import DoorbellChimeTrait +from google_nest_sdm.event import ( + CameraMotionEvent, + CameraPersonEvent, + CameraSoundEvent, + DoorbellChimeEvent, +) + +NEST_EVENT = "nest_event" +# The nest_event namespace will fire events that are triggered from messages +# received via the Pub/Sub subscriber. +# +# An example event data payload: +# { +# "device_id": "enterprises/some/device/identifier" +# "event_type": "camera_motion" +# } +# +# The following event types are fired: +EVENT_DOORBELL_CHIME = "doorbell_chime" +EVENT_CAMERA_MOTION = "camera_motion" +EVENT_CAMERA_PERSON = "camera_person" +EVENT_CAMERA_SOUND = "camera_sound" + +# Mapping of supported device traits to home assistant event types. Devices +# that support these traits will generate Pub/Sub event messages in +# the EVENT_NAME_MAP +DEVICE_TRAIT_TRIGGER_MAP = { + DoorbellChimeTrait.NAME: EVENT_DOORBELL_CHIME, + CameraMotionTrait.NAME: EVENT_CAMERA_MOTION, + CameraPersonTrait.NAME: EVENT_CAMERA_PERSON, + CameraSoundTrait.NAME: EVENT_CAMERA_SOUND, +} + +# Mapping of incoming SDM Pub/Sub event message types to the home assistant +# event type to fire. +EVENT_NAME_MAP = { + DoorbellChimeEvent.NAME: EVENT_DOORBELL_CHIME, + CameraMotionEvent.NAME: EVENT_CAMERA_MOTION, + CameraPersonEvent.NAME: EVENT_CAMERA_PERSON, + CameraSoundEvent.NAME: EVENT_CAMERA_SOUND, +} diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index b2b9500a156..9009414c5b4 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -19,7 +19,7 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType -from .const import DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE from .device_info import DeviceInfo _LOGGER = logging.getLogger(__name__) @@ -38,7 +38,7 @@ async def async_setup_sdm_entry( ) -> None: """Set up the sensors.""" - subscriber = hass.data[DOMAIN][entry.entry_id] + subscriber = hass.data[DOMAIN][DATA_SUBSCRIBER] try: device_manager = await subscriber.async_get_device_manager() except GoogleNestException as err: diff --git a/homeassistant/components/nest/strings.json b/homeassistant/components/nest/strings.json index 0ce9c902121..f945469e26f 100644 --- a/homeassistant/components/nest/strings.json +++ b/homeassistant/components/nest/strings.json @@ -7,12 +7,16 @@ "init": { "title": "Authentication Provider", "description": "[%key:common::config_flow::title::oauth2_pick_implementation%]", - "data": { "flow_impl": "Provider" } + "data": { + "flow_impl": "Provider" + } }, "link": { "title": "Link Nest Account", "description": "To link your Nest account, [authorize your account]({url}).\n\nAfter authorization, copy-paste the provided pin code below.", - "data": { "code": "[%key:common::config_flow::data::pin%]" } + "data": { + "code": "[%key:common::config_flow::data::pin%]" + } } }, "error": { @@ -31,5 +35,13 @@ "create_entry": { "default": "[%key:common::config_flow::create_entry::authenticated%]" } + }, + "device_automation": { + "trigger_type": { + "camera_person": "Person detected", + "camera_motion": "Motion detected", + "camera_sound": "Sound detected", + "doorbell_chime": "Doorbell pressed" + } } } diff --git a/homeassistant/components/nest/translations/en.json b/homeassistant/components/nest/translations/en.json index 3df0a9fa76d..b30e878368a 100644 --- a/homeassistant/components/nest/translations/en.json +++ b/homeassistant/components/nest/translations/en.json @@ -36,5 +36,13 @@ "title": "Pick Authentication Method" } } + }, + "device_automation": { + "trigger_type": { + "camera_person": "Person detected", + "camera_motion": "Motion detected", + "camera_sound": "Sound detected", + "doorbell_chime": "Doorbell pressed" + } } -} \ No newline at end of file +} diff --git a/tests/components/nest/test_device_trigger.py b/tests/components/nest/test_device_trigger.py new file mode 100644 index 00000000000..89dccf6c31e --- /dev/null +++ b/tests/components/nest/test_device_trigger.py @@ -0,0 +1,313 @@ +"""The tests for Nest device triggers.""" +from google_nest_sdm.device import Device +from google_nest_sdm.event import EventMessage +import pytest + +import homeassistant.components.automation as automation +from homeassistant.components.device_automation.exceptions import ( + InvalidDeviceAutomationConfig, +) +from homeassistant.components.nest import DOMAIN, NEST_EVENT +from homeassistant.setup import async_setup_component + +from .common import async_setup_sdm_platform + +from tests.common import ( + assert_lists_same, + async_get_device_automations, + async_mock_service, +) + +DEVICE_ID = "some-device-id" +DEVICE_NAME = "My Camera" +DATA_MESSAGE = {"message": "service-called"} + + +def make_camera(device_id, name=DEVICE_NAME, traits={}): + """Create a nest camera.""" + traits = traits.copy() + traits.update( + { + "sdm.devices.traits.Info": { + "customName": name, + }, + "sdm.devices.traits.CameraLiveStream": { + "maxVideoResolution": { + "width": 640, + "height": 480, + }, + "videoCodecs": ["H264"], + "audioCodecs": ["AAC"], + }, + } + ) + return Device.MakeDevice( + { + "name": device_id, + "type": "sdm.devices.types.CAMERA", + "traits": traits, + }, + auth=None, + ) + + +async def async_setup_camera(hass, devices=None): + """Set up the platform and prerequisites for testing available triggers.""" + if not devices: + devices = {DEVICE_ID: make_camera(device_id=DEVICE_ID)} + return await async_setup_sdm_platform(hass, "camera", devices) + + +async def setup_automation(hass, device_id, trigger_type): + """Set up an automation trigger for testing triggering.""" + return await async_setup_component( + hass, + automation.DOMAIN, + { + automation.DOMAIN: [ + { + "trigger": { + "platform": "device", + "domain": DOMAIN, + "device_id": device_id, + "type": trigger_type, + }, + "action": { + "service": "test.automation", + "data": DATA_MESSAGE, + }, + }, + ] + }, + ) + + +@pytest.fixture +def calls(hass): + """Track calls to a mock service.""" + return async_mock_service(hass, "test", "automation") + + +async def test_get_triggers(hass): + """Test we get the expected triggers from a nest.""" + camera = make_camera( + device_id=DEVICE_ID, + traits={ + "sdm.devices.traits.CameraMotion": {}, + "sdm.devices.traits.CameraPerson": {}, + }, + ) + await async_setup_camera(hass, {DEVICE_ID: camera}) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device_entry = device_registry.async_get_device( + {("nest", DEVICE_ID)}, connections={} + ) + + expected_triggers = [ + { + "platform": "device", + "domain": DOMAIN, + "type": "camera_motion", + "device_id": device_entry.id, + }, + { + "platform": "device", + "domain": DOMAIN, + "type": "camera_person", + "device_id": device_entry.id, + }, + ] + triggers = await async_get_device_automations(hass, "trigger", device_entry.id) + assert_lists_same(triggers, expected_triggers) + + +async def test_multiple_devices(hass): + """Test we get the expected triggers from a nest.""" + camera1 = make_camera( + device_id="device-id-1", + name="Camera 1", + traits={ + "sdm.devices.traits.CameraSound": {}, + }, + ) + camera2 = make_camera( + device_id="device-id-2", + name="Camera 2", + traits={ + "sdm.devices.traits.DoorbellChime": {}, + }, + ) + await async_setup_camera(hass, {"device-id-1": camera1, "device-id-2": camera2}) + + registry = await hass.helpers.entity_registry.async_get_registry() + entry1 = registry.async_get("camera.camera_1") + assert entry1.unique_id == "device-id-1-camera" + entry2 = registry.async_get("camera.camera_2") + assert entry2.unique_id == "device-id-2-camera" + + triggers = await async_get_device_automations(hass, "trigger", entry1.device_id) + assert len(triggers) == 1 + assert { + "platform": "device", + "domain": DOMAIN, + "type": "camera_sound", + "device_id": entry1.device_id, + } == triggers[0] + + triggers = await async_get_device_automations(hass, "trigger", entry2.device_id) + assert len(triggers) == 1 + assert { + "platform": "device", + "domain": DOMAIN, + "type": "doorbell_chime", + "device_id": entry2.device_id, + } == triggers[0] + + +async def test_triggers_for_invalid_device_id(hass): + """Get triggers for a device not found in the API.""" + camera = make_camera( + device_id=DEVICE_ID, + traits={ + "sdm.devices.traits.CameraMotion": {}, + "sdm.devices.traits.CameraPerson": {}, + }, + ) + await async_setup_camera(hass, {DEVICE_ID: camera}) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device_entry = device_registry.async_get_device( + {("nest", DEVICE_ID)}, connections={} + ) + assert device_entry is not None + + # Create an additional device that does not exist. Fetching supported + # triggers for an unknown device will fail. + assert len(device_entry.config_entries) == 1 + config_entry_id = next(iter(device_entry.config_entries)) + device_entry_2 = device_registry.async_get_or_create( + config_entry_id=config_entry_id, identifiers={(DOMAIN, "some-unknown-nest-id")} + ) + assert device_entry_2 is not None + + with pytest.raises(InvalidDeviceAutomationConfig): + await async_get_device_automations(hass, "trigger", device_entry_2.id) + + +async def test_no_triggers(hass): + """Test we get the expected triggers from a nest.""" + camera = make_camera(device_id=DEVICE_ID, traits={}) + await async_setup_camera(hass, {DEVICE_ID: camera}) + + registry = await hass.helpers.entity_registry.async_get_registry() + entry = registry.async_get("camera.my_camera") + assert entry.unique_id == "some-device-id-camera" + + triggers = await async_get_device_automations(hass, "trigger", entry.device_id) + assert [] == triggers + + +async def test_fires_on_camera_motion(hass, calls): + """Test camera_motion triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "camera_motion") + + message = {"device_id": DEVICE_ID, "type": "camera_motion"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data == DATA_MESSAGE + + +async def test_fires_on_camera_person(hass, calls): + """Test camera_person triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "camera_person") + + message = {"device_id": DEVICE_ID, "type": "camera_person"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data == DATA_MESSAGE + + +async def test_fires_on_camera_sound(hass, calls): + """Test camera_person triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "camera_sound") + + message = {"device_id": DEVICE_ID, "type": "camera_sound"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data == DATA_MESSAGE + + +async def test_fires_on_doorbell_chime(hass, calls): + """Test doorbell_chime triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "doorbell_chime") + + message = {"device_id": DEVICE_ID, "type": "doorbell_chime"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 1 + assert calls[0].data == DATA_MESSAGE + + +async def test_trigger_for_wrong_device_id(hass, calls): + """Test for turn_on and turn_off triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "camera_motion") + + message = {"device_id": "wrong-device-id", "type": "camera_motion"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_trigger_for_wrong_event_type(hass, calls): + """Test for turn_on and turn_off triggers firing.""" + assert await setup_automation(hass, DEVICE_ID, "camera_motion") + + message = {"device_id": DEVICE_ID, "type": "wrong-event-type"} + hass.bus.async_fire(NEST_EVENT, message) + await hass.async_block_till_done() + assert len(calls) == 0 + + +async def test_subscriber_automation(hass, calls): + """Test end to end subscriber triggers automation.""" + camera = make_camera( + device_id=DEVICE_ID, + traits={ + "sdm.devices.traits.CameraMotion": {}, + }, + ) + subscriber = await async_setup_camera(hass, {DEVICE_ID: camera}) + + device_registry = await hass.helpers.device_registry.async_get_registry() + device_entry = device_registry.async_get_device( + {("nest", DEVICE_ID)}, connections={} + ) + + assert await setup_automation(hass, device_entry.id, "camera_motion") + + # Simulate a pubsub message received by the subscriber with a motion event + event = EventMessage( + { + "eventId": "some-event-id", + "timestamp": "2019-01-01T00:00:01Z", + "resourceUpdate": { + "name": DEVICE_ID, + "events": { + "sdm.devices.events.CameraMotion.Motion": { + "eventSessionId": "CjY5Y3VKaTZwR3o4Y19YbTVfMF...", + "eventId": "FWWVQVUdGNUlTU2V4MGV2aTNXV...", + }, + }, + }, + }, + auth=None, + ) + await subscriber.async_receive_event(event) + await hass.async_block_till_done() + + assert len(calls) == 1 + assert calls[0].data == DATA_MESSAGE diff --git a/tests/components/nest/test_events.py b/tests/components/nest/test_events.py index b4b670fefbf..12314f60561 100644 --- a/tests/components/nest/test_events.py +++ b/tests/components/nest/test_events.py @@ -110,7 +110,7 @@ async def test_doorbell_chime_event(hass): assert len(events) == 1 assert events[0].data == { "device_id": entry.device_id, - "type": "DoorbellChime", + "type": "doorbell_chime", } @@ -134,7 +134,7 @@ async def test_camera_motion_event(hass): assert len(events) == 1 assert events[0].data == { "device_id": entry.device_id, - "type": "CameraMotion", + "type": "camera_motion", } @@ -158,7 +158,7 @@ async def test_camera_sound_event(hass): assert len(events) == 1 assert events[0].data == { "device_id": entry.device_id, - "type": "CameraSound", + "type": "camera_sound", } @@ -182,7 +182,7 @@ async def test_camera_person_event(hass): assert len(events) == 1 assert events[0].data == { "device_id": entry.device_id, - "type": "CameraPerson", + "type": "camera_person", } @@ -215,11 +215,11 @@ async def test_camera_multiple_event(hass): assert len(events) == 2 assert events[0].data == { "device_id": entry.device_id, - "type": "CameraMotion", + "type": "camera_motion", } assert events[1].data == { "device_id": entry.device_id, - "type": "CameraPerson", + "type": "camera_person", }