mirror of
https://github.com/home-assistant/core.git
synced 2025-07-22 12:47:08 +00:00
Add sound modes to Bang & Olufsen devices (#121209)
* Add sound mode functionality * Fix naming * Change unique sound mode symbol * Add testing for sound modes * Add test typing * Use constants for service call parameters * Add state assertions * Remove invalid decorator * Add valid sound mode check * Add test for invalid sound mode
This commit is contained in:
parent
2699eb62bd
commit
bb29c7a02f
@ -68,6 +68,7 @@ class BangOlufsenModel(StrEnum):
|
|||||||
class WebsocketNotification(StrEnum):
|
class WebsocketNotification(StrEnum):
|
||||||
"""Enum for WebSocket notification types."""
|
"""Enum for WebSocket notification types."""
|
||||||
|
|
||||||
|
ACTIVE_LISTENING_MODE = "active_listening_mode"
|
||||||
PLAYBACK_ERROR = "playback_error"
|
PLAYBACK_ERROR = "playback_error"
|
||||||
PLAYBACK_METADATA = "playback_metadata"
|
PLAYBACK_METADATA = "playback_metadata"
|
||||||
PLAYBACK_PROGRESS = "playback_progress"
|
PLAYBACK_PROGRESS = "playback_progress"
|
||||||
|
@ -13,6 +13,8 @@ from mozart_api.models import (
|
|||||||
Action,
|
Action,
|
||||||
Art,
|
Art,
|
||||||
BeolinkLeader,
|
BeolinkLeader,
|
||||||
|
ListeningModeProps,
|
||||||
|
ListeningModeRef,
|
||||||
OverlayPlayRequest,
|
OverlayPlayRequest,
|
||||||
OverlayPlayRequestTextToSpeechTextToSpeech,
|
OverlayPlayRequestTextToSpeechTextToSpeech,
|
||||||
PlaybackContentMetadata,
|
PlaybackContentMetadata,
|
||||||
@ -88,6 +90,7 @@ BANG_OLUFSEN_FEATURES = (
|
|||||||
| MediaPlayerEntityFeature.TURN_OFF
|
| MediaPlayerEntityFeature.TURN_OFF
|
||||||
| MediaPlayerEntityFeature.VOLUME_MUTE
|
| MediaPlayerEntityFeature.VOLUME_MUTE
|
||||||
| MediaPlayerEntityFeature.VOLUME_SET
|
| MediaPlayerEntityFeature.VOLUME_SET
|
||||||
|
| MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -137,6 +140,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
|||||||
self._sources: dict[str, str] = {}
|
self._sources: dict[str, str] = {}
|
||||||
self._state: str = MediaPlayerState.IDLE
|
self._state: str = MediaPlayerState.IDLE
|
||||||
self._video_sources: dict[str, str] = {}
|
self._video_sources: dict[str, str] = {}
|
||||||
|
self._sound_modes: dict[str, int] = {}
|
||||||
|
|
||||||
# Beolink compatible sources
|
# Beolink compatible sources
|
||||||
self._beolink_sources: dict[str, bool] = {}
|
self._beolink_sources: dict[str, bool] = {}
|
||||||
@ -148,6 +152,7 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
|||||||
|
|
||||||
signal_handlers: dict[str, Callable] = {
|
signal_handlers: dict[str, Callable] = {
|
||||||
CONNECTION_STATUS: self._async_update_connection_state,
|
CONNECTION_STATUS: self._async_update_connection_state,
|
||||||
|
WebsocketNotification.ACTIVE_LISTENING_MODE: self._async_update_sound_modes,
|
||||||
WebsocketNotification.BEOLINK: self._async_update_beolink,
|
WebsocketNotification.BEOLINK: self._async_update_beolink,
|
||||||
WebsocketNotification.PLAYBACK_ERROR: self._async_update_playback_error,
|
WebsocketNotification.PLAYBACK_ERROR: self._async_update_playback_error,
|
||||||
WebsocketNotification.PLAYBACK_METADATA: self._async_update_playback_metadata_and_beolink,
|
WebsocketNotification.PLAYBACK_METADATA: self._async_update_playback_metadata_and_beolink,
|
||||||
@ -211,6 +216,8 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
|||||||
# If the device has been updated with new sources, then the API will fail here.
|
# If the device has been updated with new sources, then the API will fail here.
|
||||||
await self._async_update_sources()
|
await self._async_update_sources()
|
||||||
|
|
||||||
|
await self._async_update_sound_modes()
|
||||||
|
|
||||||
async def _async_update_sources(self) -> None:
|
async def _async_update_sources(self) -> None:
|
||||||
"""Get sources for the specific product."""
|
"""Get sources for the specific product."""
|
||||||
|
|
||||||
@ -432,6 +439,29 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
|||||||
# Return JID
|
# Return JID
|
||||||
return cast(str, config_entry.data[CONF_BEOLINK_JID])
|
return cast(str, config_entry.data[CONF_BEOLINK_JID])
|
||||||
|
|
||||||
|
async def _async_update_sound_modes(
|
||||||
|
self, active_sound_mode: ListeningModeProps | ListeningModeRef | None = None
|
||||||
|
) -> None:
|
||||||
|
"""Update the available sound modes."""
|
||||||
|
sound_modes = await self._client.get_listening_mode_set()
|
||||||
|
|
||||||
|
if active_sound_mode is None:
|
||||||
|
active_sound_mode = await self._client.get_active_listening_mode()
|
||||||
|
|
||||||
|
# Add the key to make the labels unique (As labels are not required to be unique on B&O devices)
|
||||||
|
for sound_mode in sound_modes:
|
||||||
|
label = f"{sound_mode.name} ({sound_mode.id})"
|
||||||
|
|
||||||
|
self._sound_modes[label] = sound_mode.id
|
||||||
|
|
||||||
|
if sound_mode.id == active_sound_mode.id:
|
||||||
|
self._attr_sound_mode = label
|
||||||
|
|
||||||
|
# Set available options
|
||||||
|
self._attr_sound_mode_list = list(self._sound_modes.keys())
|
||||||
|
|
||||||
|
self.async_write_ha_state()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def state(self) -> MediaPlayerState:
|
def state(self) -> MediaPlayerState:
|
||||||
"""Return the current state of the media player."""
|
"""Return the current state of the media player."""
|
||||||
@ -620,6 +650,21 @@ class BangOlufsenMediaPlayer(BangOlufsenEntity, MediaPlayerEntity):
|
|||||||
# Video
|
# Video
|
||||||
await self._client.post_remote_trigger(id=key)
|
await self._client.post_remote_trigger(id=key)
|
||||||
|
|
||||||
|
async def async_select_sound_mode(self, sound_mode: str) -> None:
|
||||||
|
"""Select a sound mode."""
|
||||||
|
# Ensure only known sound modes known by the integration can be activated.
|
||||||
|
if sound_mode not in self._sound_modes:
|
||||||
|
raise ServiceValidationError(
|
||||||
|
translation_domain=DOMAIN,
|
||||||
|
translation_key="invalid_sound_mode",
|
||||||
|
translation_placeholders={
|
||||||
|
"invalid_sound_mode": sound_mode,
|
||||||
|
"valid_sound_modes": ", ".join(list(self._sound_modes.keys())),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
await self._client.activate_listening_mode(id=self._sound_modes[sound_mode])
|
||||||
|
|
||||||
async def async_play_media(
|
async def async_play_media(
|
||||||
self,
|
self,
|
||||||
media_type: MediaType | str,
|
media_type: MediaType | str,
|
||||||
|
@ -43,6 +43,9 @@
|
|||||||
},
|
},
|
||||||
"invalid_grouping_entity": {
|
"invalid_grouping_entity": {
|
||||||
"message": "Entity with id: {entity_id} can't be added to the Beolink session. Is the entity a Bang & Olufsen media_player?"
|
"message": "Entity with id: {entity_id} can't be added to the Beolink session. Is the entity a Bang & Olufsen media_player?"
|
||||||
|
},
|
||||||
|
"invalid_sound_mode": {
|
||||||
|
"message": "{invalid_sound_mode} is an invalid sound mode. Valid values are: {valid_sound_modes}."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ from __future__ import annotations
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from mozart_api.models import (
|
from mozart_api.models import (
|
||||||
|
ListeningModeProps,
|
||||||
PlaybackContentMetadata,
|
PlaybackContentMetadata,
|
||||||
PlaybackError,
|
PlaybackError,
|
||||||
PlaybackProgress,
|
PlaybackProgress,
|
||||||
@ -50,6 +51,9 @@ class BangOlufsenWebsocket(BangOlufsenBase):
|
|||||||
self._client.get_notification_notifications(self.on_notification_notification)
|
self._client.get_notification_notifications(self.on_notification_notification)
|
||||||
self._client.get_on_connection_lost(self.on_connection_lost)
|
self._client.get_on_connection_lost(self.on_connection_lost)
|
||||||
self._client.get_on_connection(self.on_connection)
|
self._client.get_on_connection(self.on_connection)
|
||||||
|
self._client.get_active_listening_mode_notifications(
|
||||||
|
self.on_active_listening_mode
|
||||||
|
)
|
||||||
self._client.get_playback_error_notifications(
|
self._client.get_playback_error_notifications(
|
||||||
self.on_playback_error_notification
|
self.on_playback_error_notification
|
||||||
)
|
)
|
||||||
@ -89,6 +93,14 @@ class BangOlufsenWebsocket(BangOlufsenBase):
|
|||||||
_LOGGER.error("Lost connection to the %s", self.entry.title)
|
_LOGGER.error("Lost connection to the %s", self.entry.title)
|
||||||
self._update_connection_status()
|
self._update_connection_status()
|
||||||
|
|
||||||
|
def on_active_listening_mode(self, notification: ListeningModeProps) -> None:
|
||||||
|
"""Send active_listening_mode dispatch."""
|
||||||
|
async_dispatcher_send(
|
||||||
|
self.hass,
|
||||||
|
f"{self._unique_id}_{WebsocketNotification.ACTIVE_LISTENING_MODE}",
|
||||||
|
notification,
|
||||||
|
)
|
||||||
|
|
||||||
def on_notification_notification(
|
def on_notification_notification(
|
||||||
self, notification: WebsocketNotificationTag
|
self, notification: WebsocketNotificationTag
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -7,6 +7,10 @@ from mozart_api.models import (
|
|||||||
Action,
|
Action,
|
||||||
BeolinkPeer,
|
BeolinkPeer,
|
||||||
ContentItem,
|
ContentItem,
|
||||||
|
ListeningMode,
|
||||||
|
ListeningModeFeatures,
|
||||||
|
ListeningModeRef,
|
||||||
|
ListeningModeTrigger,
|
||||||
PlaybackContentMetadata,
|
PlaybackContentMetadata,
|
||||||
PlaybackProgress,
|
PlaybackProgress,
|
||||||
PlaybackState,
|
PlaybackState,
|
||||||
@ -38,6 +42,9 @@ from .const import (
|
|||||||
TEST_NAME_2,
|
TEST_NAME_2,
|
||||||
TEST_SERIAL_NUMBER,
|
TEST_SERIAL_NUMBER,
|
||||||
TEST_SERIAL_NUMBER_2,
|
TEST_SERIAL_NUMBER_2,
|
||||||
|
TEST_SOUND_MODE,
|
||||||
|
TEST_SOUND_MODE_2,
|
||||||
|
TEST_SOUND_MODE_NAME,
|
||||||
)
|
)
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
@ -263,6 +270,32 @@ def mock_mozart_client() -> Generator[AsyncMock]:
|
|||||||
BeolinkPeer(friendly_name=TEST_FRIENDLY_NAME_3, jid=TEST_JID_3),
|
BeolinkPeer(friendly_name=TEST_FRIENDLY_NAME_3, jid=TEST_JID_3),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
client.get_listening_mode_set = AsyncMock()
|
||||||
|
client.get_listening_mode_set.return_value = [
|
||||||
|
ListeningMode(
|
||||||
|
id=TEST_SOUND_MODE,
|
||||||
|
name=TEST_SOUND_MODE_NAME,
|
||||||
|
features=ListeningModeFeatures(),
|
||||||
|
triggers=[ListeningModeTrigger()],
|
||||||
|
),
|
||||||
|
ListeningMode(
|
||||||
|
id=TEST_SOUND_MODE_2,
|
||||||
|
name=TEST_SOUND_MODE_NAME,
|
||||||
|
features=ListeningModeFeatures(),
|
||||||
|
triggers=[ListeningModeTrigger()],
|
||||||
|
),
|
||||||
|
ListeningMode(
|
||||||
|
id=345,
|
||||||
|
name=f"{TEST_SOUND_MODE_NAME} 2",
|
||||||
|
features=ListeningModeFeatures(),
|
||||||
|
triggers=[ListeningModeTrigger()],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
client.get_active_listening_mode = AsyncMock()
|
||||||
|
client.get_active_listening_mode.return_value = ListeningModeRef(
|
||||||
|
href="",
|
||||||
|
id=123,
|
||||||
|
)
|
||||||
client.post_standby = AsyncMock()
|
client.post_standby = AsyncMock()
|
||||||
client.set_current_volume_level = AsyncMock()
|
client.set_current_volume_level = AsyncMock()
|
||||||
client.set_volume_mute = AsyncMock()
|
client.set_volume_mute = AsyncMock()
|
||||||
@ -283,6 +316,7 @@ def mock_mozart_client() -> Generator[AsyncMock]:
|
|||||||
client.post_beolink_leave = AsyncMock()
|
client.post_beolink_leave = AsyncMock()
|
||||||
client.post_beolink_allstandby = AsyncMock()
|
client.post_beolink_allstandby = AsyncMock()
|
||||||
client.join_latest_beolink_experience = AsyncMock()
|
client.join_latest_beolink_experience = AsyncMock()
|
||||||
|
client.activate_listening_mode = AsyncMock()
|
||||||
|
|
||||||
# Non-REST API client methods
|
# Non-REST API client methods
|
||||||
client.check_device_connection = AsyncMock()
|
client.check_device_connection = AsyncMock()
|
||||||
|
@ -6,6 +6,7 @@ from unittest.mock import Mock
|
|||||||
from mozart_api.exceptions import ApiException
|
from mozart_api.exceptions import ApiException
|
||||||
from mozart_api.models import (
|
from mozart_api.models import (
|
||||||
Action,
|
Action,
|
||||||
|
ListeningModeRef,
|
||||||
OverlayPlayRequest,
|
OverlayPlayRequest,
|
||||||
OverlayPlayRequestTextToSpeechTextToSpeech,
|
OverlayPlayRequestTextToSpeechTextToSpeech,
|
||||||
PlaybackContentMetadata,
|
PlaybackContentMetadata,
|
||||||
@ -197,3 +198,14 @@ TEST_DEEZER_INVALID_FLOW = ApiException(
|
|||||||
data='{"message": "Couldn\'t start user flow for me"}', # codespell:ignore
|
data='{"message": "Couldn\'t start user flow for me"}', # codespell:ignore
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
TEST_SOUND_MODE = 123
|
||||||
|
TEST_SOUND_MODE_2 = 234
|
||||||
|
TEST_SOUND_MODE_NAME = "Test Listening Mode"
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME = f"{TEST_SOUND_MODE_NAME} ({TEST_SOUND_MODE})"
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME_2 = f"{TEST_SOUND_MODE_NAME} ({TEST_SOUND_MODE_2})"
|
||||||
|
TEST_LISTENING_MODE_REF = ListeningModeRef(href="", id=TEST_SOUND_MODE_2)
|
||||||
|
TEST_SOUND_MODES = [
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME,
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME_2,
|
||||||
|
f"{TEST_SOUND_MODE_NAME} 2 (345)",
|
||||||
|
]
|
||||||
|
@ -37,6 +37,8 @@ from homeassistant.components.media_player import (
|
|||||||
ATTR_MEDIA_TRACK,
|
ATTR_MEDIA_TRACK,
|
||||||
ATTR_MEDIA_VOLUME_LEVEL,
|
ATTR_MEDIA_VOLUME_LEVEL,
|
||||||
ATTR_MEDIA_VOLUME_MUTED,
|
ATTR_MEDIA_VOLUME_MUTED,
|
||||||
|
ATTR_SOUND_MODE,
|
||||||
|
ATTR_SOUND_MODE_LIST,
|
||||||
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
DOMAIN as MEDIA_PLAYER_DOMAIN,
|
||||||
SERVICE_CLEAR_PLAYLIST,
|
SERVICE_CLEAR_PLAYLIST,
|
||||||
SERVICE_MEDIA_NEXT_TRACK,
|
SERVICE_MEDIA_NEXT_TRACK,
|
||||||
@ -45,6 +47,7 @@ from homeassistant.components.media_player import (
|
|||||||
SERVICE_MEDIA_SEEK,
|
SERVICE_MEDIA_SEEK,
|
||||||
SERVICE_MEDIA_STOP,
|
SERVICE_MEDIA_STOP,
|
||||||
SERVICE_PLAY_MEDIA,
|
SERVICE_PLAY_MEDIA,
|
||||||
|
SERVICE_SELECT_SOUND_MODE,
|
||||||
SERVICE_SELECT_SOURCE,
|
SERVICE_SELECT_SOURCE,
|
||||||
SERVICE_TURN_OFF,
|
SERVICE_TURN_OFF,
|
||||||
SERVICE_VOLUME_MUTE,
|
SERVICE_VOLUME_MUTE,
|
||||||
@ -58,6 +61,8 @@ from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
|||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME,
|
||||||
|
TEST_ACTIVE_SOUND_MODE_NAME_2,
|
||||||
TEST_AUDIO_SOURCES,
|
TEST_AUDIO_SOURCES,
|
||||||
TEST_DEEZER_FLOW,
|
TEST_DEEZER_FLOW,
|
||||||
TEST_DEEZER_INVALID_FLOW,
|
TEST_DEEZER_INVALID_FLOW,
|
||||||
@ -66,6 +71,7 @@ from .const import (
|
|||||||
TEST_FALLBACK_SOURCES,
|
TEST_FALLBACK_SOURCES,
|
||||||
TEST_FRIENDLY_NAME_2,
|
TEST_FRIENDLY_NAME_2,
|
||||||
TEST_JID_2,
|
TEST_JID_2,
|
||||||
|
TEST_LISTENING_MODE_REF,
|
||||||
TEST_MEDIA_PLAYER_ENTITY_ID,
|
TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||||
TEST_MEDIA_PLAYER_ENTITY_ID_2,
|
TEST_MEDIA_PLAYER_ENTITY_ID_2,
|
||||||
TEST_MEDIA_PLAYER_ENTITY_ID_3,
|
TEST_MEDIA_PLAYER_ENTITY_ID_3,
|
||||||
@ -79,6 +85,8 @@ from .const import (
|
|||||||
TEST_PLAYBACK_STATE_TURN_OFF,
|
TEST_PLAYBACK_STATE_TURN_OFF,
|
||||||
TEST_RADIO_STATION,
|
TEST_RADIO_STATION,
|
||||||
TEST_SEEK_POSITION_HOME_ASSISTANT_FORMAT,
|
TEST_SEEK_POSITION_HOME_ASSISTANT_FORMAT,
|
||||||
|
TEST_SOUND_MODE_2,
|
||||||
|
TEST_SOUND_MODES,
|
||||||
TEST_SOURCES,
|
TEST_SOURCES,
|
||||||
TEST_VIDEO_SOURCES,
|
TEST_VIDEO_SOURCES,
|
||||||
TEST_VOLUME,
|
TEST_VOLUME,
|
||||||
@ -113,12 +121,15 @@ async def test_initialization(
|
|||||||
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
assert states.attributes[ATTR_INPUT_SOURCE_LIST] == TEST_SOURCES
|
assert states.attributes[ATTR_INPUT_SOURCE_LIST] == TEST_SOURCES
|
||||||
assert states.attributes[ATTR_MEDIA_POSITION_UPDATED_AT]
|
assert states.attributes[ATTR_MEDIA_POSITION_UPDATED_AT]
|
||||||
|
assert states.attributes[ATTR_SOUND_MODE_LIST] == TEST_SOUND_MODES
|
||||||
|
|
||||||
# Check API calls
|
# Check API calls
|
||||||
mock_mozart_client.get_softwareupdate_status.assert_called_once()
|
mock_mozart_client.get_softwareupdate_status.assert_called_once()
|
||||||
mock_mozart_client.get_product_state.assert_called_once()
|
mock_mozart_client.get_product_state.assert_called_once()
|
||||||
mock_mozart_client.get_available_sources.assert_called_once()
|
mock_mozart_client.get_available_sources.assert_called_once()
|
||||||
mock_mozart_client.get_remote_menu.assert_called_once()
|
mock_mozart_client.get_remote_menu.assert_called_once()
|
||||||
|
mock_mozart_client.get_listening_mode_set.assert_called_once()
|
||||||
|
mock_mozart_client.get_active_listening_mode.assert_called_once()
|
||||||
|
|
||||||
|
|
||||||
async def test_async_update_sources_audio_only(
|
async def test_async_update_sources_audio_only(
|
||||||
@ -779,6 +790,69 @@ async def test_async_select_source(
|
|||||||
assert mock_mozart_client.post_remote_trigger.call_count == video_source_call
|
assert mock_mozart_client.post_remote_trigger.call_count == video_source_call
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_select_sound_mode(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_mozart_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_select_sound_mode."""
|
||||||
|
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
|
assert states.attributes[ATTR_SOUND_MODE] == TEST_ACTIVE_SOUND_MODE_NAME
|
||||||
|
|
||||||
|
active_listening_mode_callback = (
|
||||||
|
mock_mozart_client.get_active_listening_mode_notifications.call_args[0][0]
|
||||||
|
)
|
||||||
|
|
||||||
|
await hass.services.async_call(
|
||||||
|
MEDIA_PLAYER_DOMAIN,
|
||||||
|
SERVICE_SELECT_SOUND_MODE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||||
|
ATTR_SOUND_MODE: TEST_ACTIVE_SOUND_MODE_NAME_2,
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
active_listening_mode_callback(TEST_LISTENING_MODE_REF)
|
||||||
|
|
||||||
|
assert (states := hass.states.get(TEST_MEDIA_PLAYER_ENTITY_ID))
|
||||||
|
assert states.attributes[ATTR_SOUND_MODE] == TEST_ACTIVE_SOUND_MODE_NAME_2
|
||||||
|
|
||||||
|
mock_mozart_client.activate_listening_mode.assert_called_once_with(
|
||||||
|
id=TEST_SOUND_MODE_2
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def test_async_select_sound_mode_invalid(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_mozart_client: AsyncMock,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test async_select_sound_mode with an invalid sound_mode."""
|
||||||
|
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
|
||||||
|
with pytest.raises(ServiceValidationError) as exc_info:
|
||||||
|
await hass.services.async_call(
|
||||||
|
MEDIA_PLAYER_DOMAIN,
|
||||||
|
SERVICE_SELECT_SOUND_MODE,
|
||||||
|
{
|
||||||
|
ATTR_ENTITY_ID: TEST_MEDIA_PLAYER_ENTITY_ID,
|
||||||
|
ATTR_SOUND_MODE: "invalid_sound_mode",
|
||||||
|
},
|
||||||
|
blocking=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert exc_info.value.translation_domain == DOMAIN
|
||||||
|
assert exc_info.value.translation_key == "invalid_sound_mode"
|
||||||
|
assert exc_info.errisinstance(ServiceValidationError)
|
||||||
|
|
||||||
|
|
||||||
async def test_async_play_media_invalid_type(
|
async def test_async_play_media_invalid_type(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_mozart_client: AsyncMock,
|
mock_mozart_client: AsyncMock,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user