Add test foundation to Music Assistant integration (#129534)

This commit is contained in:
Marcel van der Veldt 2024-11-22 20:09:20 +01:00 committed by GitHub
parent ecb945e08c
commit 49eeb2d99e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1076 additions and 3 deletions

View File

@ -152,6 +152,8 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
self._attr_supported_features = SUPPORTED_FEATURES
if PlayerFeature.SYNC in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.GROUPING
if PlayerFeature.VOLUME_MUTE in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE
self._attr_device_class = MediaPlayerDeviceClass.SPEAKER
self._prev_time: float = 0
@ -219,7 +221,9 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
)
)
]
self._attr_group_members = group_members_entity_ids
# NOTE: we sort the group_members for now,
# until the MA API returns them sorted (group_childs is now a set)
self._attr_group_members = sorted(group_members_entity_ids)
self._attr_volume_level = (
player.volume_level / 100 if player.volume_level is not None else None
)

View File

@ -0,0 +1,91 @@
"""Provide common test tools."""
from __future__ import annotations
from typing import Any
from unittest.mock import MagicMock
from music_assistant_models.enums import EventType
from music_assistant_models.player import Player
from music_assistant_models.player_queue import PlayerQueue
from syrupy import SnapshotAssertion
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry, load_json_object_fixture
MASS_DOMAIN = "music_assistant"
MOCK_URL = "http://mock-music_assistant-server-url"
def load_and_parse_fixture(fixture: str) -> dict[str, Any]:
"""Load and parse a fixture."""
data = load_json_object_fixture(f"music_assistant/{fixture}.json")
return data[fixture]
async def setup_integration_from_fixtures(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Set up MusicAssistant integration with fixture data."""
players = create_players_from_fixture()
music_assistant_client.players._players = {x.player_id: x for x in players}
player_queues = create_player_queues_from_fixture()
music_assistant_client.player_queues._queues = {
x.queue_id: x for x in player_queues
}
config_entry = MockConfigEntry(
domain=MASS_DOMAIN,
data={"url": MOCK_URL},
unique_id=music_assistant_client.server_info.server_id,
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
def create_players_from_fixture() -> list[Player]:
"""Create MA Players from fixture."""
fixture_data = load_and_parse_fixture("players")
return [Player.from_dict(player_data) for player_data in fixture_data]
def create_player_queues_from_fixture() -> list[Player]:
"""Create MA PlayerQueues from fixture."""
fixture_data = load_and_parse_fixture("player_queues")
return [
PlayerQueue.from_dict(player_queue_data) for player_queue_data in fixture_data
]
async def trigger_subscription_callback(
hass: HomeAssistant,
client: MagicMock,
event: EventType = EventType.PLAYER_UPDATED,
data: Any = None,
) -> None:
"""Trigger a subscription callback."""
# trigger callback on all subscribers
for sub in client.subscribe_events.call_args_list:
callback = sub.kwargs["callback"]
event_filter = sub.kwargs.get("event_filter")
if event_filter in (None, event):
callback(event, data)
await hass.async_block_till_done()
def snapshot_music_assistant_entities(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
platform: Platform,
) -> None:
"""Snapshot MusicAssistant entities."""
entities = hass.states.async_all(platform)
for entity_state in entities:
entity_entry = entity_registry.async_get(entity_state.entity_id)
assert entity_entry == snapshot(name=f"{entity_entry.entity_id}-entry")
assert entity_state == snapshot(name=f"{entity_entry.entity_id}-state")

View File

@ -1,8 +1,12 @@
"""Music Assistant test fixtures."""
from collections.abc import Generator
from unittest.mock import patch
import asyncio
from collections.abc import AsyncGenerator, Generator
from unittest.mock import MagicMock, patch
from music_assistant_client.music import Music
from music_assistant_client.player_queues import PlayerQueues
from music_assistant_client.players import Players
from music_assistant_models.api import ServerInfoMessage
import pytest
@ -11,6 +15,8 @@ from homeassistant.components.music_assistant.const import DOMAIN
from tests.common import AsyncMock, MockConfigEntry, load_fixture
MOCK_SERVER_ID = "1234"
@pytest.fixture
def mock_get_server_info() -> Generator[AsyncMock]:
@ -24,6 +30,48 @@ def mock_get_server_info() -> Generator[AsyncMock]:
yield mock_get_server_info
@pytest.fixture(name="music_assistant_client")
async def music_assistant_client_fixture() -> AsyncGenerator[MagicMock]:
"""Fixture for a Music Assistant client."""
with patch(
"homeassistant.components.music_assistant.MusicAssistantClient", autospec=True
) as client_class:
client = client_class.return_value
async def connect() -> None:
"""Mock connect."""
await asyncio.sleep(0)
async def listen(init_ready: asyncio.Event | None) -> None:
"""Mock listen."""
if init_ready is not None:
init_ready.set()
listen_block = asyncio.Event()
await listen_block.wait()
pytest.fail("Listen was not cancelled!")
client.connect = AsyncMock(side_effect=connect)
client.start_listening = AsyncMock(side_effect=listen)
client.server_info = ServerInfoMessage(
server_id=MOCK_SERVER_ID,
server_version="0.0.0",
schema_version=1,
min_supported_schema_version=1,
base_url="http://localhost:8095",
homeassistant_addon=False,
onboard_done=True,
)
client.connection = MagicMock()
client.connection.connected = True
client.players = Players(client)
client.player_queues = PlayerQueues(client)
client.music = Music(client)
client.server_url = client.server_info.base_url
client.get_media_item_image_url = MagicMock(return_value=None)
yield client
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock a config entry."""

View File

@ -0,0 +1,328 @@
{
"player_queues": [
{
"queue_id": "00:00:00:00:00:01",
"active": false,
"display_name": "Test Player 1",
"available": true,
"items": 0,
"shuffle_enabled": false,
"repeat_mode": "off",
"dont_stop_the_music_enabled": false,
"current_index": null,
"index_in_buffer": null,
"elapsed_time": 0,
"elapsed_time_last_updated": 1730118302.163217,
"state": "idle",
"current_item": null,
"next_item": null,
"radio_source": [],
"flow_mode": false,
"resume_pos": 0
},
{
"queue_id": "00:00:00:00:00:02",
"active": false,
"display_name": "My Super Test Player 2",
"available": true,
"items": 0,
"shuffle_enabled": false,
"repeat_mode": "off",
"dont_stop_the_music_enabled": false,
"current_index": null,
"index_in_buffer": null,
"elapsed_time": 0,
"elapsed_time_last_updated": 0,
"state": "idle",
"current_item": null,
"next_item": null,
"radio_source": [],
"flow_mode": false,
"resume_pos": 0
},
{
"queue_id": "test_group_player_1",
"active": true,
"display_name": "Test Group Player 1",
"available": true,
"items": 1094,
"shuffle_enabled": true,
"repeat_mode": "all",
"dont_stop_the_music_enabled": true,
"current_index": 26,
"index_in_buffer": 26,
"elapsed_time": 232.08810877799988,
"elapsed_time_last_updated": 1730313109.5659513,
"state": "playing",
"current_item": {
"queue_id": "test_group_player_1",
"queue_item_id": "5d95dc5be77e4f7eb4939f62cfef527b",
"name": "Guns N' Roses - November Rain",
"duration": 536,
"sort_index": 2109,
"streamdetails": {
"provider": "spotify",
"item_id": "3YRCqOhFifThpSRFJ1VWFM",
"audio_format": {
"content_type": "ogg",
"sample_rate": 44100,
"bit_depth": 16,
"channels": 2,
"output_format_str": "ogg",
"bit_rate": 0
},
"media_type": "track",
"stream_type": "custom",
"stream_title": null,
"duration": 536,
"size": null,
"can_seek": true,
"loudness": -12.47,
"loudness_album": null,
"prefer_album_loudness": false,
"volume_normalization_mode": "fallback_dynamic",
"target_loudness": -17,
"strip_silence_begin": false,
"strip_silence_end": true,
"stream_error": null
},
"media_item": {
"item_id": "3YRCqOhFifThpSRFJ1VWFM",
"provider": "spotify",
"name": "November Rain",
"version": "",
"sort_name": "november rain",
"uri": "spotify://track/3YRCqOhFifThpSRFJ1VWFM",
"external_ids": [["isrc", "USGF19141510"]],
"media_type": "track",
"provider_mappings": [
{
"item_id": "3YRCqOhFifThpSRFJ1VWFM",
"provider_domain": "spotify",
"provider_instance": "spotify",
"available": true,
"audio_format": {
"content_type": "ogg",
"sample_rate": 44100,
"bit_depth": 16,
"channels": 2,
"output_format_str": "ogg",
"bit_rate": 320
},
"url": "https://open.spotify.com/track/3YRCqOhFifThpSRFJ1VWFM",
"details": null
}
],
"metadata": {
"description": null,
"review": null,
"explicit": false,
"images": [
{
"type": "thumb",
"path": "https://i.scdn.co/image/ab67616d0000b273e44963b8bb127552ac761873",
"provider": "spotify",
"remotely_accessible": true
}
],
"genres": null,
"mood": null,
"style": null,
"copyright": null,
"lyrics": null,
"label": null,
"links": null,
"chapters": null,
"performers": null,
"preview": "https://p.scdn.co/mp3-preview/98deb9c370bbaa350be058b3470fbe3bc1e28d9d?cid=2eb96f9b37494be1824999d58028a305",
"popularity": 77,
"last_refresh": null
},
"favorite": false,
"position": 1372,
"duration": 536,
"artists": [
{
"item_id": "3qm84nBOXUEQ2vnTfUTTFC",
"provider": "spotify",
"name": "Guns N' Roses",
"version": "",
"sort_name": "guns n' roses",
"uri": "spotify://artist/3qm84nBOXUEQ2vnTfUTTFC",
"external_ids": [],
"media_type": "artist",
"available": true,
"image": null
}
],
"album": {
"item_id": "0CxPbTRARqKUYighiEY9Sz",
"provider": "spotify",
"name": "Use Your Illusion I",
"version": "",
"sort_name": "use your illusion i",
"uri": "spotify://album/0CxPbTRARqKUYighiEY9Sz",
"external_ids": [],
"media_type": "album",
"available": true,
"image": {
"type": "thumb",
"path": "https://i.scdn.co/image/ab67616d0000b273e44963b8bb127552ac761873",
"provider": "spotify",
"remotely_accessible": true
}
},
"disc_number": 1,
"track_number": 10
},
"image": {
"type": "thumb",
"path": "https://i.scdn.co/image/ab67616d0000b273e44963b8bb127552ac761873",
"provider": "spotify",
"remotely_accessible": true
},
"index": 0
},
"next_item": {
"queue_id": "test_group_player_1",
"queue_item_id": "990ae8f29cdf4fb588d679b115621f55",
"name": "The Stranglers - Golden Brown",
"duration": 207,
"sort_index": 1138,
"streamdetails": {
"provider": "qobuz",
"item_id": "1004735",
"audio_format": {
"content_type": "flac",
"sample_rate": 44100,
"bit_depth": 16,
"channels": 2,
"output_format_str": "flac",
"bit_rate": 0
},
"media_type": "track",
"stream_type": "http",
"stream_title": null,
"duration": 207,
"size": null,
"can_seek": true,
"loudness": -14.23,
"loudness_album": null,
"prefer_album_loudness": true,
"volume_normalization_mode": "fallback_dynamic",
"target_loudness": -17,
"strip_silence_begin": true,
"strip_silence_end": true,
"stream_error": null
},
"media_item": {
"item_id": "1004735",
"provider": "qobuz",
"name": "Golden Brown",
"version": "",
"sort_name": "golden brown",
"uri": "qobuz://track/1004735",
"external_ids": [["isrc", "GBAYE8100053"]],
"media_type": "track",
"provider_mappings": [
{
"item_id": "1004735",
"provider_domain": "qobuz",
"provider_instance": "qobuz",
"available": true,
"audio_format": {
"content_type": "flac",
"sample_rate": 44100,
"bit_depth": 16,
"channels": 2,
"output_format_str": "flac",
"bit_rate": 0
},
"url": "https://open.qobuz.com/track/1004735",
"details": null
}
],
"metadata": {
"description": null,
"review": null,
"explicit": null,
"images": [
{
"type": "thumb",
"path": "https://static.qobuz.com/images/covers/59/88/0724353468859_600.jpg",
"provider": "qobuz",
"remotely_accessible": true
}
],
"genres": null,
"mood": null,
"style": null,
"copyright": "© 2001 Parlophone Records Ltd, a Warner Music Group Company ℗ 1981 Parlophone Records Ltd, a Warner Music Group Company",
"lyrics": null,
"label": null,
"links": null,
"chapters": null,
"performers": [
"Dave Greenfield, Composer, Producer, Keyboards, Vocals",
"Jean",
"Hugh Cornwell, Composer, Producer, Guitar, Vocals",
"Jean Jacques Burnel, Producer, Bass Guitar, Vocals",
"Jet Black, Composer, Producer, Drums, Percussion",
"Jacques Burnell, Composer",
"The Stranglers, MainArtist"
],
"preview": null,
"popularity": null,
"last_refresh": null
},
"favorite": false,
"position": 183,
"duration": 207,
"artists": [
{
"item_id": "26779",
"provider": "qobuz",
"name": "The Stranglers",
"version": "",
"sort_name": "stranglers, the",
"uri": "qobuz://artist/26779",
"external_ids": [],
"media_type": "artist",
"available": true,
"image": null
}
],
"album": {
"item_id": "0724353468859",
"provider": "qobuz",
"name": "La Folie",
"version": "",
"sort_name": "folie, la",
"uri": "qobuz://album/0724353468859",
"external_ids": [["barcode", "0724353468859"]],
"media_type": "album",
"available": true,
"image": {
"type": "thumb",
"path": "https://static.qobuz.com/images/covers/59/88/0724353468859_600.jpg",
"provider": "qobuz",
"remotely_accessible": true
}
},
"disc_number": 1,
"track_number": 9
},
"image": {
"type": "thumb",
"path": "https://static.qobuz.com/images/covers/59/88/0724353468859_600.jpg",
"provider": "qobuz",
"remotely_accessible": true
},
"index": 0
},
"radio_source": [],
"flow_mode": false,
"resume_pos": 0
}
]
}

View File

@ -0,0 +1,149 @@
{
"players": [
{
"player_id": "00:00:00:00:00:01",
"provider": "test",
"type": "player",
"name": "Test Player 1",
"available": true,
"powered": false,
"device_info": {
"model": "Test Model",
"address": "192.168.1.1",
"manufacturer": "Test Manufacturer"
},
"supported_features": [
"volume_set",
"volume_mute",
"pause",
"sync",
"power",
"enqueue"
],
"elapsed_time": 0,
"elapsed_time_last_updated": 0,
"state": "idle",
"volume_level": 20,
"volume_muted": false,
"group_childs": [],
"active_source": "00:00:00:00:00:01",
"active_group": null,
"current_media": null,
"synced_to": null,
"enabled_by_default": true,
"needs_poll": false,
"poll_interval": 30,
"enabled": true,
"hidden": false,
"icon": "mdi-speaker",
"group_volume": 20,
"display_name": "Test Player 1",
"extra_data": {},
"announcement_in_progress": false
},
{
"player_id": "00:00:00:00:00:02",
"provider": "test",
"type": "player",
"name": "Test Player 2",
"available": true,
"powered": true,
"device_info": {
"model": "Test Model",
"address": "192.168.1.2",
"manufacturer": "Test Manufacturer"
},
"supported_features": [
"volume_set",
"volume_mute",
"pause",
"sync",
"power",
"enqueue"
],
"elapsed_time": 0,
"elapsed_time_last_updated": 0,
"state": "playing",
"volume_level": 20,
"volume_muted": false,
"group_childs": [],
"active_source": "spotify",
"active_group": null,
"current_media": {
"uri": "spotify://track/5d95dc5be77e4f7eb4939f62cfef527b",
"media_type": "track",
"title": "Test Track",
"artist": "Test Artist",
"album": "Test Album",
"image_url": null,
"duration": 300,
"queue_id": null,
"queue_item_id": null,
"custom_data": null
},
"synced_to": null,
"enabled_by_default": true,
"needs_poll": false,
"poll_interval": 30,
"enabled": true,
"hidden": false,
"icon": "mdi-speaker",
"group_volume": 20,
"display_name": "My Super Test Player 2",
"extra_data": {},
"announcement_in_progress": false
},
{
"player_id": "test_group_player_1",
"provider": "player_group",
"type": "group",
"name": "Test Group Player 1",
"available": true,
"powered": true,
"device_info": {
"model": "Sync Group",
"address": "",
"manufacturer": "Test"
},
"supported_features": [
"volume_set",
"volume_mute",
"pause",
"sync",
"power",
"enqueue"
],
"elapsed_time": 0.0,
"elapsed_time_last_updated": 1730315437.9904983,
"state": "idle",
"volume_level": 6,
"volume_muted": false,
"group_childs": ["00:00:00:00:00:01", "00:00:00:00:00:02"],
"active_source": "test_group_player_1",
"active_group": null,
"current_media": {
"uri": "http://192.168.1.1:8097/single/test_group_player_1/5d95dc5be77e4f7eb4939f62cfef527b.flac?ts=1730313038",
"media_type": "unknown",
"title": null,
"artist": null,
"album": null,
"image_url": null,
"duration": null,
"queue_id": "test_group_player_1",
"queue_item_id": "5d95dc5be77e4f7eb4939f62cfef527b",
"custom_data": null
},
"synced_to": null,
"enabled_by_default": true,
"needs_poll": true,
"poll_interval": 30,
"enabled": true,
"hidden": false,
"icon": "mdi-speaker-multiple",
"group_volume": 6,
"display_name": "Test Group Player 1",
"extra_data": {},
"announcement_in_progress": false
}
]
}

View File

@ -0,0 +1,190 @@
# serializer version: 1
# name: test_media_player[media_player.my_super_test_player_2-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.my_super_test_player_2',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': 'mdi:speaker',
'original_name': None,
'platform': 'music_assistant',
'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 4126655>,
'translation_key': None,
'unique_id': '00:00:00:00:00:02',
'unit_of_measurement': None,
})
# ---
# name: test_media_player[media_player.my_super_test_player_2-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_queue': None,
'app_id': 'spotify',
'device_class': 'speaker',
'entity_picture_local': None,
'friendly_name': 'My Super Test Player 2',
'group_members': list([
]),
'icon': 'mdi:speaker',
'is_volume_muted': False,
'mass_player_type': 'player',
'media_album_name': 'Test Album',
'media_artist': 'Test Artist',
'media_content_id': 'spotify://track/5d95dc5be77e4f7eb4939f62cfef527b',
'media_content_type': <MediaType.MUSIC: 'music'>,
'media_duration': 300,
'media_position': 0,
'media_title': 'Test Track',
'supported_features': <MediaPlayerEntityFeature: 4126655>,
'volume_level': 0.2,
}),
'context': <ANY>,
'entity_id': 'media_player.my_super_test_player_2',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'playing',
})
# ---
# name: test_media_player[media_player.test_group_player_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.test_group_player_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': 'mdi:speaker-multiple',
'original_name': None,
'platform': 'music_assistant',
'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 4126655>,
'translation_key': None,
'unique_id': 'test_group_player_1',
'unit_of_measurement': None,
})
# ---
# name: test_media_player[media_player.test_group_player_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_queue': 'test_group_player_1',
'app_id': 'music_assistant',
'device_class': 'speaker',
'entity_picture_local': None,
'friendly_name': 'Test Group Player 1',
'group_members': list([
'media_player.my_super_test_player_2',
'media_player.test_player_1',
]),
'icon': 'mdi:speaker-multiple',
'is_volume_muted': False,
'mass_player_type': 'group',
'media_album_name': 'Use Your Illusion I',
'media_artist': "Guns N' Roses",
'media_content_id': 'spotify://track/3YRCqOhFifThpSRFJ1VWFM',
'media_content_type': <MediaType.MUSIC: 'music'>,
'media_duration': 536,
'media_position': 232,
'media_position_updated_at': datetime.datetime(2024, 10, 30, 18, 31, 49, 565951, tzinfo=datetime.timezone.utc),
'media_title': 'November Rain',
'repeat': 'all',
'shuffle': True,
'supported_features': <MediaPlayerEntityFeature: 4126655>,
'volume_level': 0.06,
}),
'context': <ANY>,
'entity_id': 'media_player.test_group_player_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---
# name: test_media_player[media_player.test_player_1-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.test_player_1',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': 'mdi:speaker',
'original_name': None,
'platform': 'music_assistant',
'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 4126655>,
'translation_key': None,
'unique_id': '00:00:00:00:00:01',
'unit_of_measurement': None,
})
# ---
# name: test_media_player[media_player.test_player_1-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'active_queue': '00:00:00:00:00:01',
'device_class': 'speaker',
'friendly_name': 'Test Player 1',
'group_members': list([
]),
'icon': 'mdi:speaker',
'mass_player_type': 'player',
'supported_features': <MediaPlayerEntityFeature: 4126655>,
}),
'context': <ANY>,
'entity_id': 'media_player.test_player_1',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---

View File

@ -0,0 +1,263 @@
"""Test Music Assistant media player entities."""
from unittest.mock import MagicMock, call
from syrupy import SnapshotAssertion
from homeassistant.components.media_player import (
ATTR_MEDIA_REPEAT,
ATTR_MEDIA_SEEK_POSITION,
ATTR_MEDIA_SHUFFLE,
ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_VOLUME_MUTED,
DOMAIN as MEDIA_PLAYER_DOMAIN,
SERVICE_CLEAR_PLAYLIST,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_STOP,
SERVICE_REPEAT_SET,
SERVICE_SHUFFLE_SET,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE,
SERVICE_VOLUME_SET,
SERVICE_VOLUME_UP,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from .common import setup_integration_from_fixtures, snapshot_music_assistant_entities
async def test_media_player(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
music_assistant_client: MagicMock,
) -> None:
"""Test media player."""
await setup_integration_from_fixtures(hass, music_assistant_client)
snapshot_music_assistant_entities(
hass, entity_registry, snapshot, Platform.MEDIA_PLAYER
)
async def test_media_player_basic_actions(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity basic actions (play/stop/pause etc.)."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
for action, cmd in (
(SERVICE_MEDIA_PLAY, "play"),
(SERVICE_MEDIA_PAUSE, "pause"),
(SERVICE_MEDIA_STOP, "stop"),
(SERVICE_MEDIA_PREVIOUS_TRACK, "previous"),
(SERVICE_MEDIA_NEXT_TRACK, "next"),
(SERVICE_VOLUME_UP, "volume_up"),
(SERVICE_VOLUME_DOWN, "volume_down"),
):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
action,
{
ATTR_ENTITY_ID: entity_id,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
f"players/cmd/{cmd}", player_id=mass_player_id
)
music_assistant_client.send_command.reset_mock()
async def test_media_player_seek_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity seek action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
"media_seek",
{
ATTR_ENTITY_ID: entity_id,
ATTR_MEDIA_SEEK_POSITION: 100,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"players/cmd/seek", player_id=mass_player_id, position=100
)
async def test_media_player_volume_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity volume action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_SET,
{
ATTR_ENTITY_ID: entity_id,
ATTR_MEDIA_VOLUME_LEVEL: 0.5,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"players/cmd/volume_set", player_id=mass_player_id, volume_level=50
)
async def test_media_player_volume_mute_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity volume_mute action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_MUTE,
{
ATTR_ENTITY_ID: entity_id,
ATTR_MEDIA_VOLUME_MUTED: True,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"players/cmd/volume_mute", player_id=mass_player_id, muted=True
)
async def test_media_player_turn_on_off_actions(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity turn_on/turn_off actions."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
for action, pwr in (
(SERVICE_TURN_ON, True),
(SERVICE_TURN_OFF, False),
):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
action,
{
ATTR_ENTITY_ID: entity_id,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"players/cmd/power", player_id=mass_player_id, powered=pwr
)
music_assistant_client.send_command.reset_mock()
async def test_media_player_shuffle_set_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity shuffle_set action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_SHUFFLE_SET,
{
ATTR_ENTITY_ID: entity_id,
ATTR_MEDIA_SHUFFLE: True,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"player_queues/shuffle", queue_id=mass_player_id, shuffle_enabled=True
)
async def test_media_player_repeat_set_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity repeat_set action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_REPEAT_SET,
{
ATTR_ENTITY_ID: entity_id,
ATTR_MEDIA_REPEAT: "one",
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"player_queues/repeat", queue_id=mass_player_id, repeat_mode="one"
)
async def test_media_player_clear_playlist_action(
hass: HomeAssistant,
music_assistant_client: MagicMock,
) -> None:
"""Test media_player entity clear_playlist action."""
await setup_integration_from_fixtures(hass, music_assistant_client)
entity_id = "media_player.test_player_1"
mass_player_id = "00:00:00:00:00:01"
state = hass.states.get(entity_id)
assert state
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_CLEAR_PLAYLIST,
{
ATTR_ENTITY_ID: entity_id,
},
blocking=True,
)
assert music_assistant_client.send_command.call_count == 1
assert music_assistant_client.send_command.call_args == call(
"player_queues/clear", queue_id=mass_player_id
)