Compare commits
6 commits
dev
...
musicassis
Author | SHA1 | Date | |
---|---|---|---|
|
9b25bf8e09 | ||
|
588661c15f | ||
|
99e9cd507a | ||
|
62caeb1000 | ||
|
fac22c3171 | ||
|
d2ee3ab957 |
7 changed files with 993 additions and 3 deletions
|
@ -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
|
||||
)
|
||||
|
|
86
tests/components/music_assistant/common.py
Normal file
86
tests/components/music_assistant/common.py
Normal file
|
@ -0,0 +1,86 @@
|
|||
"""Provide common test tools."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
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_fixture
|
||||
|
||||
|
||||
def load_and_parse_fixture(fixture: str) -> dict[str, Any]:
|
||||
"""Load and parse a fixture."""
|
||||
return json.loads(load_fixture(f"music_assistant/{fixture}.json"))
|
||||
|
||||
|
||||
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="music_assistant", data={"url": "http://mock-music_assistant-server-url"}
|
||||
)
|
||||
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")
|
|
@ -1,16 +1,23 @@
|
|||
"""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
|
||||
|
||||
from homeassistant.components.music_assistant.config_flow import CONF_URL
|
||||
from homeassistant.components.music_assistant.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from tests.common import AsyncMock, MockConfigEntry, load_fixture
|
||||
|
||||
MOCK_SERVER_ID = "1234"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_server_info() -> Generator[AsyncMock]:
|
||||
|
@ -24,6 +31,60 @@ 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(name="integration")
|
||||
async def integration_fixture(
|
||||
hass: HomeAssistant, music_assistant_client: MagicMock
|
||||
) -> MockConfigEntry:
|
||||
"""Set up the Music Assistant integration."""
|
||||
entry = MockConfigEntry(domain=DOMAIN, data={CONF_URL: "http://localhost:8095"})
|
||||
entry.add_to_hass(hass)
|
||||
await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
return entry
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_config_entry() -> MockConfigEntry:
|
||||
"""Mock a config entry."""
|
||||
|
|
326
tests/components/music_assistant/fixtures/player_queues.json
Normal file
326
tests/components/music_assistant/fixtures/player_queues.json
Normal file
|
@ -0,0 +1,326 @@
|
|||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
147
tests/components/music_assistant/fixtures/players.json
Normal file
147
tests/components/music_assistant/fixtures/players.json
Normal file
|
@ -0,0 +1,147 @@
|
|||
[
|
||||
{
|
||||
"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
|
||||
}
|
||||
]
|
|
@ -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',
|
||||
})
|
||||
# ---
|
176
tests/components/music_assistant/test_media_player.py
Normal file
176
tests/components/music_assistant/test_media_player.py
Normal file
|
@ -0,0 +1,176 @@
|
|||
"""Test Music Assistant media player entities."""
|
||||
|
||||
from unittest.mock import MagicMock, call
|
||||
|
||||
from syrupy import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import 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_actions(
|
||||
hass: HomeAssistant,
|
||||
music_assistant_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test media_player entity 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
|
||||
|
||||
# test basic actions (play/stop/pause etc.)
|
||||
for action, cmd in (
|
||||
("media_play", "play"),
|
||||
("media_pause", "pause"),
|
||||
("media_stop", "stop"),
|
||||
("media_previous_track", "previous"),
|
||||
("media_next_track", "next"),
|
||||
("volume_up", "volume_up"),
|
||||
("volume_down", "volume_down"),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
action,
|
||||
{
|
||||
"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()
|
||||
|
||||
# test seek action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"media_seek",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
"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
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
||||
|
||||
# test volume action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"volume_set",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
"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
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
||||
|
||||
# test volume mute action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"volume_mute",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
"is_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
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
||||
|
||||
# test turn_on /turn_off action
|
||||
for action, pwr in (
|
||||
("turn_on", True),
|
||||
("turn_off", False),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
action,
|
||||
{
|
||||
"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()
|
||||
|
||||
# test shuffle action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"shuffle_set",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
"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
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
||||
|
||||
# test repeat action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"repeat_set",
|
||||
{
|
||||
"entity_id": entity_id,
|
||||
"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"
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
||||
|
||||
# test clear playlist action
|
||||
await hass.services.async_call(
|
||||
"media_player",
|
||||
"clear_playlist",
|
||||
{
|
||||
"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
|
||||
)
|
||||
music_assistant_client.send_command.reset_mock()
|
Loading…
Add table
Add a link
Reference in a new issue