Add tests to the media_player platform of the Squeezebox integration (#125378)

* Squeezebox media_player platform tests

* Fix play-pause test

* Squeezebox remove stray reference to deprecated property

* More tests for squeezebox

* Update tests to fix merge conflict with binary_sensor

* Refactor tests to use autospec

* Use freeze and snapshot

* Update media player entity before adding

* Consolidate test fixtures for different platforms

* Merge in sensor platform

* Use deepcopy

* Update tests with suggestions from code review
This commit is contained in:
Raj Laud 2024-09-13 09:20:31 -04:00 committed by GitHub
parent 0af913cc9a
commit a01036760e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 1125 additions and 135 deletions

View file

@ -14,6 +14,7 @@ import voluptuous as vol
from homeassistant.components import media_source
from homeassistant.components.media_player import (
ATTR_MEDIA_ENQUEUE,
BrowseError,
BrowseMedia,
MediaPlayerEnqueue,
MediaPlayerEntity,
@ -26,6 +27,7 @@ from homeassistant.components.media_player import (
from homeassistant.config_entries import SOURCE_INTEGRATION_DISCOVERY
from homeassistant.const import ATTR_COMMAND, CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import (
config_validation as cv,
discovery_flow,
@ -138,7 +140,7 @@ async def async_setup_entry(
_LOGGER.debug("Adding new entity: %s", player)
entity = SqueezeBoxEntity(player, lms)
known_players.append(entity)
async_add_entities([entity])
async_add_entities([entity], True)
if players := await lms.async_get_players():
for player in players:
@ -248,8 +250,11 @@ class SqueezeBoxEntity(MediaPlayerEntity):
"""Return the state of the device."""
if not self._player.power:
return MediaPlayerState.OFF
if self._player.mode:
return SQUEEZEBOX_MODE.get(self._player.mode)
if self._player.mode and self._player.mode in SQUEEZEBOX_MODE:
return SQUEEZEBOX_MODE[self._player.mode]
_LOGGER.error(
"Received unknown mode %s from player %s", self._player.mode, self.name
)
return None
async def async_update(self) -> None:
@ -278,6 +283,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
"""Volume level of the media player (0..1)."""
if self._player.volume:
return int(float(self._player.volume)) / 100.0
return None
@property
@ -322,7 +328,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
@property
def media_image_url(self) -> str | None:
"""Image url of current playing media."""
return str(self._player.image_url)
return str(self._player.image_url) if self._player.image_url else None
@property
def media_title(self) -> str | None:
@ -371,11 +377,6 @@ class SqueezeBoxEntity(MediaPlayerEntity):
if player in player_ids
]
@property
def sync_group(self) -> list[str]:
"""List players we are synced with. Deprecated."""
return self.group_members
@property
def query_result(self) -> dict | bool:
"""Return the result from the call_query service."""
@ -474,7 +475,7 @@ class SqueezeBoxEntity(MediaPlayerEntity):
"search_type": MediaType.PLAYLIST,
}
playlist = await generate_playlist(self._player, payload)
except ValueError:
except BrowseError:
# a list of urls
content = json.loads(media_id)
playlist = content["urls"]
@ -553,8 +554,8 @@ class SqueezeBoxEntity(MediaPlayerEntity):
if other_player_id := player_ids.get(other_player):
await self._player.async_sync(other_player_id)
else:
_LOGGER.debug(
"Could not find player_id for %s. Not syncing", other_player
raise ServiceValidationError(
f"Could not join unknown player {other_player}"
)
async def async_unjoin_player(self) -> None:

View file

@ -1,85 +1 @@
"""Tests for the Logitech Squeezebox integration."""
from homeassistant.components.squeezebox.const import (
DOMAIN,
STATUS_QUERY_LIBRARYNAME,
STATUS_QUERY_MAC,
STATUS_QUERY_UUID,
STATUS_QUERY_VERSION,
STATUS_SENSOR_INFO_TOTAL_ALBUMS,
STATUS_SENSOR_INFO_TOTAL_ARTISTS,
STATUS_SENSOR_INFO_TOTAL_DURATION,
STATUS_SENSOR_INFO_TOTAL_GENRES,
STATUS_SENSOR_INFO_TOTAL_SONGS,
STATUS_SENSOR_LASTSCAN,
STATUS_SENSOR_OTHER_PLAYER_COUNT,
STATUS_SENSOR_PLAYER_COUNT,
STATUS_SENSOR_RESCAN,
)
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.core import HomeAssistant
# from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
FAKE_IP = "42.42.42.42"
FAKE_MAC = "deadbeefdead"
FAKE_UUID = "deadbeefdeadbeefbeefdeafbeef42"
FAKE_PORT = 9000
FAKE_VERSION = "42.0"
FAKE_QUERY_RESPONSE = {
STATUS_QUERY_UUID: FAKE_UUID,
STATUS_QUERY_MAC: FAKE_MAC,
STATUS_QUERY_VERSION: FAKE_VERSION,
STATUS_SENSOR_RESCAN: 1,
STATUS_SENSOR_LASTSCAN: 0,
STATUS_QUERY_LIBRARYNAME: "FakeLib",
STATUS_SENSOR_INFO_TOTAL_ALBUMS: 4,
STATUS_SENSOR_INFO_TOTAL_ARTISTS: 2,
STATUS_SENSOR_INFO_TOTAL_DURATION: 500,
STATUS_SENSOR_INFO_TOTAL_GENRES: 1,
STATUS_SENSOR_INFO_TOTAL_SONGS: 42,
STATUS_SENSOR_PLAYER_COUNT: 10,
STATUS_SENSOR_OTHER_PLAYER_COUNT: 0,
"players_loop": [
{
"isplaying": 0,
"name": "SqueezeLite-HA-Addon",
"seq_no": 0,
"modelname": "SqueezeLite-HA-Addon",
"playerindex": "status",
"model": "squeezelite",
"uuid": FAKE_UUID,
"canpoweroff": 1,
"ip": "192.168.78.86:57700",
"displaytype": "none",
"playerid": "f9:23:cd:37:c5:ff",
"power": 0,
"isplayer": 1,
"connected": 1,
"firmware": "v2.0.0-1488",
}
],
"count": 1,
}
async def setup_mocked_integration(hass: HomeAssistant) -> MockConfigEntry:
"""Mock ConfigEntry in Home Assistant."""
entry = MockConfigEntry(
domain=DOMAIN,
unique_id=FAKE_UUID,
data={
CONF_HOST: FAKE_IP,
CONF_PORT: FAKE_PORT,
},
)
entry.add_to_hass(hass)
await hass.config_entries.async_setup(entry.entry_id)
await hass.async_block_till_done()
return entry

View file

@ -11,17 +11,82 @@ from homeassistant.components.squeezebox.browse_media import (
MEDIA_TYPE_TO_SQUEEZEBOX,
SQUEEZEBOX_ID_BY_TYPE,
)
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.components.squeezebox.const import (
STATUS_QUERY_LIBRARYNAME,
STATUS_QUERY_MAC,
STATUS_QUERY_UUID,
STATUS_QUERY_VERSION,
STATUS_SENSOR_INFO_TOTAL_ALBUMS,
STATUS_SENSOR_INFO_TOTAL_ARTISTS,
STATUS_SENSOR_INFO_TOTAL_DURATION,
STATUS_SENSOR_INFO_TOTAL_GENRES,
STATUS_SENSOR_INFO_TOTAL_SONGS,
STATUS_SENSOR_LASTSCAN,
STATUS_SENSOR_OTHER_PLAYER_COUNT,
STATUS_SENSOR_PLAYER_COUNT,
STATUS_SENSOR_RESCAN,
)
from homeassistant.const import CONF_HOST, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import format_mac
# from homeassistant.setup import async_setup_component
from tests.common import MockConfigEntry
TEST_HOST = "1.2.3.4"
TEST_PORT = "9000"
TEST_USE_HTTPS = False
SERVER_UUID = "12345678-1234-1234-1234-123456789012"
TEST_MAC = "aa:bb:cc:dd:ee:ff"
SERVER_UUIDS = [
"12345678-1234-1234-1234-123456789012",
"87654321-4321-4321-4321-210987654321",
]
TEST_MAC = ["aa:bb:cc:dd:ee:ff", "ff:ee:dd:cc:bb:aa"]
TEST_PLAYER_NAME = "Test Player"
TEST_SERVER_NAME = "Test Server"
FAKE_VALID_ITEM_ID = "1234"
FAKE_INVALID_ITEM_ID = "4321"
FAKE_IP = "42.42.42.42"
FAKE_MAC = "deadbeefdead"
FAKE_UUID = "deadbeefdeadbeefbeefdeafbeef42"
FAKE_PORT = 9000
FAKE_VERSION = "42.0"
FAKE_QUERY_RESPONSE = {
STATUS_QUERY_UUID: FAKE_UUID,
STATUS_QUERY_MAC: FAKE_MAC,
STATUS_QUERY_VERSION: FAKE_VERSION,
STATUS_SENSOR_RESCAN: 1,
STATUS_SENSOR_LASTSCAN: 0,
STATUS_QUERY_LIBRARYNAME: "FakeLib",
STATUS_SENSOR_INFO_TOTAL_ALBUMS: 4,
STATUS_SENSOR_INFO_TOTAL_ARTISTS: 2,
STATUS_SENSOR_INFO_TOTAL_DURATION: 500,
STATUS_SENSOR_INFO_TOTAL_GENRES: 1,
STATUS_SENSOR_INFO_TOTAL_SONGS: 42,
STATUS_SENSOR_PLAYER_COUNT: 10,
STATUS_SENSOR_OTHER_PLAYER_COUNT: 0,
"players_loop": [
{
"isplaying": 0,
"name": "SqueezeLite-HA-Addon",
"seq_no": 0,
"modelname": "SqueezeLite-HA-Addon",
"playerindex": "status",
"model": "squeezelite",
"uuid": FAKE_UUID,
"canpoweroff": 1,
"ip": "192.168.78.86:57700",
"displaytype": "none",
"playerid": "f9:23:cd:37:c5:ff",
"power": 0,
"isplayer": 1,
"connected": 1,
"firmware": "v2.0.0-1488",
}
],
"count": 1,
}
@pytest.fixture
@ -38,7 +103,7 @@ def config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Add the squeezebox mock config entry to hass."""
config_entry = MockConfigEntry(
domain=const.DOMAIN,
unique_id=SERVER_UUID,
unique_id=SERVER_UUIDS[0],
data={
CONF_HOST: TEST_HOST,
CONF_PORT: TEST_PORT,
@ -69,29 +134,41 @@ async def mock_async_browse(
fake_items = [
{
"title": "Fake Item 1",
"id": "1234",
"id": FAKE_VALID_ITEM_ID,
"hasitems": False,
"item_type": child_types[media_type],
"artwork_track_id": "b35bb9e9",
"url": "file:///var/lib/squeezeboxserver/music/track_1.mp3",
},
{
"title": "Fake Item 2",
"id": "12345",
"id": FAKE_VALID_ITEM_ID + "_2",
"hasitems": media_type == "favorites",
"item_type": child_types[media_type],
"image_url": "http://lms.internal:9000/html/images/favorites.png",
"url": "file:///var/lib/squeezeboxserver/music/track_2.mp3",
},
{
"title": "Fake Item 3",
"id": "123456",
"id": FAKE_VALID_ITEM_ID + "_3",
"hasitems": media_type == "favorites",
"album_id": "123456" if media_type == "favorites" else None,
"album_id": FAKE_VALID_ITEM_ID if media_type == "favorites" else None,
"url": "file:///var/lib/squeezeboxserver/music/track_3.mp3",
},
]
if browse_id:
search_type, search_id = browse_id
if search_id:
if search_type == "playlist_id":
return (
{
"title": "Fake Item 1",
"items": fake_items,
}
if search_id == FAKE_VALID_ITEM_ID
else None
)
if search_type in SQUEEZEBOX_ID_BY_TYPE.values():
for item in fake_items:
if item["id"] == search_id:
@ -115,20 +192,96 @@ async def mock_async_browse(
@pytest.fixture
def lms() -> MagicMock:
"""Mock a Lyrion Media Server with one mock player attached."""
lms = MagicMock()
player = MagicMock()
player.player_id = TEST_MAC
player.name = "Test Player"
player.power = False
player.async_browse = AsyncMock(side_effect=mock_async_browse)
player.async_load_playlist = AsyncMock()
player.async_update = AsyncMock()
player.generate_image_url_from_track_id = MagicMock(
return_value="http://lms.internal:9000/html/images/favorites.png"
def player() -> MagicMock:
"""Return a mock player."""
return mock_pysqueezebox_player()
@pytest.fixture
def player_factory() -> MagicMock:
"""Return a factory for creating mock players."""
return mock_pysqueezebox_player
def mock_pysqueezebox_player(uuid: str) -> MagicMock:
"""Mock a Lyrion Media Server player."""
with patch(
"homeassistant.components.squeezebox.media_player.Player", autospec=True
) as mock_player:
mock_player.async_browse = AsyncMock(side_effect=mock_async_browse)
mock_player.generate_image_url_from_track_id = MagicMock(
return_value="http://lms.internal:9000/html/images/favorites.png"
)
mock_player.name = TEST_PLAYER_NAME
mock_player.player_id = uuid
mock_player.mode = "stop"
mock_player.playlist = None
mock_player.album = None
mock_player.artist = None
mock_player.remote_title = None
mock_player.title = None
mock_player.image_url = None
return mock_player
@pytest.fixture
def lms_factory(player_factory: MagicMock) -> MagicMock:
"""Return a factory for creating mock Lyrion Media Servers with arbitrary number of players."""
return lambda player_count, uuid: mock_pysqueezebox_server(
player_factory, player_count, uuid
)
lms.async_get_players = AsyncMock(return_value=[player])
lms.async_query = AsyncMock(return_value={"uuid": format_mac(TEST_MAC)})
lms.async_status = AsyncMock(return_value={"uuid": format_mac(TEST_MAC)})
return lms
@pytest.fixture
def lms(player_factory: MagicMock) -> MagicMock:
"""Mock a Lyrion Media Server with one mock player attached."""
return mock_pysqueezebox_server(player_factory, 1, uuid=TEST_MAC[0])
def mock_pysqueezebox_server(
player_factory: MagicMock, player_count: int, uuid: str
) -> MagicMock:
"""Create a mock Lyrion Media Server with the given number of mock players attached."""
with patch("homeassistant.components.squeezebox.Server", autospec=True) as mock_lms:
players = [player_factory(TEST_MAC[index]) for index in range(player_count)]
mock_lms.async_get_players = AsyncMock(return_value=players)
mock_lms.uuid = uuid
mock_lms.name = TEST_SERVER_NAME
mock_lms.async_query = AsyncMock(return_value={"uuid": format_mac(uuid)})
mock_lms.async_status = AsyncMock(return_value={"uuid": format_mac(uuid)})
return mock_lms
async def configure_squeezebox_media_player_platform(
hass: HomeAssistant,
config_entry: MockConfigEntry,
lms: MagicMock,
) -> None:
"""Configure a squeezebox config entry with appropriate mocks for media_player."""
with (
patch("homeassistant.components.squeezebox.PLATFORMS", [Platform.MEDIA_PLAYER]),
patch("homeassistant.components.squeezebox.Server", return_value=lms),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
@pytest.fixture
async def configured_player(
hass: HomeAssistant, config_entry: MockConfigEntry, lms: MagicMock
) -> MagicMock:
"""Fixture mocking calls to pysqueezebox Player from a configured squeezebox."""
await configure_squeezebox_media_player_platform(hass, config_entry, lms)
return (await lms.async_get_players())[0]
@pytest.fixture
async def configured_players(
hass: HomeAssistant, config_entry: MockConfigEntry, lms_factory: MagicMock
) -> list[MagicMock]:
"""Fixture mocking calls to two pysqueezebox Players from a configured squeezebox."""
lms = lms_factory(2, uuid=SERVER_UUIDS[0])
await configure_squeezebox_media_player_platform(hass, config_entry, lms)
return await lms.async_get_players()

View file

@ -0,0 +1,99 @@
# serializer version: 1
# name: test_device_registry
DeviceRegistryEntrySnapshot({
'area_id': None,
'config_entries': <ANY>,
'configuration_url': None,
'connections': set({
tuple(
'mac',
'aa:bb:cc:dd:ee:ff',
),
}),
'disabled_by': None,
'entry_type': <DeviceEntryType.SERVICE: 'service'>,
'hw_version': None,
'id': <ANY>,
'identifiers': set({
tuple(
'squeezebox',
'aa:bb:cc:dd:ee:ff',
),
}),
'is_new': False,
'labels': set({
}),
'manufacturer': 'https://lyrion.org/',
'model': 'Lyrion Music Server',
'model_id': None,
'name': 'Test Player',
'name_by_user': None,
'primary_config_entry': <ANY>,
'serial_number': None,
'suggested_area': None,
'sw_version': None,
'via_device_id': <ANY>,
})
# ---
# name: test_entity_registry[media_player.test_player-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',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'squeezebox',
'previous_unique_id': None,
'supported_features': <MediaPlayerEntityFeature: 3077055>,
'translation_key': None,
'unique_id': 'aa:bb:cc:dd:ee:ff',
'unit_of_measurement': None,
})
# ---
# name: test_entity_registry[media_player.test_player-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Test Player',
'group_members': list([
]),
'is_volume_muted': True,
'media_album_name': 'None',
'media_artist': 'None',
'media_channel': 'None',
'media_duration': 1,
'media_position': 1,
'media_title': 'None',
'query_result': dict({
}),
'repeat': <RepeatMode.OFF: 'off'>,
'shuffle': False,
'supported_features': <MediaPlayerEntityFeature: 3077055>,
'volume_level': 0.01,
}),
'context': <ANY>,
'entity_id': 'media_player.test_player',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'idle',
})
# ---

View file

@ -1,22 +1,21 @@
"""Test squeezebox binary sensors."""
import copy
from copy import deepcopy
from unittest.mock import patch
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import FAKE_QUERY_RESPONSE, setup_mocked_integration
from .conftest import FAKE_QUERY_RESPONSE
from tests.common import MockConfigEntry
async def test_binary_sensor(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
config_entry: MockConfigEntry,
) -> None:
"""Test binary sensor states and attributes."""
# Setup component
with (
patch(
"homeassistant.components.squeezebox.PLATFORMS",
@ -24,11 +23,13 @@ async def test_binary_sensor(
),
patch(
"homeassistant.components.squeezebox.Server.async_query",
return_value=copy.deepcopy(FAKE_QUERY_RESPONSE),
return_value=deepcopy(FAKE_QUERY_RESPONSE),
),
):
await setup_mocked_integration(hass)
state = hass.states.get("binary_sensor.fakelib_library_rescan")
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("binary_sensor.fakelib_needs_restart")
assert state is not None
assert state.state == "on"
assert state.state == "off"

View file

@ -0,0 +1,815 @@
"""Tests for the squeezebox media player component."""
from datetime import timedelta
import json
from unittest.mock import AsyncMock, MagicMock, patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from syrupy import SnapshotAssertion
from homeassistant.components.media_player import (
ATTR_GROUP_MEMBERS,
ATTR_MEDIA_CONTENT_ID,
ATTR_MEDIA_CONTENT_TYPE,
ATTR_MEDIA_ENQUEUE,
ATTR_MEDIA_POSITION,
ATTR_MEDIA_POSITION_UPDATED_AT,
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,
SERVICE_JOIN,
SERVICE_PLAY_MEDIA,
SERVICE_UNJOIN,
MediaPlayerEnqueue,
MediaPlayerState,
MediaType,
RepeatMode,
)
from homeassistant.components.squeezebox.const import DOMAIN, SENSOR_UPDATE_INTERVAL
from homeassistant.components.squeezebox.media_player import (
ATTR_PARAMETERS,
DISCOVERY_INTERVAL,
SERVICE_CALL_METHOD,
SERVICE_CALL_QUERY,
)
from homeassistant.const import (
ATTR_COMMAND,
ATTR_ENTITY_ID,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PLAY_PAUSE,
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_SEEK,
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,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.entity_registry import EntityRegistry
from homeassistant.util.dt import utcnow
from .conftest import FAKE_VALID_ITEM_ID, TEST_MAC
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
async def test_device_registry(
hass: HomeAssistant,
device_registry: DeviceRegistry,
configured_player: MagicMock,
snapshot: SnapshotAssertion,
) -> None:
"""Test squeezebox device registered in the device registry."""
reg_device = device_registry.async_get_device(identifiers={(DOMAIN, TEST_MAC[0])})
assert reg_device is not None
assert reg_device == snapshot
async def test_entity_registry(
hass: HomeAssistant,
entity_registry: EntityRegistry,
configured_player: MagicMock,
snapshot: SnapshotAssertion,
config_entry: MockConfigEntry,
) -> None:
"""Test squeezebox media_player entity registered in the entity registry."""
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
async def test_squeezebox_player_rediscovery(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test rediscovery of a squeezebox player."""
assert hass.states.get("media_player.test_player").state == MediaPlayerState.IDLE
# Make the player appear unavailable
configured_player.connected = False
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
assert hass.states.get("media_player.test_player").state == STATE_UNAVAILABLE
# Make the player available again
configured_player.connected = True
freezer.tick(timedelta(seconds=DISCOVERY_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == MediaPlayerState.IDLE
async def test_squeezebox_turn_on(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test turn on service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_TURN_ON,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_set_power.assert_called_once_with(True)
async def test_squeezebox_turn_off(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test turn off service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_set_power.assert_called_once_with(False)
async def test_squeezebox_state(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test determining the MediaPlayerState."""
configured_player.power = True
configured_player.mode = "stop"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == MediaPlayerState.IDLE
configured_player.mode = "play"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == MediaPlayerState.PLAYING
configured_player.mode = "pause"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == MediaPlayerState.PAUSED
configured_player.power = False
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == MediaPlayerState.OFF
async def test_squeezebox_volume_up(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test volume up service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_UP,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_set_volume.assert_called_once_with("+5")
async def test_squeezebox_volume_down(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test volume down service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_DOWN,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_set_volume.assert_called_once_with("-5")
async def test_squeezebox_volume_set(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test volume set service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_SET,
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_MEDIA_VOLUME_LEVEL: 0.5},
blocking=True,
)
configured_player.async_set_volume.assert_called_once_with("50")
async def test_squeezebox_volume_property(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test volume property."""
configured_player.volume = 50
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_VOLUME_LEVEL]
== 0.5
)
configured_player.volume = None
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
ATTR_MEDIA_VOLUME_LEVEL
not in hass.states.get("media_player.test_player").attributes
)
async def test_squeezebox_mute(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test mute service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_MEDIA_VOLUME_MUTED: True},
blocking=True,
)
configured_player.async_set_muting.assert_called_once_with(True)
async def test_squeezebox_unmute(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test unmute service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_VOLUME_MUTE,
{ATTR_ENTITY_ID: "media_player.test_player", ATTR_MEDIA_VOLUME_MUTED: False},
blocking=True,
)
configured_player.async_set_muting.assert_called_once_with(False)
async def test_squeezebox_mute_property(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test the mute property."""
configured_player.muting = True
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_VOLUME_MUTED]
is True
)
configured_player.muting = False
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_VOLUME_MUTED]
is False
)
async def test_squeezebox_repeat_mode(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test set repeat mode service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_REPEAT_SET,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_REPEAT: RepeatMode.ALL,
},
blocking=True,
)
configured_player.async_set_repeat.assert_called_once_with("playlist")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_REPEAT_SET,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_REPEAT: RepeatMode.ONE,
},
blocking=True,
)
configured_player.async_set_repeat.assert_called_with("song")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_REPEAT_SET,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_REPEAT: RepeatMode.OFF,
},
blocking=True,
)
configured_player.async_set_repeat.assert_called_with("none")
async def test_squeezebox_repeat_mode_property(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test the repeat mode property."""
configured_player.repeat = "playlist"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_REPEAT]
== RepeatMode.ALL
)
configured_player.repeat = "song"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_REPEAT]
== RepeatMode.ONE
)
configured_player.repeat = "none"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_REPEAT]
== RepeatMode.OFF
)
async def test_squeezebox_shuffle(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test set shuffle service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_SHUFFLE_SET,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_SHUFFLE: True,
},
blocking=True,
)
configured_player.async_set_shuffle.assert_called_once_with("song")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_SHUFFLE_SET,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_SHUFFLE: False,
},
blocking=True,
)
configured_player.async_set_shuffle.assert_called_with("none")
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_SHUFFLE]
is False
)
async def test_squeezebox_shuffle_property(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test the shuffle property."""
configured_player.shuffle = "song"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_SHUFFLE]
is True
)
configured_player.shuffle = "none"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_SHUFFLE]
is False
)
async def test_squeezebox_play(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test play service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PLAY,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_play.assert_called_once()
async def test_squeezebox_play_pause(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test play/pause service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PLAY_PAUSE,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_toggle_pause.assert_called_once()
async def test_squeezebox_pause(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test pause service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PAUSE,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_pause.assert_called_once()
async def test_squeezebox_seek(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test seek service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
},
blocking=True,
)
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_SEEK,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_SEEK_POSITION: 100,
},
blocking=True,
)
configured_player.async_time.assert_called_once_with(100)
async def test_squeezebox_stop(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test stop service call."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_STOP,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_stop.assert_called_once()
async def test_squeezebox_load_playlist(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test load a playlist."""
# load a playlist by number
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.PLAYLIST,
},
blocking=True,
)
assert configured_player.async_load_playlist.call_count == 1
# load a list of urls
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: json.dumps(
{
"urls": [
{"url": FAKE_VALID_ITEM_ID},
{"url": FAKE_VALID_ITEM_ID + "_2"},
],
"index": "0",
}
),
ATTR_MEDIA_CONTENT_TYPE: MediaType.PLAYLIST,
},
blocking=True,
)
assert configured_player.async_load_playlist.call_count == 2
# clear the playlist
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_CLEAR_PLAYLIST,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_clear_playlist.assert_called_once()
async def test_squeezebox_enqueue(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test the various enqueue service calls."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.ADD,
},
blocking=True,
)
configured_player.async_load_url.assert_called_once_with(FAKE_VALID_ITEM_ID, "add")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.NEXT,
},
blocking=True,
)
configured_player.async_load_url.assert_called_with(FAKE_VALID_ITEM_ID, "insert")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.MUSIC,
ATTR_MEDIA_ENQUEUE: MediaPlayerEnqueue.PLAY,
},
blocking=True,
)
configured_player.async_load_url.assert_called_with(FAKE_VALID_ITEM_ID, "play_now")
async def test_squeezebox_skip_tracks(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test track skipping service calls."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_MEDIA_CONTENT_ID: FAKE_VALID_ITEM_ID,
ATTR_MEDIA_CONTENT_TYPE: MediaType.PLAYLIST,
},
blocking=True,
)
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_NEXT_TRACK,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_index.assert_called_once_with("+1")
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_MEDIA_PREVIOUS_TRACK,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_index.assert_called_with("-1")
async def test_squeezebox_call_query(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test query service call."""
await hass.services.async_call(
DOMAIN,
SERVICE_CALL_QUERY,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_COMMAND: "test_command",
ATTR_PARAMETERS: ["param1", "param2"],
},
blocking=True,
)
configured_player.async_query.assert_called_once_with(
"test_command", "param1", "param2"
)
async def test_squeezebox_call_method(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test method call service call."""
await hass.services.async_call(
DOMAIN,
SERVICE_CALL_METHOD,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_COMMAND: "test_command",
ATTR_PARAMETERS: ["param1", "param2"],
},
blocking=True,
)
configured_player.async_query.assert_called_once_with(
"test_command", "param1", "param2"
)
async def test_squeezebox_invalid_state(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test handling an unexpected state from pysqueezebox."""
configured_player.mode = "invalid"
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").state == STATE_UNKNOWN
async def test_squeezebox_server_discovery(
hass: HomeAssistant,
lms: MagicMock,
lms_factory: MagicMock,
config_entry: MockConfigEntry,
) -> None:
"""Test discovery of a squeezebox server."""
async def mock_async_discover(callback):
"""Mock the async_discover function of pysqueezebox."""
return callback(lms_factory(2))
with (
patch(
"homeassistant.components.squeezebox.Server",
return_value=lms,
),
patch(
"homeassistant.components.squeezebox.media_player.async_discover",
mock_async_discover,
),
):
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
# how do we check that a config flow started?
async def test_squeezebox_join(hass: HomeAssistant, configured_players: list) -> None:
"""Test joining a squeezebox player."""
# join a valid player
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_JOIN,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_GROUP_MEMBERS: ["media_player.test_player_2"],
},
blocking=True,
)
configured_players[0].async_sync.assert_called_once_with(
configured_players[1].player_id
)
# try to join an invalid player
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_JOIN,
{
ATTR_ENTITY_ID: "media_player.test_player",
ATTR_GROUP_MEMBERS: ["media_player.invalid"],
},
blocking=True,
)
async def test_squeezebox_unjoin(
hass: HomeAssistant, configured_player: MagicMock
) -> None:
"""Test unjoining a squeezebox player."""
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_UNJOIN,
{ATTR_ENTITY_ID: "media_player.test_player"},
blocking=True,
)
configured_player.async_unsync.assert_called_once()
async def test_squeezebox_media_content_properties(
hass: HomeAssistant,
configured_player: MagicMock,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test media_content_id and media_content_type properties."""
playlist_urls = [
{"url": "test_title"},
{"url": "test_title_2"},
]
configured_player.current_index = 0
configured_player.playlist = playlist_urls
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("media_player.test_player").attributes[
ATTR_MEDIA_CONTENT_ID
] == json.dumps({"index": 0, "urls": playlist_urls})
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_CONTENT_TYPE]
== MediaType.PLAYLIST
)
configured_player.url = "test_url"
configured_player.playlist = [{"url": "test_url"}]
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_CONTENT_ID]
== "test_url"
)
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_CONTENT_TYPE]
== MediaType.MUSIC
)
configured_player.playlist = None
configured_player.url = None
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
ATTR_MEDIA_CONTENT_ID
not in hass.states.get("media_player.test_player").attributes
)
assert (
ATTR_MEDIA_CONTENT_TYPE
not in hass.states.get("media_player.test_player").attributes
)
async def test_squeezebox_media_position_property(
hass: HomeAssistant, configured_player: MagicMock, freezer: FrozenDateTimeFactory
) -> None:
"""Test media_position property."""
configured_player.time = 100
configured_player.async_update = AsyncMock(
side_effect=lambda: setattr(configured_player, "time", 105)
)
last_update = utcnow()
freezer.tick(timedelta(seconds=SENSOR_UPDATE_INTERVAL))
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert (
hass.states.get("media_player.test_player").attributes[ATTR_MEDIA_POSITION]
== 105
)
assert (
(
hass.states.get("media_player.test_player").attributes[
ATTR_MEDIA_POSITION_UPDATED_AT
]
)
> last_update
)

View file

@ -1,15 +1,18 @@
"""Test squeezebox sensors."""
from copy import deepcopy
from unittest.mock import patch
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from . import FAKE_QUERY_RESPONSE, setup_mocked_integration
from .conftest import FAKE_QUERY_RESPONSE
from tests.common import MockConfigEntry
async def test_sensor(hass: HomeAssistant) -> None:
"""Test binary sensor states and attributes."""
async def test_sensor(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Test sensor states and attributes."""
# Setup component
with (
@ -19,10 +22,12 @@ async def test_sensor(hass: HomeAssistant) -> None:
),
patch(
"homeassistant.components.squeezebox.Server.async_query",
return_value=FAKE_QUERY_RESPONSE,
return_value=deepcopy(FAKE_QUERY_RESPONSE),
),
):
await setup_mocked_integration(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done(wait_background_tasks=True)
state = hass.states.get("sensor.fakelib_player_count")
assert state is not None