-
-
Notifications
You must be signed in to change notification settings - Fork 37.5k
Add tests for join and unjoin service calls in Sonos #145602
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
877077e
94786df
e532087
6c8eb91
f76f191
4e74d0b
4285df1
65902d6
98eeac8
cc262d8
e3bbdbb
b104f32
153febd
13e0993
b4e1270
1d6eed3
f7c3737
3bee01b
a2518ff
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,53 +1,191 @@ | ||
| """Tests for Sonos services.""" | ||
|
|
||
| import asyncio | ||
| from contextlib import asynccontextmanager | ||
| import logging | ||
| import re | ||
| from unittest.mock import Mock, patch | ||
|
|
||
| import pytest | ||
|
|
||
| from homeassistant.components.media_player import DOMAIN as MP_DOMAIN, SERVICE_JOIN | ||
| from homeassistant.components.media_player import ( | ||
| DOMAIN as MP_DOMAIN, | ||
| SERVICE_JOIN, | ||
| SERVICE_UNJOIN, | ||
| ) | ||
| from homeassistant.core import HomeAssistant | ||
| from homeassistant.exceptions import HomeAssistantError | ||
|
|
||
| from tests.common import MockConfigEntry | ||
| from .conftest import MockSoCo, group_speakers, ungroup_speakers | ||
|
|
||
|
|
||
| async def test_media_player_join( | ||
| hass: HomeAssistant, async_autosetup_sonos, config_entry: MockConfigEntry | ||
| hass: HomeAssistant, | ||
| sonos_setup_two_speakers: list[MockSoCo], | ||
| caplog: pytest.LogCaptureFixture, | ||
| ) -> None: | ||
| """Test join service.""" | ||
| valid_entity_id = "media_player.zone_a" | ||
| mocked_entity_id = "media_player.mocked" | ||
| """Test joining two speakers together.""" | ||
| soco_living_room = sonos_setup_two_speakers[0] | ||
| soco_bedroom = sonos_setup_two_speakers[1] | ||
|
|
||
| # After dispatching the join to the speakers, the integration waits for the | ||
| # group to be updated before returning. To simulate this we will dispatch | ||
| # a ZGS event to group the speaker. This event is | ||
| # triggered by the firing of the join_complete_event in the join mock. | ||
| join_complete_event = asyncio.Event() | ||
|
|
||
| def mock_join(*args, **kwargs) -> None: | ||
| hass.loop.call_soon_threadsafe(join_complete_event.set) | ||
|
|
||
| soco_bedroom.join = Mock(side_effect=mock_join) | ||
|
|
||
| with caplog.at_level(logging.WARNING): | ||
| caplog.clear() | ||
| await hass.services.async_call( | ||
| MP_DOMAIN, | ||
| SERVICE_JOIN, | ||
| { | ||
| "entity_id": "media_player.living_room", | ||
| "group_members": ["media_player.bedroom"], | ||
| }, | ||
| blocking=False, | ||
| ) | ||
| await join_complete_event.wait() | ||
| # Fire the ZGS event to update the speaker grouping as the join method is waiting | ||
| # for the speakers to be regrouped. | ||
| group_speakers(soco_living_room, soco_bedroom) | ||
| await hass.async_block_till_done(wait_background_tasks=True) | ||
|
Comment on lines
+42
to
+57
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the joining failed I think we should raise a
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, that is a good change. |
||
|
|
||
| # Code logs warning messages if the join is not successful, so we check | ||
| # that no warning messages were logged. | ||
| assert len(caplog.records) == 0 | ||
| # The API joins the group members to the entity_id speaker. | ||
| assert soco_bedroom.join.call_count == 1 | ||
| assert soco_bedroom.join.call_args[0][0] == soco_living_room | ||
| assert soco_living_room.join.call_count == 0 | ||
|
|
||
|
|
||
| async def test_media_player_join_bad_entity( | ||
| hass: HomeAssistant, | ||
| sonos_setup_two_speakers: list[MockSoCo], | ||
| ) -> None: | ||
| """Test error handling of joining with a bad entity.""" | ||
|
|
||
| # Ensure an error is raised if the entity is unknown | ||
| with pytest.raises(HomeAssistantError): | ||
| with pytest.raises(HomeAssistantError) as excinfo: | ||
| await hass.services.async_call( | ||
| MP_DOMAIN, | ||
| SERVICE_JOIN, | ||
| {"entity_id": valid_entity_id, "group_members": mocked_entity_id}, | ||
| { | ||
| "entity_id": "media_player.living_room", | ||
| "group_members": "media_player.bad_entity", | ||
| }, | ||
| blocking=True, | ||
| ) | ||
| assert "media_player.bad_entity" in str(excinfo.value) | ||
|
|
||
|
|
||
| # Ensure SonosSpeaker.join_multi is called if entity is found | ||
| mocked_speaker = Mock() | ||
| mock_entity_id_mappings = {mocked_entity_id: mocked_speaker} | ||
| @asynccontextmanager | ||
| async def instant_timeout(*args, **kwargs) -> None: | ||
| """Mock a timeout error.""" | ||
| raise TimeoutError | ||
| # This is never reached, but is needed to satisfy the asynccontextmanager | ||
| yield # pylint: disable=unreachable | ||
|
|
||
|
|
||
| async def test_media_player_join_timeout( | ||
| hass: HomeAssistant, | ||
| sonos_setup_two_speakers: list[MockSoCo], | ||
| caplog: pytest.LogCaptureFixture, | ||
| ) -> None: | ||
| """Test joining of two speakers with timeout error.""" | ||
|
|
||
| soco_living_room = sonos_setup_two_speakers[0] | ||
| soco_bedroom = sonos_setup_two_speakers[1] | ||
|
|
||
| expected = ( | ||
| "Timeout while waiting for Sonos player to join the " | ||
| "group ['Living Room: Living Room, Bedroom']" | ||
| ) | ||
| with ( | ||
| patch.dict( | ||
| config_entry.runtime_data.entity_id_mappings, | ||
| mock_entity_id_mappings, | ||
| ), | ||
| patch( | ||
| "homeassistant.components.sonos.speaker.SonosSpeaker.join_multi" | ||
| ) as mock_join_multi, | ||
| "homeassistant.components.sonos.speaker.asyncio.timeout", instant_timeout | ||
| ), | ||
| pytest.raises(HomeAssistantError, match=re.escape(expected)), | ||
| ): | ||
| await hass.services.async_call( | ||
| MP_DOMAIN, | ||
| SERVICE_JOIN, | ||
| {"entity_id": valid_entity_id, "group_members": mocked_entity_id}, | ||
| { | ||
| "entity_id": "media_player.living_room", | ||
| "group_members": ["media_player.bedroom"], | ||
| }, | ||
| blocking=True, | ||
| ) | ||
| assert soco_bedroom.join.call_count == 1 | ||
| assert soco_bedroom.join.call_args[0][0] == soco_living_room | ||
| assert soco_living_room.join.call_count == 0 | ||
|
|
||
| found_speaker = config_entry.runtime_data.entity_id_mappings[valid_entity_id] | ||
| mock_join_multi.assert_called_with( | ||
| hass, config_entry, found_speaker, [mocked_speaker] | ||
|
|
||
| async def test_media_player_unjoin( | ||
| hass: HomeAssistant, | ||
| sonos_setup_two_speakers: list[MockSoCo], | ||
| caplog: pytest.LogCaptureFixture, | ||
| ) -> None: | ||
| """Test unjoing two speaker.""" | ||
| soco_living_room = sonos_setup_two_speakers[0] | ||
| soco_bedroom = sonos_setup_two_speakers[1] | ||
|
|
||
| # First group the speakers together | ||
| group_speakers(soco_living_room, soco_bedroom) | ||
| await hass.async_block_till_done(wait_background_tasks=True) | ||
|
|
||
| # Now that the speaker are joined, test unjoining | ||
| unjoin_complete_event = asyncio.Event() | ||
|
|
||
| def mock_unjoin(*args, **kwargs): | ||
| hass.loop.call_soon_threadsafe(unjoin_complete_event.set) | ||
|
|
||
| soco_bedroom.unjoin = Mock(side_effect=mock_unjoin) | ||
|
|
||
| with caplog.at_level(logging.WARNING): | ||
| caplog.clear() | ||
| await hass.services.async_call( | ||
| MP_DOMAIN, | ||
| SERVICE_UNJOIN, | ||
| {"entity_id": "media_player.bedroom"}, | ||
| blocking=False, | ||
| ) | ||
| await unjoin_complete_event.wait() | ||
| # Fire the ZGS event to ungroup the speakers as the unjoin method is waiting | ||
| # for the speakers to be ungrouped. | ||
| ungroup_speakers(soco_living_room, soco_bedroom) | ||
| await hass.async_block_till_done(wait_background_tasks=True) | ||
|
|
||
| assert len(caplog.records) == 0 | ||
| assert soco_bedroom.unjoin.call_count == 1 | ||
| assert soco_living_room.unjoin.call_count == 0 | ||
|
|
||
|
|
||
| async def test_media_player_unjoin_already_unjoined( | ||
| hass: HomeAssistant, | ||
| sonos_setup_two_speakers: list[MockSoCo], | ||
| caplog: pytest.LogCaptureFixture, | ||
| ) -> None: | ||
| """Test unjoining when already unjoined.""" | ||
| soco_living_room = sonos_setup_two_speakers[0] | ||
| soco_bedroom = sonos_setup_two_speakers[1] | ||
|
|
||
| with caplog.at_level(logging.WARNING): | ||
| caplog.clear() | ||
| await hass.services.async_call( | ||
| MP_DOMAIN, | ||
| SERVICE_UNJOIN, | ||
| {"entity_id": "media_player.bedroom"}, | ||
| blocking=True, | ||
| ) | ||
|
|
||
| assert len(caplog.records) == 0 | ||
| # Should not have called unjoin, since the speakers are already unjoined. | ||
| assert soco_bedroom.unjoin.call_count == 0 | ||
| assert soco_living_room.unjoin.call_count == 0 | ||
Uh oh!
There was an error while loading. Please reload this page.