"""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 from homeassistant.components.nest.events import NEST_EVENT from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.setup import async_setup_component from homeassistant.util.dt import utcnow 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 = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) 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 = er.async_get(hass) 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 triggers[0] == { "platform": "device", "domain": DOMAIN, "type": "camera_sound", "device_id": entry1.device_id, } triggers = await async_get_device_automations(hass, "trigger", entry2.device_id) assert len(triggers) == 1 assert triggers[0] == { "platform": "device", "domain": DOMAIN, "type": "doorbell_chime", "device_id": entry2.device_id, } 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 = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) 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 = er.async_get(hass) 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", "timestamp": utcnow()} 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", "timestamp": utcnow()} 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", "timestamp": utcnow()} 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", "timestamp": utcnow()} 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", "timestamp": utcnow(), } 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", "timestamp": utcnow(), } 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 = dr.async_get(hass) device_entry = device_registry.async_get_device({("nest", DEVICE_ID)}) 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