352 lines
12 KiB
Python
352 lines
12 KiB
Python
"""Configuration for Sonos tests."""
|
|
from copy import copy
|
|
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
|
|
|
import pytest
|
|
from soco import SoCo
|
|
|
|
from homeassistant.components import ssdp, zeroconf
|
|
from homeassistant.components.media_player import DOMAIN as MP_DOMAIN
|
|
from homeassistant.components.sonos import DOMAIN
|
|
from homeassistant.const import CONF_HOSTS
|
|
|
|
from tests.common import MockConfigEntry
|
|
|
|
|
|
class SonosMockService:
|
|
"""Mock a Sonos Service used in callbacks."""
|
|
|
|
def __init__(self, service_type):
|
|
"""Initialize the instance."""
|
|
self.service_type = service_type
|
|
self.subscribe = AsyncMock()
|
|
|
|
|
|
class SonosMockEvent:
|
|
"""Mock a sonos Event used in callbacks."""
|
|
|
|
def __init__(self, soco, service, variables):
|
|
"""Initialize the instance."""
|
|
self.sid = f"{soco.uid}_sub0000000001"
|
|
self.seq = "0"
|
|
self.timestamp = 1621000000.0
|
|
self.service = service
|
|
self.variables = variables
|
|
|
|
def increment_variable(self, var_name):
|
|
"""Increment the value of the var_name key in variables dict attribute.
|
|
|
|
Assumes value has a format of <str>:<int>.
|
|
"""
|
|
self.variables = copy(self.variables)
|
|
base, count = self.variables[var_name].split(":")
|
|
newcount = int(count) + 1
|
|
self.variables[var_name] = ":".join([base, str(newcount)])
|
|
return self.variables[var_name]
|
|
|
|
|
|
@pytest.fixture
|
|
def zeroconf_payload():
|
|
"""Return a default zeroconf payload."""
|
|
return zeroconf.ZeroconfServiceInfo(
|
|
host="192.168.4.2",
|
|
addresses=["192.168.4.2"],
|
|
hostname="Sonos-aaa",
|
|
name="Sonos-aaa@Living Room._sonos._tcp.local.",
|
|
port=None,
|
|
properties={"bootseq": "1234"},
|
|
type="mock_type",
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
async def async_autosetup_sonos(async_setup_sonos):
|
|
"""Set up a Sonos integration instance on test run."""
|
|
await async_setup_sonos()
|
|
|
|
|
|
@pytest.fixture
|
|
def async_setup_sonos(hass, config_entry):
|
|
"""Return a coroutine to set up a Sonos integration instance on demand."""
|
|
|
|
async def _wrapper():
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
return _wrapper
|
|
|
|
|
|
@pytest.fixture(name="config_entry")
|
|
def config_entry_fixture():
|
|
"""Create a mock Sonos config entry."""
|
|
return MockConfigEntry(domain=DOMAIN, title="Sonos")
|
|
|
|
|
|
@pytest.fixture(name="soco")
|
|
def soco_fixture(
|
|
music_library, speaker_info, current_track_info_empty, battery_info, alarm_clock
|
|
):
|
|
"""Create a mock soco SoCo fixture."""
|
|
with patch("homeassistant.components.sonos.SoCo", autospec=True) as mock, patch(
|
|
"socket.gethostbyname", return_value="192.168.42.2"
|
|
):
|
|
mock_soco = mock.return_value
|
|
mock_soco.ip_address = "192.168.42.2"
|
|
mock_soco.uid = "RINCON_test"
|
|
mock_soco.play_mode = "NORMAL"
|
|
mock_soco.music_library = music_library
|
|
mock_soco.get_current_track_info.return_value = current_track_info_empty
|
|
mock_soco.music_source_from_uri = SoCo.music_source_from_uri
|
|
mock_soco.get_speaker_info.return_value = speaker_info
|
|
mock_soco.avTransport = SonosMockService("AVTransport")
|
|
mock_soco.renderingControl = SonosMockService("RenderingControl")
|
|
mock_soco.zoneGroupTopology = SonosMockService("ZoneGroupTopology")
|
|
mock_soco.contentDirectory = SonosMockService("ContentDirectory")
|
|
mock_soco.deviceProperties = SonosMockService("DeviceProperties")
|
|
mock_soco.alarmClock = alarm_clock
|
|
mock_soco.mute = False
|
|
mock_soco.night_mode = True
|
|
mock_soco.dialog_level = True
|
|
mock_soco.loudness = True
|
|
mock_soco.volume = 19
|
|
mock_soco.audio_delay = 2
|
|
mock_soco.bass = 1
|
|
mock_soco.treble = -1
|
|
mock_soco.mic_enabled = False
|
|
mock_soco.sub_enabled = False
|
|
mock_soco.sub_gain = 5
|
|
mock_soco.surround_enabled = True
|
|
mock_soco.surround_mode = True
|
|
mock_soco.surround_level = 3
|
|
mock_soco.music_surround_level = 4
|
|
mock_soco.soundbar_audio_input_format = "Dolby 5.1"
|
|
mock_soco.get_battery_info.return_value = battery_info
|
|
mock_soco.all_zones = {mock_soco}
|
|
mock_soco.visible_zones = {mock_soco}
|
|
mock_soco.group.coordinator = mock_soco
|
|
yield mock_soco
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
async def silent_ssdp_scanner(hass):
|
|
"""Start SSDP component and get Scanner, prevent actual SSDP traffic."""
|
|
with patch(
|
|
"homeassistant.components.ssdp.Scanner._async_start_ssdp_listeners"
|
|
), patch("homeassistant.components.ssdp.Scanner._async_stop_ssdp_listeners"), patch(
|
|
"homeassistant.components.ssdp.Scanner.async_scan"
|
|
), patch(
|
|
"homeassistant.components.ssdp.Server._async_start_upnp_servers"
|
|
), patch(
|
|
"homeassistant.components.ssdp.Server._async_stop_upnp_servers"
|
|
):
|
|
yield
|
|
|
|
|
|
@pytest.fixture(name="discover", autouse=True)
|
|
def discover_fixture(soco):
|
|
"""Create a mock soco discover fixture."""
|
|
|
|
async def do_callback(hass, callback, *args, **kwargs):
|
|
await callback(
|
|
ssdp.SsdpServiceInfo(
|
|
ssdp_location=f"http://{soco.ip_address}/",
|
|
ssdp_st="urn:schemas-upnp-org:device:ZonePlayer:1",
|
|
ssdp_usn=f"uuid:{soco.uid}_MR::urn:schemas-upnp-org:service:GroupRenderingControl:1",
|
|
upnp={
|
|
ssdp.ATTR_UPNP_UDN: f"uuid:{soco.uid}",
|
|
},
|
|
),
|
|
ssdp.SsdpChange.ALIVE,
|
|
)
|
|
return MagicMock()
|
|
|
|
with patch(
|
|
"homeassistant.components.ssdp.async_register_callback", side_effect=do_callback
|
|
) as mock:
|
|
yield mock
|
|
|
|
|
|
@pytest.fixture(name="config")
|
|
def config_fixture():
|
|
"""Create hass config fixture."""
|
|
return {DOMAIN: {MP_DOMAIN: {CONF_HOSTS: ["192.168.42.1"]}}}
|
|
|
|
|
|
@pytest.fixture(name="music_library")
|
|
def music_library_fixture():
|
|
"""Create music_library fixture."""
|
|
music_library = MagicMock()
|
|
music_library.get_sonos_favorites.return_value.update_id = 1
|
|
return music_library
|
|
|
|
|
|
@pytest.fixture(name="alarm_clock")
|
|
def alarm_clock_fixture():
|
|
"""Create alarmClock fixture."""
|
|
alarm_clock = SonosMockService("AlarmClock")
|
|
alarm_clock.ListAlarms = Mock()
|
|
alarm_clock.ListAlarms.return_value = {
|
|
"CurrentAlarmListVersion": "RINCON_test:14",
|
|
"CurrentAlarmList": "<Alarms>"
|
|
'<Alarm ID="14" StartTime="07:00:00" Duration="02:00:00" Recurrence="DAILY" '
|
|
'Enabled="1" RoomUUID="RINCON_test" ProgramURI="x-rincon-buzzer:0" '
|
|
'ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" Volume="25" '
|
|
'IncludeLinkedZones="0"/>'
|
|
"</Alarms>",
|
|
}
|
|
return alarm_clock
|
|
|
|
|
|
@pytest.fixture(name="alarm_clock_extended")
|
|
def alarm_clock_fixture_extended():
|
|
"""Create alarmClock fixture."""
|
|
alarm_clock = SonosMockService("AlarmClock")
|
|
alarm_clock.ListAlarms = Mock()
|
|
alarm_clock.ListAlarms.return_value = {
|
|
"CurrentAlarmListVersion": "RINCON_test:15",
|
|
"CurrentAlarmList": "<Alarms>"
|
|
'<Alarm ID="14" StartTime="07:00:00" Duration="02:00:00" Recurrence="DAILY" '
|
|
'Enabled="1" RoomUUID="RINCON_test" ProgramURI="x-rincon-buzzer:0" '
|
|
'ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" Volume="25" '
|
|
'IncludeLinkedZones="0"/>'
|
|
'<Alarm ID="15" StartTime="07:00:00" Duration="02:00:00" '
|
|
'Recurrence="DAILY" Enabled="1" RoomUUID="RINCON_test" '
|
|
'ProgramURI="x-rincon-buzzer:0" ProgramMetaData="" PlayMode="SHUFFLE_NOREPEAT" '
|
|
'Volume="25" IncludeLinkedZones="0"/>'
|
|
"</Alarms>",
|
|
}
|
|
return alarm_clock
|
|
|
|
|
|
@pytest.fixture(name="speaker_info")
|
|
def speaker_info_fixture():
|
|
"""Create speaker_info fixture."""
|
|
return {
|
|
"zone_name": "Zone A",
|
|
"uid": "RINCON_test",
|
|
"model_name": "Model Name",
|
|
"model_number": "S12",
|
|
"hardware_version": "1.20.1.6-1.1",
|
|
"software_version": "49.2-64250",
|
|
"mac_address": "00-11-22-33-44-55",
|
|
"display_version": "13.1",
|
|
}
|
|
|
|
|
|
@pytest.fixture(name="current_track_info_empty")
|
|
def current_track_info_empty_fixture():
|
|
"""Create current_track_info_empty fixture."""
|
|
return {
|
|
"title": "",
|
|
"artist": "",
|
|
"album": "",
|
|
"album_art": "",
|
|
"position": "NOT_IMPLEMENTED",
|
|
"playlist_position": "1",
|
|
"duration": "NOT_IMPLEMENTED",
|
|
"uri": "",
|
|
"metadata": "NOT_IMPLEMENTED",
|
|
}
|
|
|
|
|
|
@pytest.fixture(name="battery_info")
|
|
def battery_info_fixture():
|
|
"""Create battery_info fixture."""
|
|
return {
|
|
"Health": "GREEN",
|
|
"Level": 100,
|
|
"Temperature": "NORMAL",
|
|
"PowerSource": "SONOS_CHARGING_RING",
|
|
}
|
|
|
|
|
|
@pytest.fixture(name="device_properties_event")
|
|
def device_properties_event_fixture(soco):
|
|
"""Create device_properties_event fixture."""
|
|
variables = {
|
|
"zone_name": "Zone A",
|
|
"mic_enabled": "1",
|
|
"more_info": "BattChg:NOT_CHARGING,RawBattPct:100,BattPct:100,BattTmp:25",
|
|
}
|
|
return SonosMockEvent(soco, soco.deviceProperties, 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:1",
|
|
"time_format": "INV",
|
|
"date_format": "INV",
|
|
"daily_index_refresh_time": None,
|
|
}
|
|
|
|
return SonosMockEvent(soco, soco.alarmClock, variables)
|
|
|
|
|
|
@pytest.fixture(name="no_media_event")
|
|
def no_media_event_fixture(soco):
|
|
"""Create no_media_event_fixture."""
|
|
variables = {
|
|
"current_crossfade_mode": "0",
|
|
"current_play_mode": "NORMAL",
|
|
"current_section": "0",
|
|
"current_track_meta_data": "",
|
|
"current_track_uri": "",
|
|
"enqueued_transport_uri": "",
|
|
"enqueued_transport_uri_meta_data": "",
|
|
"number_of_tracks": "0",
|
|
"transport_state": "STOPPED",
|
|
}
|
|
return SonosMockEvent(soco, soco.avTransport, variables)
|
|
|
|
|
|
@pytest.fixture(name="tv_event")
|
|
def tv_event_fixture(soco):
|
|
"""Create alarm_event fixture."""
|
|
variables = {
|
|
"transport_state": "PLAYING",
|
|
"current_play_mode": "NORMAL",
|
|
"current_crossfade_mode": "0",
|
|
"number_of_tracks": "1",
|
|
"current_track": "1",
|
|
"current_section": "0",
|
|
"current_track_uri": f"x-sonos-htastream:{soco.uid}:spdif",
|
|
"current_track_duration": "",
|
|
"current_track_meta_data": {
|
|
"title": " ",
|
|
"parent_id": "-1",
|
|
"item_id": "-1",
|
|
"restricted": True,
|
|
"resources": [],
|
|
"desc": None,
|
|
},
|
|
"next_track_uri": "",
|
|
"next_track_meta_data": "",
|
|
"enqueued_transport_uri": "",
|
|
"enqueued_transport_uri_meta_data": "",
|
|
"playback_storage_medium": "NETWORK",
|
|
"av_transport_uri": f"x-sonos-htastream:{soco.uid}:spdif",
|
|
"av_transport_uri_meta_data": {
|
|
"title": soco.uid,
|
|
"parent_id": "0",
|
|
"item_id": "spdif-input",
|
|
"restricted": False,
|
|
"resources": [],
|
|
"desc": None,
|
|
},
|
|
"current_transport_actions": "Set, Play",
|
|
"current_valid_play_modes": "",
|
|
}
|
|
return SonosMockEvent(soco, soco.avTransport, variables)
|
|
|
|
|
|
@pytest.fixture(autouse=True)
|
|
def mock_get_source_ip(mock_get_source_ip):
|
|
"""Mock network util's async_get_source_ip in all sonos tests."""
|
|
return mock_get_source_ip
|