mirror of https://github.com/home-assistant/core
378 lines
12 KiB
Python
378 lines
12 KiB
Python
"""Tests for the Jellyfin media_player platform."""
|
|
|
|
from datetime import timedelta
|
|
from unittest.mock import MagicMock
|
|
|
|
from homeassistant.components.jellyfin.const import DOMAIN
|
|
from homeassistant.components.media_player import (
|
|
ATTR_MEDIA_ALBUM_ARTIST,
|
|
ATTR_MEDIA_ALBUM_NAME,
|
|
ATTR_MEDIA_ARTIST,
|
|
ATTR_MEDIA_CONTENT_ID,
|
|
ATTR_MEDIA_CONTENT_TYPE,
|
|
ATTR_MEDIA_DURATION,
|
|
ATTR_MEDIA_EPISODE,
|
|
ATTR_MEDIA_POSITION,
|
|
ATTR_MEDIA_POSITION_UPDATED_AT,
|
|
ATTR_MEDIA_SEASON,
|
|
ATTR_MEDIA_SERIES_TITLE,
|
|
ATTR_MEDIA_TRACK,
|
|
ATTR_MEDIA_VOLUME_LEVEL,
|
|
ATTR_MEDIA_VOLUME_MUTED,
|
|
DOMAIN as MP_DOMAIN,
|
|
MediaClass,
|
|
MediaPlayerState,
|
|
MediaType,
|
|
)
|
|
from homeassistant.const import (
|
|
ATTR_DEVICE_CLASS,
|
|
ATTR_ENTITY_ID,
|
|
ATTR_FRIENDLY_NAME,
|
|
ATTR_ICON,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
|
from homeassistant.util.dt import utcnow
|
|
|
|
from . import async_load_json_fixture
|
|
|
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
|
from tests.typing import WebSocketGenerator
|
|
|
|
|
|
async def test_media_player(
|
|
hass: HomeAssistant,
|
|
device_registry: dr.DeviceRegistry,
|
|
entity_registry: er.EntityRegistry,
|
|
init_integration: MockConfigEntry,
|
|
mock_jellyfin: MagicMock,
|
|
mock_api: MagicMock,
|
|
) -> None:
|
|
"""Test the Jellyfin media player."""
|
|
state = hass.states.get("media_player.jellyfin_device")
|
|
|
|
assert state
|
|
assert state.state == MediaPlayerState.PAUSED
|
|
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN-DEVICE"
|
|
assert state.attributes.get(ATTR_ICON) is None
|
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 0.0
|
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is True
|
|
assert state.attributes.get(ATTR_MEDIA_DURATION) == 60
|
|
assert state.attributes.get(ATTR_MEDIA_POSITION) == 10
|
|
assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
|
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "EPISODE-UUID"
|
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.TVSHOW
|
|
assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) == "SERIES"
|
|
assert state.attributes.get(ATTR_MEDIA_SEASON) == 1
|
|
assert state.attributes.get(ATTR_MEDIA_EPISODE) == 3
|
|
|
|
entry = entity_registry.async_get(state.entity_id)
|
|
assert entry
|
|
assert entry.device_id
|
|
assert entry.entity_category is None
|
|
assert entry.unique_id == "SERVER-UUID-SESSION-UUID"
|
|
|
|
assert len(mock_api.sessions.mock_calls) == 1
|
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
|
|
await hass.async_block_till_done()
|
|
assert len(mock_api.sessions.mock_calls) == 2
|
|
|
|
mock_api.sessions.return_value = []
|
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=20))
|
|
await hass.async_block_till_done()
|
|
assert len(mock_api.sessions.mock_calls) == 3
|
|
|
|
device = device_registry.async_get(entry.device_id)
|
|
assert device
|
|
assert device.configuration_url is None
|
|
assert device.connections == set()
|
|
assert device.entry_type is None
|
|
assert device.hw_version is None
|
|
assert device.identifiers == {(DOMAIN, "DEVICE-UUID")}
|
|
assert device.manufacturer == "Jellyfin"
|
|
assert device.name == "JELLYFIN-DEVICE"
|
|
assert device.sw_version == "1.0.0"
|
|
|
|
|
|
async def test_media_player_music(
|
|
hass: HomeAssistant,
|
|
entity_registry: er.EntityRegistry,
|
|
init_integration: MockConfigEntry,
|
|
mock_jellyfin: MagicMock,
|
|
mock_api: MagicMock,
|
|
) -> None:
|
|
"""Test the Jellyfin media player."""
|
|
state = hass.states.get("media_player.jellyfin_device_four")
|
|
|
|
assert state
|
|
assert state.state == MediaPlayerState.PLAYING
|
|
assert state.attributes.get(ATTR_DEVICE_CLASS) is None
|
|
assert state.attributes.get(ATTR_FRIENDLY_NAME) == "JELLYFIN DEVICE FOUR"
|
|
assert state.attributes.get(ATTR_ICON) is None
|
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_LEVEL) == 1.0
|
|
assert state.attributes.get(ATTR_MEDIA_VOLUME_MUTED) is False
|
|
assert state.attributes.get(ATTR_MEDIA_DURATION) == 73
|
|
assert state.attributes.get(ATTR_MEDIA_POSITION) == 22
|
|
assert state.attributes.get(ATTR_MEDIA_POSITION_UPDATED_AT)
|
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_ID) == "MUSIC-UUID"
|
|
assert state.attributes.get(ATTR_MEDIA_CONTENT_TYPE) == MediaType.MUSIC
|
|
assert state.attributes.get(ATTR_MEDIA_ALBUM_NAME) == "ALBUM"
|
|
assert state.attributes.get(ATTR_MEDIA_ALBUM_ARTIST) == "Album Artist"
|
|
assert state.attributes.get(ATTR_MEDIA_ARTIST) == "Contributing Artist"
|
|
assert state.attributes.get(ATTR_MEDIA_TRACK) == 1
|
|
assert state.attributes.get(ATTR_MEDIA_SERIES_TITLE) is None
|
|
assert state.attributes.get(ATTR_MEDIA_SEASON) is None
|
|
assert state.attributes.get(ATTR_MEDIA_EPISODE) is None
|
|
|
|
entry = entity_registry.async_get(state.entity_id)
|
|
assert entry
|
|
assert entry.device_id is None
|
|
assert entry.entity_category is None
|
|
assert entry.unique_id == "SERVER-UUID-SESSION-UUID-FOUR"
|
|
|
|
|
|
async def test_services(
|
|
hass: HomeAssistant,
|
|
init_integration: MockConfigEntry,
|
|
mock_jellyfin: MagicMock,
|
|
mock_api: MagicMock,
|
|
) -> None:
|
|
"""Test Jellyfin media player services."""
|
|
state = hass.states.get("media_player.jellyfin_device")
|
|
assert state
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"play_media",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
"media_content_type": "",
|
|
"media_content_id": "ITEM-UUID",
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_play_media.mock_calls) == 1
|
|
assert mock_api.remote_play_media.mock_calls[0].args == (
|
|
"SESSION-UUID",
|
|
["ITEM-UUID"],
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"media_pause",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_pause.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"media_play",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_unpause.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"media_play_pause",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_playpause.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"media_seek",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
"seek_position": 10,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_seek.mock_calls) == 1
|
|
assert mock_api.remote_seek.mock_calls[0].args == (
|
|
"SESSION-UUID",
|
|
100000000,
|
|
)
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"media_stop",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_stop.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"volume_set",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
"volume_level": 0.5,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_set_volume.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"volume_mute",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
"is_volume_muted": True,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_mute.mock_calls) == 1
|
|
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
"volume_mute",
|
|
{
|
|
ATTR_ENTITY_ID: state.entity_id,
|
|
"is_volume_muted": False,
|
|
},
|
|
blocking=True,
|
|
)
|
|
assert len(mock_api.remote_unmute.mock_calls) == 1
|
|
|
|
|
|
async def test_browse_media(
|
|
hass: HomeAssistant,
|
|
hass_ws_client: WebSocketGenerator,
|
|
init_integration: MockConfigEntry,
|
|
mock_jellyfin: MagicMock,
|
|
mock_api: MagicMock,
|
|
) -> None:
|
|
"""Test Jellyfin browse media."""
|
|
client = await hass_ws_client()
|
|
|
|
# browse root folder
|
|
await client.send_json(
|
|
{
|
|
"id": 1,
|
|
"type": "media_player/browse_media",
|
|
"entity_id": "media_player.jellyfin_device",
|
|
}
|
|
)
|
|
response = await client.receive_json()
|
|
assert response["success"]
|
|
expected_child_item = {
|
|
"title": "COLLECTION FOLDER",
|
|
"media_class": MediaClass.DIRECTORY.value,
|
|
"media_content_type": "collection",
|
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
|
"can_play": False,
|
|
"can_expand": True,
|
|
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
|
"children_media_class": None,
|
|
}
|
|
|
|
assert response["result"]["media_content_id"] == ""
|
|
assert response["result"]["media_content_type"] == "root"
|
|
assert response["result"]["title"] == "Jellyfin"
|
|
assert response["result"]["children"][0] == expected_child_item
|
|
|
|
# browse collection folder
|
|
await client.send_json(
|
|
{
|
|
"id": 2,
|
|
"type": "media_player/browse_media",
|
|
"entity_id": "media_player.jellyfin_device",
|
|
"media_content_type": "collection",
|
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
|
}
|
|
)
|
|
|
|
response = await client.receive_json()
|
|
expected_child_item = {
|
|
"title": "EPISODE",
|
|
"media_class": MediaClass.EPISODE.value,
|
|
"media_content_type": MediaType.EPISODE.value,
|
|
"media_content_id": "EPISODE-UUID",
|
|
"can_play": True,
|
|
"can_expand": False,
|
|
"thumbnail": "http://localhost/Items/c22fd826-17fc-44f4-9b04-1eb3e8fb9173/Images/Backdrop.jpg",
|
|
"children_media_class": None,
|
|
}
|
|
|
|
assert response["success"]
|
|
assert response["result"]["media_content_id"] == "COLLECTION-FOLDER-UUID"
|
|
assert response["result"]["title"] == "FOLDER"
|
|
assert response["result"]["children"][0] == expected_child_item
|
|
|
|
# browse for collection without children
|
|
mock_api.user_items.side_effect = None
|
|
mock_api.user_items.return_value = {}
|
|
|
|
await client.send_json(
|
|
{
|
|
"id": 3,
|
|
"type": "media_player/browse_media",
|
|
"entity_id": "media_player.jellyfin_device",
|
|
"media_content_type": "collection",
|
|
"media_content_id": "COLLECTION-FOLDER-UUID",
|
|
}
|
|
)
|
|
|
|
response = await client.receive_json()
|
|
assert response["success"] is False
|
|
assert response["error"]
|
|
assert (
|
|
response["error"]["message"]
|
|
== "Media not found: collection / COLLECTION-FOLDER-UUID"
|
|
)
|
|
|
|
# browse for non-existent item
|
|
mock_api.get_item.side_effect = None
|
|
mock_api.get_item.return_value = {}
|
|
|
|
await client.send_json(
|
|
{
|
|
"id": 4,
|
|
"type": "media_player/browse_media",
|
|
"entity_id": "media_player.jellyfin_device",
|
|
"media_content_type": "collection",
|
|
"media_content_id": "COLLECTION-UUID-404",
|
|
}
|
|
)
|
|
|
|
response = await client.receive_json()
|
|
assert response["success"] is False
|
|
assert response["error"]
|
|
assert (
|
|
response["error"]["message"]
|
|
== "Media not found: collection / COLLECTION-UUID-404"
|
|
)
|
|
|
|
|
|
async def test_new_client_connected(
|
|
hass: HomeAssistant,
|
|
init_integration: MockConfigEntry,
|
|
mock_jellyfin: MagicMock,
|
|
mock_api: MagicMock,
|
|
) -> None:
|
|
"""Test Jellyfin media player reacts to new clients connecting."""
|
|
mock_api.sessions.return_value = await async_load_json_fixture(
|
|
hass,
|
|
"sessions-new-client.json",
|
|
)
|
|
|
|
assert len(mock_api.sessions.mock_calls) == 1
|
|
async_fire_time_changed(hass, utcnow() + timedelta(seconds=10))
|
|
await hass.async_block_till_done()
|
|
assert len(mock_api.sessions.mock_calls) == 2
|
|
|
|
state = hass.states.get("media_player.jellyfin_device_five")
|
|
assert state
|