diff --git a/homeassistant/components/sonos/media_player.py b/homeassistant/components/sonos/media_player.py index ff6981cfc9b5e81f2a185e487ceca9b5b1f381d0..e68d3dfa97ac9f673cd6f8f419b24cd9afdd0914 100644 --- a/homeassistant/components/sonos/media_player.py +++ b/homeassistant/components/sonos/media_player.py @@ -542,8 +542,13 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity): raise HomeAssistantError( f"Error when calling Sonos websocket: {exc}" ) from exc - if response["success"]: + if response.get("success"): return + raise HomeAssistantError( + translation_domain=SONOS_DOMAIN, + translation_key="announce_media_error", + translation_placeholders={"media_id": media_id, "response": response}, + ) if spotify.is_spotify_media_type(media_type): media_type = spotify.resolve_spotify_media_type(media_type) diff --git a/homeassistant/components/sonos/strings.json b/homeassistant/components/sonos/strings.json index 5833e5a27f2439cc9a9056b1744a092776abc393..7a73378d69b00013e7a8d232217733eb972ba6c4 100644 --- a/homeassistant/components/sonos/strings.json +++ b/homeassistant/components/sonos/strings.json @@ -180,6 +180,9 @@ }, "invalid_sonos_playlist": { "message": "Could not find Sonos playlist: {name}" + }, + "announce_media_error": { + "message": "Announcing clip {media_id} failed {response}" } } } diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 996fd4c6663f109f5ef8f77c983f00cab2460d4d..6abb010557ef492cc0f730a5011e5a0e4d0cd007 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -255,6 +255,19 @@ def soco_sharelink(): yield mock_instance +@pytest.fixture(name="sonos_websocket") +def sonos_websocket(): + """Fixture to mock SonosWebSocket.""" + with patch( + "homeassistant.components.sonos.speaker.SonosWebsocket" + ) as mock_sonos_ws: + mock_instance = AsyncMock() + mock_instance.play_clip = AsyncMock() + mock_instance.play_clip.return_value = [{"success": 1}, {}] + mock_sonos_ws.return_value = mock_instance + yield mock_instance + + @pytest.fixture(name="soco_factory") def soco_factory( music_library, @@ -263,6 +276,7 @@ def soco_factory( battery_info, alarm_clock, sonos_playlists: SearchResult, + sonos_websocket, ): """Create factory for instantiating SoCo mocks.""" factory = SoCoMockFactory( diff --git a/tests/components/sonos/test_media_player.py b/tests/components/sonos/test_media_player.py index b29ae1b6cfb4233f42dbe9ad75e9cc45fb189657..fa77293fbdef28607be4bbc2098b92de5250bc38 100644 --- a/tests/components/sonos/test_media_player.py +++ b/tests/components/sonos/test_media_player.py @@ -5,13 +5,16 @@ from unittest.mock import patch import pytest from soco.data_structures import SearchResult +from sonos_websocket.exception import SonosWebsocketError from syrupy import SnapshotAssertion from homeassistant.components.media_player import ( ATTR_INPUT_SOURCE, + ATTR_MEDIA_ANNOUNCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_ENQUEUE, + ATTR_MEDIA_EXTRA, ATTR_MEDIA_REPEAT, ATTR_MEDIA_SHUFFLE, ATTR_MEDIA_VOLUME_LEVEL, @@ -47,7 +50,7 @@ from homeassistant.const import ( SERVICE_VOLUME_UP, ) from homeassistant.core import HomeAssistant -from homeassistant.exceptions import ServiceValidationError +from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.helpers import entity_registry as er from homeassistant.helpers.device_registry import ( CONNECTION_NETWORK_MAC, @@ -1050,3 +1053,71 @@ async def test_media_transport( blocking=True, ) assert getattr(soco, client_call).call_count == 1 + + +async def test_play_media_announce( + hass: HomeAssistant, + soco: MockSoCo, + async_autosetup_sonos, + sonos_websocket, +) -> None: + """Test playing media with the announce.""" + content_id: str = "http://10.0.0.1:8123/local/sounds/doorbell.mp3" + volume: float = 0.30 + + # Test the success path + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: "media_player.zone_a", + ATTR_MEDIA_CONTENT_TYPE: "music", + ATTR_MEDIA_CONTENT_ID: content_id, + ATTR_MEDIA_ANNOUNCE: True, + ATTR_MEDIA_EXTRA: {"volume": volume}, + }, + blocking=True, + ) + assert sonos_websocket.play_clip.call_count == 1 + sonos_websocket.play_clip.assert_called_with(content_id, volume=volume) + + # Test receiving a websocket exception + sonos_websocket.play_clip.reset_mock() + sonos_websocket.play_clip.side_effect = SonosWebsocketError("Error Message") + with pytest.raises( + HomeAssistantError, match="Error when calling Sonos websocket: Error Message" + ): + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: "media_player.zone_a", + ATTR_MEDIA_CONTENT_TYPE: "music", + ATTR_MEDIA_CONTENT_ID: content_id, + ATTR_MEDIA_ANNOUNCE: True, + }, + blocking=True, + ) + assert sonos_websocket.play_clip.call_count == 1 + sonos_websocket.play_clip.assert_called_with(content_id, volume=None) + + # Test receiving a non success result + sonos_websocket.play_clip.reset_mock() + sonos_websocket.play_clip.side_effect = None + retval = {"success": 0} + sonos_websocket.play_clip.return_value = [retval, {}] + with pytest.raises( + HomeAssistantError, match=f"Announcing clip {content_id} failed {retval}" + ): + await hass.services.async_call( + MP_DOMAIN, + SERVICE_PLAY_MEDIA, + { + ATTR_ENTITY_ID: "media_player.zone_a", + ATTR_MEDIA_CONTENT_TYPE: "music", + ATTR_MEDIA_CONTENT_ID: content_id, + ATTR_MEDIA_ANNOUNCE: True, + }, + blocking=True, + ) + assert sonos_websocket.play_clip.call_count == 1