From f7f8672eeacbaa4bba50b948b51fcfd7a858bbd7 Mon Sep 17 00:00:00 2001 From: Aaron David Schneider Date: Thu, 27 May 2021 19:56:59 +0200 Subject: [PATCH] Add tests for sonos switch platform (#51142) * add tests * refactor async_added_to_hass * fix tests and race condition * use async_get * typo --- homeassistant/components/sonos/speaker.py | 2 - homeassistant/components/sonos/switch.py | 18 ++++++--- tests/components/sonos/conftest.py | 48 +++++++++++++++++++---- tests/components/sonos/test_switch.py | 35 +++++++++++++++-- 4 files changed, 84 insertions(+), 19 deletions(-) diff --git a/homeassistant/components/sonos/speaker.py b/homeassistant/components/sonos/speaker.py index c1f1dbb9104..bb6cb306426 100644 --- a/homeassistant/components/sonos/speaker.py +++ b/homeassistant/components/sonos/speaker.py @@ -420,8 +420,6 @@ class SonosSpeaker: async_dispatcher_send(self.hass, SONOS_ALARM_UPDATE, self) - self.async_write_entity_states() - async def async_update_battery_info(self, battery_dict: dict[str, Any]) -> None: """Update battery info using the decoded SonosEvent.""" self._last_battery_event = dt_util.utcnow() diff --git a/homeassistant/components/sonos/switch.py b/homeassistant/components/sonos/switch.py index 967bc21da59..879d5fa0a99 100644 --- a/homeassistant/components/sonos/switch.py +++ b/homeassistant/components/sonos/switch.py @@ -43,11 +43,6 @@ async def async_setup_entry(hass, config_entry, async_add_entities): entity = SonosAlarmEntity(alarm_id, speaker) async_add_entities([entity]) configured_alarms.add(alarm_id) - config_entry.async_on_unload( - async_dispatcher_connect( - hass, SONOS_ALARM_UPDATE, entity.async_update - ) - ) config_entry.async_on_unload( async_dispatcher_connect(hass, SONOS_CREATE_ALARM, _async_create_entity) @@ -64,9 +59,20 @@ class SonosAlarmEntity(SonosEntity, SwitchEntity): self._alarm_id = alarm_id self.entity_id = ENTITY_ID_FORMAT.format(f"sonos_alarm_{self.alarm_id}") + async def async_added_to_hass(self) -> None: + """Handle switch setup when added to hass.""" + await super().async_added_to_hass() + self.async_on_remove( + async_dispatcher_connect( + self.hass, + SONOS_ALARM_UPDATE, + self.async_update, + ) + ) + @property def alarm(self): - """Return the ID of the alarm.""" + """Return the alarm instance.""" return self.hass.data[DATA_SONOS].alarms[self.alarm_id] @property diff --git a/tests/components/sonos/conftest.py b/tests/components/sonos/conftest.py index 2feb2b54896..aa14dcaa5cf 100644 --- a/tests/components/sonos/conftest.py +++ b/tests/components/sonos/conftest.py @@ -30,7 +30,7 @@ def config_entry_fixture(): @pytest.fixture(name="soco") def soco_fixture( - music_library, speaker_info, battery_info, dummy_soco_service, alarmClock + music_library, speaker_info, battery_info, dummy_soco_service, alarm_clock ): """Create a mock pysonos SoCo fixture.""" with patch("pysonos.SoCo", autospec=True) as mock, patch( @@ -46,7 +46,7 @@ def soco_fixture( mock_soco.zoneGroupTopology = dummy_soco_service mock_soco.contentDirectory = dummy_soco_service mock_soco.deviceProperties = dummy_soco_service - mock_soco.alarmClock = alarmClock + mock_soco.alarmClock = alarm_clock mock_soco.mute = False mock_soco.night_mode = True mock_soco.dialog_mode = True @@ -90,12 +90,28 @@ def music_library_fixture(): return music_library -@pytest.fixture(name="alarmClock") -def alarmClock_fixture(): +@pytest.fixture(name="alarm_clock") +def alarm_clock_fixture(): """Create alarmClock fixture.""" - alarmClock = Mock() - alarmClock.subscribe = AsyncMock() - alarmClock.ListAlarms.return_value = { + alarm_clock = Mock() + alarm_clock.subscribe = AsyncMock() + alarm_clock.ListAlarms.return_value = { + "CurrentAlarmList": "" + '' + " " + } + return alarm_clock + + +@pytest.fixture(name="alarm_clock_extended") +def alarm_clock_fixture_extended(): + """Create alarmClock fixture.""" + alarm_clock = Mock() + alarm_clock.subscribe = AsyncMock() + alarm_clock.ListAlarms.return_value = { "CurrentAlarmList": "" '' " " } - return alarmClock + return alarm_clock @pytest.fixture(name="speaker_info") @@ -141,3 +157,19 @@ def battery_event_fixture(soco): "more_info": "BattChg:NOT_CHARGING,RawBattPct:100,BattPct:100,BattTmp:25", } return SonosMockEvent(soco, variables) + + +@pytest.fixture(name="alarm_event") +def alarm_event_fixture(soco): + """Create alarm_event fixture.""" + variables = { + "time_zone": "ffc40a000503000003000502ffc4", + "time_server": "0.sonostime.pool.ntp.org,1.sonostime.pool.ntp.org,2.sonostime.pool.ntp.org,3.sonostime.pool.ntp.org", + "time_generation": "20000001", + "alarm_list_version": "RINCON_test", + "time_format": "INV", + "date_format": "INV", + "daily_index_refresh_time": None, + } + + return SonosMockEvent(soco, variables) diff --git a/tests/components/sonos/test_switch.py b/tests/components/sonos/test_switch.py index c33c472ee27..d4448d22b32 100644 --- a/tests/components/sonos/test_switch.py +++ b/tests/components/sonos/test_switch.py @@ -9,17 +9,18 @@ from homeassistant.components.sonos.switch import ( ATTR_VOLUME, ) from homeassistant.const import ATTR_TIME, STATE_ON +from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry from homeassistant.setup import async_setup_component async def setup_platform(hass, config_entry, config): - """Set up the media player platform for testing.""" + """Set up the switch platform for testing.""" config_entry.add_to_hass(hass) assert await async_setup_component(hass, DOMAIN, config) await hass.async_block_till_done() -async def test_entity_registry(hass, config_entry, config, soco): +async def test_entity_registry(hass, config_entry, config): """Test sonos device with alarm registered in the device registry.""" await setup_platform(hass, config_entry, config) @@ -29,7 +30,7 @@ async def test_entity_registry(hass, config_entry, config, soco): assert "switch.sonos_alarm_14" in entity_registry.entities -async def test_alarm_attributes(hass, config_entry, config, soco): +async def test_alarm_attributes(hass, config_entry, config): """Test for correct sonos alarm state.""" await setup_platform(hass, config_entry, config) @@ -45,3 +46,31 @@ async def test_alarm_attributes(hass, config_entry, config, soco): assert alarm_state.attributes.get(ATTR_VOLUME) == 0.25 assert alarm_state.attributes.get(ATTR_PLAY_MODE) == "SHUFFLE_NOREPEAT" assert not alarm_state.attributes.get(ATTR_INCLUDE_LINKED_ZONES) + + +async def test_alarm_create_delete( + hass, config_entry, config, soco, alarm_clock, alarm_clock_extended, alarm_event +): + """Test for correct creation and deletion of alarms during runtime.""" + soco.alarmClock = alarm_clock_extended + + await setup_platform(hass, config_entry, config) + + subscription = alarm_clock_extended.subscribe.return_value + sub_callback = subscription.callback + + sub_callback(event=alarm_event) + await hass.async_block_till_done() + + entity_registry = async_get_entity_registry(hass) + + assert "switch.sonos_alarm_14" in entity_registry.entities + assert "switch.sonos_alarm_15" in entity_registry.entities + + alarm_clock_extended.ListAlarms.return_value = alarm_clock.ListAlarms.return_value + + sub_callback(event=alarm_event) + await hass.async_block_till_done() + + assert "switch.sonos_alarm_14" in entity_registry.entities + assert "switch.sonos_alarm_15" not in entity_registry.entities