core/tests/components/jellyfin/test_media_player.py

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