2021-12-03 18:53:05 +00:00
|
|
|
"""Test for Nest Media Source.
|
|
|
|
|
|
|
|
These tests simulate recent camera events received by the subscriber exposed
|
|
|
|
as media in the media source.
|
|
|
|
"""
|
|
|
|
|
|
|
|
import datetime
|
|
|
|
from http import HTTPStatus
|
|
|
|
|
|
|
|
import aiohttp
|
|
|
|
from google_nest_sdm.device import Device
|
|
|
|
from google_nest_sdm.event import EventMessage
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from homeassistant.components import media_source
|
|
|
|
from homeassistant.components.media_player.errors import BrowseError
|
|
|
|
from homeassistant.components.media_source import const
|
|
|
|
from homeassistant.components.media_source.error import Unresolvable
|
|
|
|
from homeassistant.helpers import device_registry as dr
|
2021-12-05 08:39:18 +00:00
|
|
|
from homeassistant.helpers.template import DATE_STR_FORMAT
|
2021-12-03 18:53:05 +00:00
|
|
|
import homeassistant.util.dt as dt_util
|
|
|
|
|
|
|
|
from .common import async_setup_sdm_platform
|
|
|
|
|
|
|
|
DOMAIN = "nest"
|
|
|
|
DEVICE_ID = "example/api/device/id"
|
|
|
|
DEVICE_NAME = "Front"
|
|
|
|
PLATFORM = "camera"
|
|
|
|
NEST_EVENT = "nest_event"
|
2021-12-06 07:59:24 +00:00
|
|
|
EVENT_ID = "1aXEvi9ajKVTdDsXdJda8fzfCa..."
|
2021-12-03 18:53:05 +00:00
|
|
|
EVENT_SESSION_ID = "CjY5Y3VKaTZwR3o4Y19YbTVfMF..."
|
|
|
|
CAMERA_DEVICE_TYPE = "sdm.devices.types.CAMERA"
|
|
|
|
CAMERA_TRAITS = {
|
|
|
|
"sdm.devices.traits.Info": {
|
|
|
|
"customName": DEVICE_NAME,
|
|
|
|
},
|
|
|
|
"sdm.devices.traits.CameraImage": {},
|
|
|
|
"sdm.devices.traits.CameraEventImage": {},
|
|
|
|
"sdm.devices.traits.CameraPerson": {},
|
|
|
|
"sdm.devices.traits.CameraMotion": {},
|
|
|
|
}
|
|
|
|
BATTERY_CAMERA_TRAITS = {
|
|
|
|
"sdm.devices.traits.Info": {
|
|
|
|
"customName": DEVICE_NAME,
|
|
|
|
},
|
|
|
|
"sdm.devices.traits.CameraClipPreview": {},
|
|
|
|
"sdm.devices.traits.CameraLiveStream": {},
|
|
|
|
"sdm.devices.traits.CameraPerson": {},
|
|
|
|
"sdm.devices.traits.CameraMotion": {},
|
|
|
|
}
|
|
|
|
PERSON_EVENT = "sdm.devices.events.CameraPerson.Person"
|
|
|
|
MOTION_EVENT = "sdm.devices.events.CameraMotion.Motion"
|
|
|
|
|
|
|
|
TEST_IMAGE_URL = "https://domain/sdm_event_snapshot/dGTZwR3o4Y1..."
|
|
|
|
GENERATE_IMAGE_URL_RESPONSE = {
|
|
|
|
"results": {
|
|
|
|
"url": TEST_IMAGE_URL,
|
|
|
|
"token": "g.0.eventToken",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
IMAGE_BYTES_FROM_EVENT = b"test url image bytes"
|
|
|
|
IMAGE_AUTHORIZATION_HEADERS = {"Authorization": "Basic g.0.eventToken"}
|
|
|
|
|
|
|
|
|
|
|
|
async def async_setup_devices(hass, auth, device_type, traits={}, events=[]):
|
|
|
|
"""Set up the platform and prerequisites."""
|
|
|
|
devices = {
|
|
|
|
DEVICE_ID: Device.MakeDevice(
|
|
|
|
{
|
|
|
|
"name": DEVICE_ID,
|
|
|
|
"type": device_type,
|
|
|
|
"traits": traits,
|
|
|
|
},
|
|
|
|
auth=auth,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
subscriber = await async_setup_sdm_platform(hass, PLATFORM, devices=devices)
|
|
|
|
if events:
|
|
|
|
for event in events:
|
|
|
|
await subscriber.async_receive_event(event)
|
|
|
|
await hass.async_block_till_done()
|
|
|
|
return subscriber
|
|
|
|
|
|
|
|
|
2021-12-06 07:59:24 +00:00
|
|
|
def create_event(
|
|
|
|
event_session_id, event_id, event_type, timestamp=None, device_id=None
|
|
|
|
):
|
2021-12-03 18:53:05 +00:00
|
|
|
"""Create an EventMessage for a single event type."""
|
|
|
|
if not timestamp:
|
|
|
|
timestamp = dt_util.now()
|
|
|
|
event_data = {
|
|
|
|
event_type: {
|
2021-12-06 07:59:24 +00:00
|
|
|
"eventSessionId": event_session_id,
|
2021-12-03 18:53:05 +00:00
|
|
|
"eventId": event_id,
|
|
|
|
},
|
|
|
|
}
|
2021-12-06 07:59:24 +00:00
|
|
|
return create_event_message(event_data, timestamp, device_id=device_id)
|
2021-12-03 18:53:05 +00:00
|
|
|
|
|
|
|
|
2021-12-06 07:59:24 +00:00
|
|
|
def create_event_message(event_data, timestamp, device_id=None):
|
2021-12-03 18:53:05 +00:00
|
|
|
"""Create an EventMessage for a single event type."""
|
2021-12-05 21:02:37 +00:00
|
|
|
if device_id is None:
|
|
|
|
device_id = DEVICE_ID
|
2021-12-03 18:53:05 +00:00
|
|
|
return EventMessage(
|
|
|
|
{
|
2021-12-06 07:59:24 +00:00
|
|
|
"eventId": f"{EVENT_ID}-{timestamp}",
|
2021-12-03 18:53:05 +00:00
|
|
|
"timestamp": timestamp.isoformat(timespec="seconds"),
|
|
|
|
"resourceUpdate": {
|
2021-12-05 21:02:37 +00:00
|
|
|
"name": device_id,
|
2021-12-03 18:53:05 +00:00
|
|
|
"events": event_data,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
auth=None,
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_no_eligible_devices(hass, auth):
|
|
|
|
"""Test a media source with no eligible camera devices."""
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
"sdm.devices.types.THERMOSTAT",
|
|
|
|
{
|
|
|
|
"sdm.devices.traits.Temperature": {},
|
|
|
|
},
|
|
|
|
)
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}")
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == ""
|
|
|
|
assert browse.title == "Nest"
|
|
|
|
assert not browse.children
|
|
|
|
|
|
|
|
|
|
|
|
async def test_supported_device(hass, auth):
|
|
|
|
"""Test a media source with a supported camera."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}")
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.title == "Nest"
|
|
|
|
assert browse.identifier == ""
|
|
|
|
assert browse.can_expand
|
|
|
|
assert len(browse.children) == 1
|
|
|
|
assert browse.children[0].domain == DOMAIN
|
|
|
|
assert browse.children[0].identifier == device.id
|
|
|
|
assert browse.children[0].title == "Front: Recent Events"
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}"
|
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == device.id
|
|
|
|
assert browse.title == "Front: Recent Events"
|
|
|
|
assert len(browse.children) == 0
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_event(hass, auth, hass_client):
|
|
|
|
"""Test a media source and image created for an event."""
|
|
|
|
event_timestamp = dt_util.now()
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
CAMERA_DEVICE_TYPE,
|
|
|
|
CAMERA_TRAITS,
|
|
|
|
events=[
|
|
|
|
create_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
EVENT_SESSION_ID,
|
|
|
|
EVENT_ID,
|
2021-12-03 18:53:05 +00:00
|
|
|
PERSON_EVENT,
|
|
|
|
timestamp=event_timestamp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
# Media root directory
|
|
|
|
browse = await media_source.async_browse_media(hass, f"{const.URI_SCHEME}{DOMAIN}")
|
|
|
|
assert browse.title == "Nest"
|
|
|
|
assert browse.identifier == ""
|
|
|
|
assert browse.can_expand
|
|
|
|
# A device is represented as a child directory
|
|
|
|
assert len(browse.children) == 1
|
|
|
|
assert browse.children[0].domain == DOMAIN
|
|
|
|
assert browse.children[0].identifier == device.id
|
|
|
|
assert browse.children[0].title == "Front: Recent Events"
|
|
|
|
assert browse.children[0].can_expand
|
|
|
|
# Expanding the root does not expand the device
|
|
|
|
assert len(browse.children[0].children) == 0
|
|
|
|
|
|
|
|
# Browse to the device
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}"
|
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == device.id
|
|
|
|
assert browse.title == "Front: Recent Events"
|
|
|
|
assert browse.can_expand
|
|
|
|
# The device expands recent events
|
|
|
|
assert len(browse.children) == 1
|
|
|
|
assert browse.children[0].domain == DOMAIN
|
2021-12-06 07:59:24 +00:00
|
|
|
assert browse.children[0].identifier == f"{device.id}/{EVENT_SESSION_ID}"
|
2021-12-05 08:39:18 +00:00
|
|
|
event_timestamp_string = event_timestamp.strftime(DATE_STR_FORMAT)
|
2021-12-03 18:53:05 +00:00
|
|
|
assert browse.children[0].title == f"Person @ {event_timestamp_string}"
|
|
|
|
assert not browse.children[0].can_expand
|
|
|
|
assert len(browse.children[0].children) == 0
|
|
|
|
|
|
|
|
# Browse to the event
|
|
|
|
browse = await media_source.async_browse_media(
|
2021-12-06 07:59:24 +00:00
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
2021-12-06 07:59:24 +00:00
|
|
|
assert browse.identifier == f"{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
assert "Person" in browse.title
|
|
|
|
assert not browse.can_expand
|
|
|
|
assert not browse.children
|
|
|
|
|
|
|
|
# Resolving the event links to the media
|
|
|
|
media = await media_source.async_resolve_media(
|
2021-12-06 07:59:24 +00:00
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
)
|
2021-12-06 07:59:24 +00:00
|
|
|
assert media.url == f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
assert media.mime_type == "image/jpeg"
|
|
|
|
|
|
|
|
auth.responses = [
|
|
|
|
aiohttp.web.json_response(GENERATE_IMAGE_URL_RESPONSE),
|
|
|
|
aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT),
|
|
|
|
]
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get(media.url)
|
|
|
|
assert response.status == HTTPStatus.OK, "Response not matched: %s" % response
|
|
|
|
contents = await response.read()
|
|
|
|
assert contents == IMAGE_BYTES_FROM_EVENT
|
|
|
|
|
|
|
|
|
|
|
|
async def test_event_order(hass, auth):
|
|
|
|
"""Test multiple events are in descending timestamp order."""
|
2021-12-06 07:59:24 +00:00
|
|
|
event_session_id1 = "FWWVQVUdGNUlTU2V4MGV2aTNXV..."
|
2021-12-03 18:53:05 +00:00
|
|
|
event_timestamp1 = dt_util.now()
|
2021-12-06 07:59:24 +00:00
|
|
|
event_session_id2 = "GXXWRWVeHNUlUU3V3MGV3bUOYW..."
|
2021-12-03 18:53:05 +00:00
|
|
|
event_timestamp2 = event_timestamp1 + datetime.timedelta(seconds=5)
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
CAMERA_DEVICE_TYPE,
|
|
|
|
CAMERA_TRAITS,
|
|
|
|
events=[
|
|
|
|
create_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
event_session_id1,
|
|
|
|
EVENT_ID + "1",
|
2021-12-03 18:53:05 +00:00
|
|
|
PERSON_EVENT,
|
|
|
|
timestamp=event_timestamp1,
|
|
|
|
),
|
|
|
|
create_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
event_session_id2,
|
|
|
|
EVENT_ID + "2",
|
2021-12-03 18:53:05 +00:00
|
|
|
MOTION_EVENT,
|
|
|
|
timestamp=event_timestamp2,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}"
|
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == device.id
|
|
|
|
assert browse.title == "Front: Recent Events"
|
|
|
|
assert browse.can_expand
|
|
|
|
|
|
|
|
# Motion event is most recent
|
|
|
|
assert len(browse.children) == 2
|
|
|
|
assert browse.children[0].domain == DOMAIN
|
2021-12-06 07:59:24 +00:00
|
|
|
assert browse.children[0].identifier == f"{device.id}/{event_session_id2}"
|
2021-12-05 08:39:18 +00:00
|
|
|
event_timestamp_string = event_timestamp2.strftime(DATE_STR_FORMAT)
|
2021-12-03 18:53:05 +00:00
|
|
|
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
|
|
|
|
assert not browse.children[0].can_expand
|
|
|
|
|
|
|
|
# Person event is next
|
|
|
|
assert browse.children[1].domain == DOMAIN
|
|
|
|
|
2021-12-06 07:59:24 +00:00
|
|
|
assert browse.children[1].identifier == f"{device.id}/{event_session_id1}"
|
2021-12-05 08:39:18 +00:00
|
|
|
event_timestamp_string = event_timestamp1.strftime(DATE_STR_FORMAT)
|
2021-12-03 18:53:05 +00:00
|
|
|
assert browse.children[1].title == f"Person @ {event_timestamp_string}"
|
|
|
|
assert not browse.children[1].can_expand
|
|
|
|
|
|
|
|
|
|
|
|
async def test_browse_invalid_device_id(hass, auth):
|
|
|
|
"""Test a media source request for an invalid device id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
with pytest.raises(BrowseError):
|
|
|
|
await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/invalid-device-id"
|
|
|
|
)
|
|
|
|
|
|
|
|
with pytest.raises(BrowseError):
|
|
|
|
await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/invalid-device-id/invalid-event-id"
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_browse_invalid_event_id(hass, auth):
|
|
|
|
"""Test a media source browsing for an invalid event id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}"
|
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == device.id
|
|
|
|
assert browse.title == "Front: Recent Events"
|
|
|
|
|
|
|
|
with pytest.raises(BrowseError):
|
|
|
|
await media_source.async_browse_media(
|
|
|
|
hass,
|
|
|
|
f"{const.URI_SCHEME}{DOMAIN}/{device.id}/GXXWRWVeHNUlUU3V3MGV3bUOYW...",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_resolve_missing_event_id(hass, auth):
|
|
|
|
"""Test a media source request missing an event id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
with pytest.raises(Unresolvable):
|
|
|
|
await media_source.async_resolve_media(
|
|
|
|
hass,
|
|
|
|
f"{const.URI_SCHEME}{DOMAIN}/{device.id}",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_resolve_invalid_device_id(hass, auth):
|
|
|
|
"""Test resolving media for an invalid event id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
with pytest.raises(Unresolvable):
|
|
|
|
await media_source.async_resolve_media(
|
|
|
|
hass,
|
|
|
|
f"{const.URI_SCHEME}{DOMAIN}/invalid-device-id/GXXWRWVeHNUlUU3V3MGV3bUOYW...",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_resolve_invalid_event_id(hass, auth):
|
|
|
|
"""Test resolving media for an invalid event id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
with pytest.raises(Unresolvable):
|
|
|
|
await media_source.async_resolve_media(
|
|
|
|
hass,
|
|
|
|
f"{const.URI_SCHEME}{DOMAIN}/{device.id}/GXXWRWVeHNUlUU3V3MGV3bUOYW...",
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_camera_event_clip_preview(hass, auth, hass_client):
|
|
|
|
"""Test an event for a battery camera video clip."""
|
|
|
|
event_timestamp = dt_util.now()
|
|
|
|
event_data = {
|
2021-12-06 07:59:24 +00:00
|
|
|
"sdm.devices.events.CameraMotion.Motion": {
|
|
|
|
"eventSessionId": EVENT_SESSION_ID,
|
|
|
|
"eventId": "n:2",
|
|
|
|
},
|
2021-12-03 18:53:05 +00:00
|
|
|
"sdm.devices.events.CameraClipPreview.ClipPreview": {
|
|
|
|
"eventSessionId": EVENT_SESSION_ID,
|
|
|
|
"previewUrl": "https://127.0.0.1/example",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
CAMERA_DEVICE_TYPE,
|
|
|
|
BATTERY_CAMERA_TRAITS,
|
|
|
|
events=[
|
|
|
|
create_event_message(
|
|
|
|
event_data,
|
|
|
|
timestamp=event_timestamp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
# Browse to the device
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}"
|
|
|
|
)
|
|
|
|
assert browse.domain == DOMAIN
|
|
|
|
assert browse.identifier == device.id
|
|
|
|
assert browse.title == "Front: Recent Events"
|
|
|
|
assert browse.can_expand
|
|
|
|
# The device expands recent events
|
|
|
|
assert len(browse.children) == 1
|
|
|
|
assert browse.children[0].domain == DOMAIN
|
|
|
|
actual_event_id = browse.children[0].identifier
|
2021-12-05 08:39:18 +00:00
|
|
|
event_timestamp_string = event_timestamp.strftime(DATE_STR_FORMAT)
|
2021-12-06 07:59:24 +00:00
|
|
|
assert browse.children[0].title == f"Motion @ {event_timestamp_string}"
|
2021-12-03 18:53:05 +00:00
|
|
|
assert not browse.children[0].can_expand
|
|
|
|
assert len(browse.children[0].children) == 0
|
|
|
|
|
|
|
|
# Resolving the event links to the media
|
|
|
|
media = await media_source.async_resolve_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{actual_event_id}"
|
|
|
|
)
|
|
|
|
assert media.url == f"/api/nest/event_media/{actual_event_id}"
|
|
|
|
assert media.mime_type == "video/mp4"
|
|
|
|
|
|
|
|
auth.responses = [
|
|
|
|
aiohttp.web.Response(body=IMAGE_BYTES_FROM_EVENT),
|
|
|
|
]
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get(media.url)
|
|
|
|
assert response.status == HTTPStatus.OK, "Response not matched: %s" % response
|
|
|
|
contents = await response.read()
|
|
|
|
assert contents == IMAGE_BYTES_FROM_EVENT
|
|
|
|
|
|
|
|
|
|
|
|
async def test_event_media_render_invalid_device_id(hass, auth, hass_client):
|
|
|
|
"""Test event media API called with an invalid device id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get("/api/nest/event_media/invalid-device-id")
|
|
|
|
assert response.status == HTTPStatus.NOT_FOUND, (
|
|
|
|
"Response not matched: %s" % response
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_event_media_render_invalid_event_id(hass, auth, hass_client):
|
|
|
|
"""Test event media API called with an invalid device id."""
|
|
|
|
await async_setup_devices(hass, auth, CAMERA_DEVICE_TYPE, CAMERA_TRAITS)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get("/api/nest/event_media/{device.id}/invalid-event-id")
|
|
|
|
assert response.status == HTTPStatus.NOT_FOUND, (
|
|
|
|
"Response not matched: %s" % response
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_event_media_failure(hass, auth, hass_client):
|
|
|
|
"""Test event media fetch sees a failure from the server."""
|
|
|
|
event_timestamp = dt_util.now()
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
CAMERA_DEVICE_TYPE,
|
|
|
|
CAMERA_TRAITS,
|
|
|
|
events=[
|
|
|
|
create_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
EVENT_SESSION_ID,
|
|
|
|
EVENT_ID,
|
2021-12-03 18:53:05 +00:00
|
|
|
PERSON_EVENT,
|
|
|
|
timestamp=event_timestamp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
|
|
|
# Resolving the event links to the media
|
|
|
|
media = await media_source.async_resolve_media(
|
2021-12-06 07:59:24 +00:00
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
)
|
2021-12-06 07:59:24 +00:00
|
|
|
assert media.url == f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
assert media.mime_type == "image/jpeg"
|
|
|
|
|
|
|
|
auth.responses = [
|
|
|
|
aiohttp.web.Response(status=HTTPStatus.INTERNAL_SERVER_ERROR),
|
|
|
|
]
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get(media.url)
|
|
|
|
assert response.status == HTTPStatus.INTERNAL_SERVER_ERROR, (
|
|
|
|
"Response not matched: %s" % response
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
async def test_media_permission_unauthorized(hass, auth, hass_client, hass_admin_user):
|
|
|
|
"""Test case where user does not have permissions to view media."""
|
|
|
|
event_timestamp = dt_util.now()
|
|
|
|
await async_setup_devices(
|
|
|
|
hass,
|
|
|
|
auth,
|
|
|
|
CAMERA_DEVICE_TYPE,
|
|
|
|
CAMERA_TRAITS,
|
|
|
|
events=[
|
|
|
|
create_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
EVENT_SESSION_ID,
|
|
|
|
EVENT_ID,
|
2021-12-03 18:53:05 +00:00
|
|
|
PERSON_EVENT,
|
|
|
|
timestamp=event_timestamp,
|
|
|
|
),
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
assert len(hass.states.async_all()) == 1
|
|
|
|
camera = hass.states.get("camera.front")
|
|
|
|
assert camera is not None
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device = device_registry.async_get_device({(DOMAIN, DEVICE_ID)})
|
|
|
|
assert device
|
|
|
|
assert device.name == DEVICE_NAME
|
|
|
|
|
2021-12-06 07:59:24 +00:00
|
|
|
media_url = f"/api/nest/event_media/{device.id}/{EVENT_SESSION_ID}"
|
2021-12-03 18:53:05 +00:00
|
|
|
|
|
|
|
# Empty policy with no access to the entity
|
|
|
|
hass_admin_user.mock_policy({})
|
|
|
|
|
|
|
|
client = await hass_client()
|
|
|
|
response = await client.get(media_url)
|
|
|
|
assert response.status == HTTPStatus.UNAUTHORIZED, (
|
|
|
|
"Response not matched: %s" % response
|
|
|
|
)
|
2021-12-05 21:02:37 +00:00
|
|
|
|
|
|
|
|
|
|
|
async def test_multiple_devices(hass, auth, hass_client):
|
|
|
|
"""Test events received for multiple devices."""
|
|
|
|
device_id1 = f"{DEVICE_ID}-1"
|
|
|
|
device_id2 = f"{DEVICE_ID}-2"
|
|
|
|
|
|
|
|
devices = {
|
|
|
|
device_id1: Device.MakeDevice(
|
|
|
|
{
|
|
|
|
"name": device_id1,
|
|
|
|
"type": CAMERA_DEVICE_TYPE,
|
|
|
|
"traits": CAMERA_TRAITS,
|
|
|
|
},
|
|
|
|
auth=auth,
|
|
|
|
),
|
|
|
|
device_id2: Device.MakeDevice(
|
|
|
|
{
|
|
|
|
"name": device_id2,
|
|
|
|
"type": CAMERA_DEVICE_TYPE,
|
|
|
|
"traits": CAMERA_TRAITS,
|
|
|
|
},
|
|
|
|
auth=auth,
|
|
|
|
),
|
|
|
|
}
|
|
|
|
subscriber = await async_setup_sdm_platform(hass, PLATFORM, devices=devices)
|
|
|
|
|
|
|
|
device_registry = dr.async_get(hass)
|
|
|
|
device1 = device_registry.async_get_device({(DOMAIN, device_id1)})
|
|
|
|
assert device1
|
|
|
|
device2 = device_registry.async_get_device({(DOMAIN, device_id2)})
|
|
|
|
assert device2
|
|
|
|
|
|
|
|
# Very no events have been received yet
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device1.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 0
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device2.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 0
|
|
|
|
|
|
|
|
# Send events for device #1
|
|
|
|
for i in range(0, 5):
|
|
|
|
await subscriber.async_receive_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
create_event(
|
|
|
|
f"event-session-id-{i}",
|
|
|
|
f"event-id-{i}",
|
|
|
|
PERSON_EVENT,
|
|
|
|
device_id=device_id1,
|
|
|
|
)
|
2021-12-05 21:02:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device1.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 5
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device2.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 0
|
|
|
|
|
|
|
|
# Send events for device #2
|
|
|
|
for i in range(0, 3):
|
|
|
|
await subscriber.async_receive_event(
|
2021-12-06 07:59:24 +00:00
|
|
|
create_event(
|
|
|
|
f"other-id-{i}", f"event-id{i}", PERSON_EVENT, device_id=device_id2
|
|
|
|
)
|
2021-12-05 21:02:37 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device1.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 5
|
|
|
|
browse = await media_source.async_browse_media(
|
|
|
|
hass, f"{const.URI_SCHEME}{DOMAIN}/{device2.id}"
|
|
|
|
)
|
|
|
|
assert len(browse.children) == 3
|