mirror of
https://github.com/home-assistant/core.git
synced 2025-11-12 12:30:31 +00:00
340 lines
12 KiB
Python
340 lines
12 KiB
Python
"""Tests for the Sonos Alarm switch platform."""
|
|
|
|
from copy import copy
|
|
from datetime import timedelta
|
|
from unittest.mock import patch
|
|
|
|
import pytest
|
|
|
|
from homeassistant.components.sonos import DOMAIN
|
|
from homeassistant.components.sonos.const import (
|
|
DATA_SONOS_DISCOVERY_MANAGER,
|
|
MODEL_SONOS_ARC_ULTRA,
|
|
)
|
|
from homeassistant.components.sonos.switch import (
|
|
ATTR_DURATION,
|
|
ATTR_ID,
|
|
ATTR_INCLUDE_LINKED_ZONES,
|
|
ATTR_PLAY_MODE,
|
|
ATTR_RECURRENCE,
|
|
ATTR_SPEECH_ENHANCEMENT,
|
|
ATTR_SPEECH_ENHANCEMENT_ENABLED,
|
|
ATTR_VOLUME,
|
|
)
|
|
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
|
|
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
|
|
from homeassistant.const import (
|
|
ATTR_ENTITY_ID,
|
|
ATTR_TIME,
|
|
SERVICE_TURN_OFF,
|
|
SERVICE_TURN_ON,
|
|
STATE_OFF,
|
|
STATE_ON,
|
|
)
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
|
from homeassistant.setup import async_setup_component
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
from .conftest import (
|
|
MockSoCo,
|
|
SoCoMockFactory,
|
|
SonosMockEvent,
|
|
SonosMockService,
|
|
create_rendering_control_event,
|
|
)
|
|
|
|
from tests.common import async_fire_time_changed
|
|
|
|
|
|
async def test_entity_registry(
|
|
hass: HomeAssistant, async_autosetup_sonos, entity_registry: er.EntityRegistry
|
|
) -> None:
|
|
"""Test sonos device with alarm registered in the device registry."""
|
|
assert "media_player.zone_a" in entity_registry.entities
|
|
assert "switch.sonos_alarm_14" in entity_registry.entities
|
|
assert "switch.zone_a_status_light" in entity_registry.entities
|
|
assert "switch.zone_a_loudness" in entity_registry.entities
|
|
assert "switch.zone_a_night_sound" in entity_registry.entities
|
|
assert "switch.zone_a_speech_enhancement" in entity_registry.entities
|
|
assert "switch.zone_a_subwoofer_enabled" in entity_registry.entities
|
|
assert "switch.zone_a_surround_enabled" in entity_registry.entities
|
|
assert "switch.zone_a_touch_controls" in entity_registry.entities
|
|
|
|
|
|
async def test_switch_attributes(
|
|
hass: HomeAssistant,
|
|
async_autosetup_sonos,
|
|
soco,
|
|
fire_zgs_event,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Test for correct Sonos switch states."""
|
|
alarm = entity_registry.entities["switch.sonos_alarm_14"]
|
|
alarm_state = hass.states.get(alarm.entity_id)
|
|
assert alarm_state.state == STATE_ON
|
|
assert alarm_state.attributes.get(ATTR_TIME) == "07:00:00"
|
|
assert alarm_state.attributes.get(ATTR_ID) == "14"
|
|
assert alarm_state.attributes.get(ATTR_DURATION) == "02:00:00"
|
|
assert alarm_state.attributes.get(ATTR_RECURRENCE) == "DAILY"
|
|
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)
|
|
|
|
surround_music_full_volume = entity_registry.entities[
|
|
"switch.zone_a_surround_music_full_volume"
|
|
]
|
|
surround_music_full_volume_state = hass.states.get(
|
|
surround_music_full_volume.entity_id
|
|
)
|
|
assert surround_music_full_volume_state.state == STATE_ON
|
|
|
|
night_sound = entity_registry.entities["switch.zone_a_night_sound"]
|
|
night_sound_state = hass.states.get(night_sound.entity_id)
|
|
assert night_sound_state.state == STATE_ON
|
|
|
|
loudness = entity_registry.entities["switch.zone_a_loudness"]
|
|
loudness_state = hass.states.get(loudness.entity_id)
|
|
assert loudness_state.state == STATE_ON
|
|
|
|
speech_enhancement = entity_registry.entities["switch.zone_a_speech_enhancement"]
|
|
speech_enhancement_state = hass.states.get(speech_enhancement.entity_id)
|
|
assert speech_enhancement_state.state == STATE_ON
|
|
|
|
crossfade = entity_registry.entities["switch.zone_a_crossfade"]
|
|
crossfade_state = hass.states.get(crossfade.entity_id)
|
|
assert crossfade_state.state == STATE_ON
|
|
|
|
# Ensure switches are disabled
|
|
status_light = entity_registry.entities["switch.zone_a_status_light"]
|
|
assert hass.states.get(status_light.entity_id) is None
|
|
|
|
touch_controls = entity_registry.entities["switch.zone_a_touch_controls"]
|
|
assert hass.states.get(touch_controls.entity_id) is None
|
|
|
|
sub_switch = entity_registry.entities["switch.zone_a_subwoofer_enabled"]
|
|
sub_switch_state = hass.states.get(sub_switch.entity_id)
|
|
assert sub_switch_state.state == STATE_OFF
|
|
|
|
surround_switch = entity_registry.entities["switch.zone_a_surround_enabled"]
|
|
surround_switch_state = hass.states.get(surround_switch.entity_id)
|
|
assert surround_switch_state.state == STATE_ON
|
|
|
|
# Enable disabled switches
|
|
for entity in (status_light, touch_controls):
|
|
entity_registry.async_update_entity(
|
|
entity_id=entity.entity_id, disabled_by=None
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
# Fire event to cancel poll timer and avoid triggering errors during time jump
|
|
service = soco.contentDirectory
|
|
empty_event = SonosMockEvent(soco, service, {})
|
|
subscription = service.subscribe.return_value
|
|
subscription.callback(event=empty_event)
|
|
await hass.async_block_till_done()
|
|
|
|
# Mock shutdown calls during config entry reload
|
|
with patch.object(hass.data[DATA_SONOS_DISCOVERY_MANAGER], "async_shutdown") as m:
|
|
async_fire_time_changed(
|
|
hass,
|
|
dt_util.utcnow() + timedelta(seconds=RELOAD_AFTER_UPDATE_DELAY + 1),
|
|
)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
assert m.called
|
|
|
|
# Trigger subscription callback for speaker discovery
|
|
await fire_zgs_event()
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
status_light_state = hass.states.get(status_light.entity_id)
|
|
assert status_light_state.state == STATE_ON
|
|
|
|
touch_controls = entity_registry.entities["switch.zone_a_touch_controls"]
|
|
touch_controls_state = hass.states.get(touch_controls.entity_id)
|
|
assert touch_controls_state.state == STATE_ON
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("model", "attribute"),
|
|
[
|
|
("Sonos One SL", ATTR_SPEECH_ENHANCEMENT),
|
|
(MODEL_SONOS_ARC_ULTRA.lower(), ATTR_SPEECH_ENHANCEMENT_ENABLED),
|
|
],
|
|
)
|
|
async def test_switch_speech_enhancement(
|
|
hass: HomeAssistant,
|
|
async_setup_sonos,
|
|
soco: MockSoCo,
|
|
speaker_info: dict[str, str],
|
|
entity_registry: er.EntityRegistry,
|
|
model: str,
|
|
attribute: str,
|
|
) -> None:
|
|
"""Tests the speech enhancement switch and attribute substitution for different models."""
|
|
entity_id = "switch.zone_a_speech_enhancement"
|
|
speaker_info["model_name"] = model
|
|
soco.get_speaker_info.return_value = speaker_info
|
|
setattr(soco, attribute, True)
|
|
await async_setup_sonos()
|
|
switch = entity_registry.entities[entity_id]
|
|
state = hass.states.get(switch.entity_id)
|
|
assert state.state == STATE_ON
|
|
|
|
event = create_rendering_control_event(soco)
|
|
event.variables[attribute] = False
|
|
soco.renderingControl.subscribe.return_value._callback(event)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
state = hass.states.get(switch.entity_id)
|
|
assert state.state == STATE_OFF
|
|
|
|
await hass.services.async_call(
|
|
SWITCH_DOMAIN,
|
|
SERVICE_TURN_ON,
|
|
{ATTR_ENTITY_ID: entity_id},
|
|
blocking=True,
|
|
)
|
|
assert getattr(soco, attribute) is True
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("service", "expected_result"),
|
|
[
|
|
(SERVICE_TURN_OFF, "0"),
|
|
(SERVICE_TURN_ON, "1"),
|
|
],
|
|
)
|
|
async def test_switch_alarm_turn_on(
|
|
hass: HomeAssistant,
|
|
async_setup_sonos,
|
|
soco: MockSoCo,
|
|
service: str,
|
|
expected_result: str,
|
|
) -> None:
|
|
"""Test enabling and disabling of alarm."""
|
|
await async_setup_sonos()
|
|
|
|
await hass.services.async_call(
|
|
SWITCH_DOMAIN, service, {ATTR_ENTITY_ID: "switch.sonos_alarm_14"}, blocking=True
|
|
)
|
|
|
|
assert soco.alarmClock.UpdateAlarm.call_count == 1
|
|
call_args = soco.alarmClock.UpdateAlarm.call_args[0]
|
|
assert call_args[0][0] == ("ID", "14")
|
|
assert call_args[0][4] == ("Enabled", expected_result)
|
|
|
|
|
|
async def test_alarm_create_delete(
|
|
hass: HomeAssistant,
|
|
async_setup_sonos,
|
|
soco,
|
|
alarm_clock,
|
|
alarm_clock_extended,
|
|
alarm_event,
|
|
entity_registry: er.EntityRegistry,
|
|
) -> None:
|
|
"""Test for correct creation and deletion of alarms during runtime."""
|
|
one_alarm = copy(alarm_clock.ListAlarms.return_value)
|
|
two_alarms = copy(alarm_clock_extended.ListAlarms.return_value)
|
|
|
|
await async_setup_sonos()
|
|
|
|
assert "switch.sonos_alarm_14" in entity_registry.entities
|
|
assert "switch.sonos_alarm_15" not in entity_registry.entities
|
|
|
|
subscription = alarm_clock.subscribe.return_value
|
|
sub_callback = subscription.callback
|
|
|
|
alarm_clock.ListAlarms.return_value = two_alarms
|
|
|
|
alarm_event.variables["alarm_list_version"] = two_alarms["CurrentAlarmListVersion"]
|
|
|
|
sub_callback(event=alarm_event)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert "switch.sonos_alarm_14" in entity_registry.entities
|
|
assert "switch.sonos_alarm_15" in entity_registry.entities
|
|
|
|
one_alarm["CurrentAlarmListVersion"] = alarm_event.increment_variable(
|
|
"alarm_list_version"
|
|
)
|
|
|
|
alarm_clock.ListAlarms.return_value = one_alarm
|
|
|
|
sub_callback(event=alarm_event)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert "switch.sonos_alarm_14" in entity_registry.entities
|
|
assert "switch.sonos_alarm_15" not in entity_registry.entities
|
|
|
|
|
|
async def test_alarm_change_device(
|
|
hass: HomeAssistant,
|
|
alarm_clock: SonosMockService,
|
|
alarm_clock_extended: SonosMockService,
|
|
alarm_event: SonosMockEvent,
|
|
entity_registry: er.EntityRegistry,
|
|
device_registry: dr.DeviceRegistry,
|
|
soco_factory: SoCoMockFactory,
|
|
) -> None:
|
|
"""Test Sonos Alarm being moved to a different speaker.
|
|
|
|
This test simulates a scenario where an alarm is created on one speaker
|
|
and then moved to another speaker. It checks that the entity is correctly
|
|
created on the new speaker and removed from the old one.
|
|
"""
|
|
|
|
# Create the alarm on the soco_lr speaker
|
|
soco_factory.mock_zones = True
|
|
soco_lr = soco_factory.cache_mock(MockSoCo(), "10.10.10.1", "Living Room")
|
|
alarm_dict = copy(alarm_clock.ListAlarms.return_value)
|
|
alarm_dict["CurrentAlarmList"] = alarm_dict["CurrentAlarmList"].replace(
|
|
"RINCON_test", f"{soco_lr.uid}"
|
|
)
|
|
alarm_dict["CurrentAlarmListVersion"] = "RINCON_test:900"
|
|
soco_lr.alarmClock.ListAlarms.return_value = alarm_dict
|
|
soco_br = soco_factory.cache_mock(MockSoCo(), "10.10.10.2", "Bedroom")
|
|
await async_setup_component(
|
|
hass,
|
|
DOMAIN,
|
|
{
|
|
DOMAIN: {
|
|
"media_player": {
|
|
"interface_addr": "127.0.0.1",
|
|
"hosts": ["10.10.10.1", "10.10.10.2"],
|
|
}
|
|
}
|
|
},
|
|
)
|
|
await hass.async_block_till_done()
|
|
|
|
entity_id = "switch.sonos_alarm_14"
|
|
|
|
# Verify the alarm is created on the soco_lr speaker
|
|
assert entity_id in entity_registry.entities
|
|
entity = entity_registry.async_get(entity_id)
|
|
device = device_registry.async_get(entity.device_id)
|
|
assert device.name == soco_lr.get_speaker_info()["zone_name"]
|
|
|
|
# Simulate the alarm being moved to the soco_br speaker
|
|
alarm_update = copy(alarm_clock_extended.ListAlarms.return_value)
|
|
alarm_update["CurrentAlarmList"] = alarm_update["CurrentAlarmList"].replace(
|
|
"RINCON_test", f"{soco_br.uid}"
|
|
)
|
|
alarm_clock.ListAlarms.return_value = alarm_update
|
|
|
|
# Update the alarm_list_version so it gets processed.
|
|
alarm_event.variables["alarm_list_version"] = "RINCON_test:1000"
|
|
alarm_update["CurrentAlarmListVersion"] = alarm_event.increment_variable(
|
|
"alarm_list_version"
|
|
)
|
|
|
|
alarm_clock.subscribe.return_value.callback(event=alarm_event)
|
|
await hass.async_block_till_done(wait_background_tasks=True)
|
|
|
|
assert entity_id in entity_registry.entities
|
|
alarm_14 = entity_registry.async_get(entity_id)
|
|
device = device_registry.async_get(alarm_14.device_id)
|
|
assert device.name == soco_br.get_speaker_info()["zone_name"]
|