mirror of https://github.com/home-assistant/core
495 lines
16 KiB
Python
495 lines
16 KiB
Python
"""Test Local Media Source."""
|
|
|
|
import logging
|
|
from unittest.mock import AsyncMock, Mock, call
|
|
|
|
from motioneye_client.client import MotionEyeClientPathError
|
|
import pytest
|
|
|
|
from homeassistant.components.media_source import (
|
|
URI_SCHEME,
|
|
MediaSourceError,
|
|
PlayMedia,
|
|
Unresolvable,
|
|
async_browse_media,
|
|
async_resolve_media,
|
|
)
|
|
from homeassistant.components.motioneye.const import DOMAIN
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import device_registry as dr
|
|
from homeassistant.setup import async_setup_component
|
|
|
|
from . import (
|
|
TEST_CAMERA_DEVICE_IDENTIFIER,
|
|
TEST_CAMERA_ID,
|
|
TEST_CONFIG_ENTRY_ID,
|
|
create_mock_motioneye_client,
|
|
setup_mock_motioneye_config_entry,
|
|
)
|
|
|
|
TEST_MOVIES = {
|
|
"mediaList": [
|
|
{
|
|
"mimeType": "video/mp4",
|
|
"sizeStr": "4.7 MB",
|
|
"momentStrShort": "25 Apr, 00:26",
|
|
"timestamp": 1619335614.0353653,
|
|
"momentStr": "25 April 2021, 00:26",
|
|
"path": "/2021-04-25/00-26-22.mp4",
|
|
},
|
|
{
|
|
"mimeType": "video/mp4",
|
|
"sizeStr": "9.2 MB",
|
|
"momentStrShort": "25 Apr, 00:37",
|
|
"timestamp": 1619336268.0683491,
|
|
"momentStr": "25 April 2021, 00:37",
|
|
"path": "/2021-04-25/00-36-49.mp4",
|
|
},
|
|
{
|
|
"mimeType": "video/mp4",
|
|
"sizeStr": "28.3 MB",
|
|
"momentStrShort": "25 Apr, 00:03",
|
|
"timestamp": 1619334211.0403328,
|
|
"momentStr": "25 April 2021, 00:03",
|
|
"path": "/2021-04-25/00-02-27.mp4",
|
|
},
|
|
]
|
|
}
|
|
|
|
TEST_IMAGES = {
|
|
"mediaList": [
|
|
{
|
|
"mimeType": "image/jpeg",
|
|
"sizeStr": "216.5 kB",
|
|
"momentStrShort": "12 Apr, 20:13",
|
|
"timestamp": 1618283619.6541321,
|
|
"momentStr": "12 April 2021, 20:13",
|
|
"path": "/2021-04-12/20-13-39.jpg",
|
|
}
|
|
],
|
|
}
|
|
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def setup_media_source(hass: HomeAssistant) -> None:
|
|
"""Set up media source."""
|
|
assert await async_setup_component(hass, "media_source", {})
|
|
|
|
|
|
async def test_async_browse_media_success(
|
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
|
) -> None:
|
|
"""Test successful browse media."""
|
|
|
|
client = create_mock_motioneye_client()
|
|
config = await setup_mock_motioneye_config_entry(hass, client=client)
|
|
|
|
device = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={TEST_CAMERA_DEVICE_IDENTIFIER},
|
|
)
|
|
|
|
media = await async_browse_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}",
|
|
)
|
|
|
|
assert media.as_dict() == {
|
|
"title": "motionEye Media",
|
|
"media_class": "directory",
|
|
"media_content_type": "",
|
|
"media_content_id": "media-source://motioneye",
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "directory",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "http://test:8766",
|
|
"media_class": "directory",
|
|
"media_content_type": "",
|
|
"media_content_id": (
|
|
"media-source://motioneye/74565ad414754616000674c87bdc876c"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": None,
|
|
"children_media_class": "directory",
|
|
}
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
media = await async_browse_media(hass, f"{URI_SCHEME}{DOMAIN}/{config.entry_id}")
|
|
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766",
|
|
"media_class": "directory",
|
|
"media_content_type": "",
|
|
"media_content_id": "media-source://motioneye/74565ad414754616000674c87bdc876c",
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "directory",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "Test Camera",
|
|
"media_class": "directory",
|
|
"media_content_type": "",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": None,
|
|
"children_media_class": "directory",
|
|
}
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
media = await async_browse_media(
|
|
hass, f"{URI_SCHEME}{DOMAIN}/{config.entry_id}#{device.id}"
|
|
)
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766 Test Camera",
|
|
"media_class": "directory",
|
|
"media_content_type": "",
|
|
"media_content_id": (
|
|
f"media-source://motioneye/74565ad414754616000674c87bdc876c#{device.id}"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "directory",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "Movies",
|
|
"media_class": "directory",
|
|
"media_content_type": "video",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": None,
|
|
"children_media_class": "video",
|
|
},
|
|
{
|
|
"title": "Images",
|
|
"media_class": "directory",
|
|
"media_content_type": "image",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#images"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": None,
|
|
"children_media_class": "image",
|
|
},
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
client.async_get_movies = AsyncMock(return_value=TEST_MOVIES)
|
|
media = await async_browse_media(
|
|
hass, f"{URI_SCHEME}{DOMAIN}/{config.entry_id}#{device.id}#movies"
|
|
)
|
|
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766 Test Camera Movies",
|
|
"media_class": "directory",
|
|
"media_content_type": "video",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "video",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "2021-04-25",
|
|
"media_class": "directory",
|
|
"media_content_type": "video",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies#/2021-04-25"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": None,
|
|
"children_media_class": "directory",
|
|
}
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
client.get_movie_url = Mock(return_value="http://movie")
|
|
media = await async_browse_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{config.entry_id}#{device.id}#movies#/2021-04-25",
|
|
)
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766 Test Camera Movies 2021-04-25",
|
|
"media_class": "directory",
|
|
"media_content_type": "video",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "video",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "00-02-27.mp4",
|
|
"media_class": "video",
|
|
"media_content_type": "video/mp4",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies#"
|
|
"/2021-04-25/00-02-27.mp4"
|
|
),
|
|
"can_play": True,
|
|
"can_expand": False,
|
|
"thumbnail": "http://movie",
|
|
"children_media_class": None,
|
|
},
|
|
{
|
|
"title": "00-26-22.mp4",
|
|
"media_class": "video",
|
|
"media_content_type": "video/mp4",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies#"
|
|
"/2021-04-25/00-26-22.mp4"
|
|
),
|
|
"can_play": True,
|
|
"can_expand": False,
|
|
"thumbnail": "http://movie",
|
|
"children_media_class": None,
|
|
},
|
|
{
|
|
"title": "00-36-49.mp4",
|
|
"media_class": "video",
|
|
"media_content_type": "video/mp4",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies#"
|
|
"/2021-04-25/00-36-49.mp4"
|
|
),
|
|
"can_play": True,
|
|
"can_expand": False,
|
|
"thumbnail": "http://movie",
|
|
"children_media_class": None,
|
|
},
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
|
|
async def test_async_browse_media_images_success(
|
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
|
) -> None:
|
|
"""Test successful browse media of images."""
|
|
|
|
client = create_mock_motioneye_client()
|
|
config = await setup_mock_motioneye_config_entry(hass, client=client)
|
|
|
|
device = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={TEST_CAMERA_DEVICE_IDENTIFIER},
|
|
)
|
|
|
|
client.async_get_images = AsyncMock(return_value=TEST_IMAGES)
|
|
client.get_image_url = Mock(return_value="http://image")
|
|
|
|
media = await async_browse_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{config.entry_id}#{device.id}#images#/2021-04-12",
|
|
)
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766 Test Camera Images 2021-04-12",
|
|
"media_class": "directory",
|
|
"media_content_type": "image",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#images"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "image",
|
|
"thumbnail": None,
|
|
"children": [
|
|
{
|
|
"title": "20-13-39.jpg",
|
|
"media_class": "image",
|
|
"media_content_type": "image/jpeg",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#images#"
|
|
"/2021-04-12/20-13-39.jpg"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": False,
|
|
"thumbnail": "http://image",
|
|
"children_media_class": None,
|
|
}
|
|
],
|
|
"not_shown": 0,
|
|
}
|
|
|
|
|
|
async def test_async_resolve_media_success(
|
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
|
) -> None:
|
|
"""Test successful resolve media."""
|
|
|
|
client = create_mock_motioneye_client()
|
|
|
|
config = await setup_mock_motioneye_config_entry(hass, client=client)
|
|
|
|
device = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={TEST_CAMERA_DEVICE_IDENTIFIER},
|
|
)
|
|
|
|
# Test successful resolve for a movie.
|
|
client.get_movie_url = Mock(return_value="http://movie-url")
|
|
media = await async_resolve_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#/foo.mp4",
|
|
None,
|
|
)
|
|
assert media == PlayMedia(url="http://movie-url", mime_type="video/mp4")
|
|
assert client.get_movie_url.call_args == call(TEST_CAMERA_ID, "/foo.mp4")
|
|
|
|
# Test successful resolve for an image.
|
|
client.get_image_url = Mock(return_value="http://image-url")
|
|
media = await async_resolve_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#images#/foo.jpg",
|
|
None,
|
|
)
|
|
assert media == PlayMedia(url="http://image-url", mime_type="image/jpeg")
|
|
assert client.get_image_url.call_args == call(TEST_CAMERA_ID, "/foo.jpg")
|
|
|
|
|
|
async def test_async_resolve_media_failure(
|
|
hass: HomeAssistant, device_registry: dr.DeviceRegistry
|
|
) -> None:
|
|
"""Test failed resolve media calls."""
|
|
|
|
client = create_mock_motioneye_client()
|
|
|
|
config = await setup_mock_motioneye_config_entry(hass, client=client)
|
|
|
|
device = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={TEST_CAMERA_DEVICE_IDENTIFIER},
|
|
)
|
|
|
|
broken_device_1 = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={(DOMAIN, config.entry_id)},
|
|
)
|
|
broken_device_2 = device_registry.async_get_or_create(
|
|
config_entry_id=config.entry_id,
|
|
identifiers={(DOMAIN, f"{config.entry_id}_NOTINT")},
|
|
)
|
|
client.get_movie_url = Mock(return_value="http://url")
|
|
|
|
# URI doesn't contain necessary components.
|
|
with pytest.raises(Unresolvable):
|
|
await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/foo", None)
|
|
|
|
# Config entry doesn't exist.
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(hass, f"{URI_SCHEME}{DOMAIN}/1#2#3#4", None)
|
|
|
|
# Device doesn't exist.
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(
|
|
hass, f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#2#3#4", None
|
|
)
|
|
|
|
# Device identifiers are incorrect (no camera id)
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(
|
|
hass,
|
|
(
|
|
f"{URI_SCHEME}{DOMAIN}"
|
|
f"/{TEST_CONFIG_ENTRY_ID}#{broken_device_1.id}#images#4"
|
|
),
|
|
None,
|
|
)
|
|
|
|
# Device identifiers are incorrect (non integer camera id)
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(
|
|
hass,
|
|
(
|
|
f"{URI_SCHEME}{DOMAIN}"
|
|
f"/{TEST_CONFIG_ENTRY_ID}#{broken_device_2.id}#images#4"
|
|
),
|
|
None,
|
|
)
|
|
|
|
# Kind is incorrect.
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#games#moo",
|
|
None,
|
|
)
|
|
|
|
# Playback URL raises exception.
|
|
client.get_movie_url = Mock(side_effect=MotionEyeClientPathError)
|
|
with pytest.raises(Unresolvable):
|
|
await async_resolve_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#/foo.mp4",
|
|
None,
|
|
)
|
|
|
|
# Media path does not start with '/'
|
|
client.get_movie_url = Mock(side_effect=MotionEyeClientPathError)
|
|
with pytest.raises(MediaSourceError):
|
|
await async_resolve_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{TEST_CONFIG_ENTRY_ID}#{device.id}#movies#foo.mp4",
|
|
None,
|
|
)
|
|
|
|
# Media missing path.
|
|
broken_movies = {"mediaList": [{}, {"path": "something", "mimeType": "NOT_A_MIME"}]}
|
|
client.async_get_movies = AsyncMock(return_value=broken_movies)
|
|
media = await async_browse_media(
|
|
hass,
|
|
f"{URI_SCHEME}{DOMAIN}/{config.entry_id}#{device.id}#movies#/2021-04-25",
|
|
)
|
|
assert media.as_dict() == {
|
|
"title": "http://test:8766 Test Camera Movies 2021-04-25",
|
|
"media_class": "directory",
|
|
"media_content_type": "video",
|
|
"media_content_id": (
|
|
"media-source://motioneye"
|
|
f"/74565ad414754616000674c87bdc876c#{device.id}#movies"
|
|
),
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"children_media_class": "video",
|
|
"thumbnail": None,
|
|
"children": [],
|
|
"not_shown": 0,
|
|
}
|