Improve Sonos tests, begin adding coverage (#61198)

* Update entity registry handling

* Add and use fixtures to test setup via config entry

* Remove legacy redundant tests

* Remove unnecessary mock_coro

* Remove unnecessary namespace change

* Move zeroconf payload to fixture

* Begin adding Sonos to codecov

* Mock proper return value

* Revert return value for platform
pull/61401/head
jjlawren 2021-12-08 12:28:27 -06:00 committed by GitHub
parent af91addc6c
commit 9f3a4c3617
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 88 additions and 130 deletions

View File

@ -1003,7 +1003,16 @@ omit =
homeassistant/components/somfy/switch.py
homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.py
homeassistant/components/sonos/*
homeassistant/components/sonos/__init__.py
homeassistant/components/sonos/alarms.py
homeassistant/components/sonos/entity.py
homeassistant/components/sonos/favorites.py
homeassistant/components/sonos/helpers.py
homeassistant/components/sonos/household_coordinator.py
homeassistant/components/sonos/media_browser.py
homeassistant/components/sonos/media_player.py
homeassistant/components/sonos/speaker.py
homeassistant/components/sonos/switch.py
homeassistant/components/sony_projector/switch.py
homeassistant/components/spc/*
homeassistant/components/spider/*

View File

@ -1,9 +1,9 @@
"""Configuration for Sonos tests."""
from unittest.mock import AsyncMock, MagicMock, Mock, patch as patch
from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest
from homeassistant.components import ssdp
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
@ -42,6 +42,37 @@ class SonosMockEvent:
return self.variables[var_name]
@pytest.fixture
def zeroconf_payload():
"""Return a default zeroconf payload."""
return zeroconf.ZeroconfServiceInfo(
host="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."""

View File

@ -37,21 +37,14 @@ async def test_user_form(discover_mock: MagicMock, hass: core.HomeAssistant):
assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_form(hass: core.HomeAssistant):
"""Test we pass sonos devices to the discovery manager."""
async def test_zeroconf_form(hass: core.HomeAssistant, zeroconf_payload):
"""Test we pass Zeroconf discoveries to the manager."""
mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock()
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="192.168.4.2",
hostname="Sonos-aaa",
name="Sonos-aaa@Living Room._sonos._tcp.local.",
port=None,
properties={"bootseq": "1234"},
type="mock_type",
),
data=zeroconf_payload,
)
assert result["type"] == "form"
assert result["errors"] is None
@ -128,21 +121,16 @@ async def test_zeroconf_sonos_v1(hass: core.HomeAssistant):
assert len(mock_manager.mock_calls) == 2
async def test_zeroconf_form_not_sonos(hass: core.HomeAssistant):
async def test_zeroconf_form_not_sonos(hass: core.HomeAssistant, zeroconf_payload):
"""Test we abort on non-sonos devices."""
mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock()
zeroconf_payload.hostname = "not-aaa"
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo(
host="192.168.4.2",
hostname="not-aaa",
name="mock_name",
port=None,
properties={"bootseq": "1234"},
type="mock_type",
),
data=zeroconf_payload,
)
assert result["type"] == "abort"
assert result["reason"] == "not_sonos_device"

View File

@ -5,14 +5,11 @@ from homeassistant import config_entries, data_entry_flow
from homeassistant.components import sonos
from homeassistant.setup import async_setup_component
from tests.common import mock_coro
async def test_creating_entry_sets_up_media_player(hass):
"""Test setting up Sonos loads the media player."""
with patch(
"homeassistant.components.sonos.media_player.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup, patch("soco.discover", return_value=True):
result = await hass.config_entries.flow.async_init(
sonos.DOMAIN, context={"source": config_entries.SOURCE_USER}
@ -32,7 +29,8 @@ async def test_creating_entry_sets_up_media_player(hass):
async def test_configuring_sonos_creates_entry(hass):
"""Test that specifying config will create an entry."""
with patch(
"homeassistant.components.sonos.async_setup_entry", return_value=mock_coro(True)
"homeassistant.components.sonos.async_setup_entry",
return_value=True,
) as mock_setup, patch("soco.discover", return_value=True):
await async_setup_component(
hass,
@ -47,7 +45,8 @@ async def test_configuring_sonos_creates_entry(hass):
async def test_not_configuring_sonos_not_creates_entry(hass):
"""Test that no config will not create an entry."""
with patch(
"homeassistant.components.sonos.async_setup_entry", return_value=mock_coro(True)
"homeassistant.components.sonos.async_setup_entry",
return_value=True,
) as mock_setup, patch("soco.discover", return_value=True):
await async_setup_component(hass, sonos.DOMAIN, {})
await hass.async_block_till_done()

View File

@ -9,54 +9,23 @@ from homeassistant.const import STATE_IDLE
from homeassistant.core import Context
from homeassistant.exceptions import Unauthorized
from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
async def setup_platform(hass, config_entry, config):
"""Set up the media player 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_async_setup_entry_hosts(hass, config_entry, config, soco):
"""Test static setup."""
await setup_platform(hass, config_entry, config)
speakers = list(hass.data[DATA_SONOS].discovered.values())
speaker = speakers[0]
assert speaker.soco == soco
media_player = hass.states.get("media_player.zone_a")
assert media_player.state == STATE_IDLE
async def test_async_setup_entry_discover(hass, config_entry, discover):
"""Test discovery setup."""
await setup_platform(hass, config_entry, {})
speakers = list(hass.data[DATA_SONOS].discovered.values())
speaker = speakers[0]
assert speaker.soco.uid == "RINCON_test"
media_player = hass.states.get("media_player.zone_a")
assert media_player.state == STATE_IDLE
async def test_discovery_ignore_unsupported_device(hass, config_entry, soco, caplog):
async def test_discovery_ignore_unsupported_device(
hass, async_setup_sonos, soco, caplog
):
"""Test discovery setup."""
message = f"GetVolume not supported on {soco.ip_address}"
type(soco).volume = PropertyMock(side_effect=NotSupportedException(message))
await setup_platform(hass, config_entry, {})
await async_setup_sonos()
assert message in caplog.text
assert not hass.data[DATA_SONOS].discovered
async def test_services(hass, config_entry, config, hass_read_only_user):
async def test_services(hass, async_autosetup_sonos, hass_read_only_user):
"""Test join/unjoin requires control access."""
await setup_platform(hass, config_entry, config)
with pytest.raises(Unauthorized):
await hass.services.async_call(
DOMAIN,
@ -67,10 +36,8 @@ async def test_services(hass, config_entry, config, hass_read_only_user):
)
async def test_device_registry(hass, config_entry, config, soco):
async def test_device_registry(hass, async_autosetup_sonos, soco):
"""Test sonos device registered in the device registry."""
await setup_platform(hass, config_entry, config)
device_registry = dr.async_get(hass)
reg_device = device_registry.async_get_device(
identifiers={("sonos", "RINCON_test")}
@ -86,10 +53,8 @@ async def test_device_registry(hass, config_entry, config, soco):
assert reg_device.name == "Zone A"
async def test_entity_basic(hass, config_entry, discover):
async def test_entity_basic(hass, async_autosetup_sonos, discover):
"""Test basic state and attributes."""
await setup_platform(hass, config_entry, {})
state = hass.states.get("media_player.zone_a")
assert state.state == STATE_IDLE
attributes = state.attributes

View File

@ -14,16 +14,9 @@ from homeassistant.components.plex.const import PLEX_URI_SCHEME
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.exceptions import HomeAssistantError
from .test_media_player import setup_platform
async def test_plex_play_media(
hass,
config_entry,
config,
):
async def test_plex_play_media(hass, async_autosetup_sonos):
"""Test playing media via the Plex integration."""
await setup_platform(hass, config_entry, config)
media_player = "media_player.zone_a"
media_content_id = (
'{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}'

View File

@ -1,48 +1,36 @@
"""Tests for the Sonos battery sensor platform."""
from soco.exceptions import NotSupportedException
from homeassistant.components.sonos import DOMAIN
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.setup import async_setup_component
from homeassistant.helpers import entity_registry as ent_reg
async def setup_platform(hass, config_entry, config):
"""Set up the media player 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_unsupported(hass, config_entry, config, soco):
async def test_entity_registry_unsupported(hass, async_setup_sonos, soco):
"""Test sonos device without battery registered in the device registry."""
soco.get_battery_info.side_effect = NotSupportedException
await setup_platform(hass, config_entry, config)
await async_setup_sonos()
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
assert "media_player.zone_a" in entity_registry.entities
assert "sensor.zone_a_battery" not in entity_registry.entities
assert "binary_sensor.zone_a_power" not in entity_registry.entities
async def test_entity_registry_supported(hass, config_entry, config, soco):
async def test_entity_registry_supported(hass, async_autosetup_sonos, soco):
"""Test sonos device with battery registered in the device registry."""
await setup_platform(hass, config_entry, config)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
assert "media_player.zone_a" in entity_registry.entities
assert "sensor.zone_a_battery" in entity_registry.entities
assert "binary_sensor.zone_a_power" in entity_registry.entities
async def test_battery_attributes(hass, config_entry, config, soco):
async def test_battery_attributes(hass, async_autosetup_sonos, soco):
"""Test sonos device with battery state."""
await setup_platform(hass, config_entry, config)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
battery = entity_registry.entities["sensor.zone_a_battery"]
battery_state = hass.states.get(battery.entity_id)
@ -57,16 +45,16 @@ async def test_battery_attributes(hass, config_entry, config, soco):
)
async def test_battery_on_S1(hass, config_entry, config, soco, battery_event):
async def test_battery_on_S1(hass, async_setup_sonos, soco, battery_event):
"""Test battery state updates on a Sonos S1 device."""
soco.get_battery_info.return_value = {}
await setup_platform(hass, config_entry, config)
await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
assert "sensor.zone_a_battery" not in entity_registry.entities
assert "binary_sensor.zone_a_power" not in entity_registry.entities
@ -86,12 +74,12 @@ async def test_battery_on_S1(hass, config_entry, config, soco, battery_event):
async def test_device_payload_without_battery(
hass, config_entry, config, soco, battery_event, caplog
hass, async_setup_sonos, soco, battery_event, caplog
):
"""Test device properties event update without battery info."""
soco.get_battery_info.return_value = None
await setup_platform(hass, config_entry, config)
await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback
@ -106,12 +94,12 @@ async def test_device_payload_without_battery(
async def test_device_payload_without_battery_and_ignored_keys(
hass, config_entry, config, soco, battery_event, caplog
hass, async_setup_sonos, soco, battery_event, caplog
):
"""Test device properties event update without battery info and ignored keys."""
soco.get_battery_info.return_value = None
await setup_platform(hass, config_entry, config)
await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback
@ -125,11 +113,9 @@ async def test_device_payload_without_battery_and_ignored_keys(
assert ignored_payload not in caplog.text
async def test_audio_input_sensor(hass, config_entry, config, soco):
async def test_audio_input_sensor(hass, async_autosetup_sonos, soco):
"""Test sonos device with battery state."""
await setup_platform(hass, config_entry, config)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"]
audio_input_state = hass.states.get(audio_input_sensor.entity_id)

View File

@ -3,7 +3,6 @@ from copy import copy
from datetime import timedelta
from unittest.mock import patch
from homeassistant.components.sonos import DOMAIN
from homeassistant.components.sonos.const import DATA_SONOS_DISCOVERY_MANAGER
from homeassistant.components.sonos.switch import (
ATTR_DURATION,
@ -15,8 +14,7 @@ from homeassistant.components.sonos.switch import (
)
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
from homeassistant.const import ATTR_TIME, STATE_OFF, STATE_ON
from homeassistant.helpers.entity_registry import async_get as async_get_entity_registry
from homeassistant.setup import async_setup_component
from homeassistant.helpers import entity_registry as ent_reg
from homeassistant.util import dt
from .conftest import SonosMockEvent
@ -24,18 +22,9 @@ from .conftest import SonosMockEvent
from tests.common import async_fire_time_changed
async def setup_platform(hass, config_entry, config):
"""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):
async def test_entity_registry(hass, async_autosetup_sonos):
"""Test sonos device with alarm registered in the device registry."""
await setup_platform(hass, config_entry, config)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
assert "media_player.zone_a" in entity_registry.entities
assert "switch.sonos_alarm_14" in entity_registry.entities
@ -47,11 +36,9 @@ async def test_entity_registry(hass, config_entry, config):
assert "switch.sonos_zone_a_touch_controls" in entity_registry.entities
async def test_switch_attributes(hass, config_entry, config, soco):
async def test_switch_attributes(hass, async_autosetup_sonos, soco):
"""Test for correct Sonos switch states."""
await setup_platform(hass, config_entry, config)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
entity_registry = ent_reg.async_get(hass)
alarm = entity_registry.entities["switch.sonos_alarm_14"]
alarm_state = hass.states.get(alarm.entity_id)
@ -125,15 +112,15 @@ async def test_switch_attributes(hass, config_entry, config, soco):
async def test_alarm_create_delete(
hass, config_entry, config, soco, alarm_clock, alarm_clock_extended, alarm_event
hass, async_setup_sonos, soco, alarm_clock, alarm_clock_extended, alarm_event
):
"""Test for correct creation and deletion of alarms during runtime."""
entity_registry = async_get_entity_registry(hass)
entity_registry = ent_reg.async_get(hass)
one_alarm = copy(alarm_clock.ListAlarms.return_value)
two_alarms = copy(alarm_clock_extended.ListAlarms.return_value)
await setup_platform(hass, config_entry, config)
await async_setup_sonos()
assert "switch.sonos_alarm_14" in entity_registry.entities
assert "switch.sonos_alarm_15" not in entity_registry.entities