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
This commit is contained in:
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/switch.py
homeassistant/components/somfy_mylink/__init__.py homeassistant/components/somfy_mylink/__init__.py
homeassistant/components/somfy_mylink/cover.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/sony_projector/switch.py
homeassistant/components/spc/* homeassistant/components/spc/*
homeassistant/components/spider/* homeassistant/components/spider/*

View file

@ -1,9 +1,9 @@
"""Configuration for Sonos tests.""" """Configuration for Sonos tests."""
from unittest.mock import AsyncMock, MagicMock, Mock, patch as patch from unittest.mock import AsyncMock, MagicMock, Mock, patch
import pytest 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.media_player import DOMAIN as MP_DOMAIN
from homeassistant.components.sonos import DOMAIN from homeassistant.components.sonos import DOMAIN
from homeassistant.const import CONF_HOSTS from homeassistant.const import CONF_HOSTS
@ -42,6 +42,37 @@ class SonosMockEvent:
return self.variables[var_name] 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") @pytest.fixture(name="config_entry")
def config_entry_fixture(): def config_entry_fixture():
"""Create a mock Sonos config entry.""" """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 assert len(mock_setup_entry.mock_calls) == 1
async def test_zeroconf_form(hass: core.HomeAssistant): async def test_zeroconf_form(hass: core.HomeAssistant, zeroconf_payload):
"""Test we pass sonos devices to the discovery manager.""" """Test we pass Zeroconf discoveries to the manager."""
mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock() mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock()
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo( data=zeroconf_payload,
host="192.168.4.2",
hostname="Sonos-aaa",
name="Sonos-aaa@Living Room._sonos._tcp.local.",
port=None,
properties={"bootseq": "1234"},
type="mock_type",
),
) )
assert result["type"] == "form" assert result["type"] == "form"
assert result["errors"] is None 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 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.""" """Test we abort on non-sonos devices."""
mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock() mock_manager = hass.data[DATA_SONOS_DISCOVERY_MANAGER] = MagicMock()
zeroconf_payload.hostname = "not-aaa"
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
DOMAIN, DOMAIN,
context={"source": config_entries.SOURCE_ZEROCONF}, context={"source": config_entries.SOURCE_ZEROCONF},
data=zeroconf.ZeroconfServiceInfo( data=zeroconf_payload,
host="192.168.4.2",
hostname="not-aaa",
name="mock_name",
port=None,
properties={"bootseq": "1234"},
type="mock_type",
),
) )
assert result["type"] == "abort" assert result["type"] == "abort"
assert result["reason"] == "not_sonos_device" 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.components import sonos
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from tests.common import mock_coro
async def test_creating_entry_sets_up_media_player(hass): async def test_creating_entry_sets_up_media_player(hass):
"""Test setting up Sonos loads the media player.""" """Test setting up Sonos loads the media player."""
with patch( with patch(
"homeassistant.components.sonos.media_player.async_setup_entry", "homeassistant.components.sonos.media_player.async_setup_entry",
return_value=mock_coro(True),
) as mock_setup, patch("soco.discover", return_value=True): ) as mock_setup, patch("soco.discover", return_value=True):
result = await hass.config_entries.flow.async_init( result = await hass.config_entries.flow.async_init(
sonos.DOMAIN, context={"source": config_entries.SOURCE_USER} 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): async def test_configuring_sonos_creates_entry(hass):
"""Test that specifying config will create an entry.""" """Test that specifying config will create an entry."""
with patch( 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): ) as mock_setup, patch("soco.discover", return_value=True):
await async_setup_component( await async_setup_component(
hass, hass,
@ -47,7 +45,8 @@ async def test_configuring_sonos_creates_entry(hass):
async def test_not_configuring_sonos_not_creates_entry(hass): async def test_not_configuring_sonos_not_creates_entry(hass):
"""Test that no config will not create an entry.""" """Test that no config will not create an entry."""
with patch( 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): ) as mock_setup, patch("soco.discover", return_value=True):
await async_setup_component(hass, sonos.DOMAIN, {}) await async_setup_component(hass, sonos.DOMAIN, {})
await hass.async_block_till_done() 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.core import Context
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from homeassistant.setup import async_setup_component
async def setup_platform(hass, config_entry, config): async def test_discovery_ignore_unsupported_device(
"""Set up the media player platform for testing.""" hass, async_setup_sonos, soco, caplog
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):
"""Test discovery setup.""" """Test discovery setup."""
message = f"GetVolume not supported on {soco.ip_address}" message = f"GetVolume not supported on {soco.ip_address}"
type(soco).volume = PropertyMock(side_effect=NotSupportedException(message)) type(soco).volume = PropertyMock(side_effect=NotSupportedException(message))
await setup_platform(hass, config_entry, {})
await async_setup_sonos()
assert message in caplog.text assert message in caplog.text
assert not hass.data[DATA_SONOS].discovered 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.""" """Test join/unjoin requires control access."""
await setup_platform(hass, config_entry, config)
with pytest.raises(Unauthorized): with pytest.raises(Unauthorized):
await hass.services.async_call( await hass.services.async_call(
DOMAIN, 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.""" """Test sonos device registered in the device registry."""
await setup_platform(hass, config_entry, config)
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
reg_device = device_registry.async_get_device( reg_device = device_registry.async_get_device(
identifiers={("sonos", "RINCON_test")} identifiers={("sonos", "RINCON_test")}
@ -86,10 +53,8 @@ async def test_device_registry(hass, config_entry, config, soco):
assert reg_device.name == "Zone A" 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.""" """Test basic state and attributes."""
await setup_platform(hass, config_entry, {})
state = hass.states.get("media_player.zone_a") state = hass.states.get("media_player.zone_a")
assert state.state == STATE_IDLE assert state.state == STATE_IDLE
attributes = state.attributes 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.const import ATTR_ENTITY_ID
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from .test_media_player import setup_platform
async def test_plex_play_media(hass, async_autosetup_sonos):
async def test_plex_play_media(
hass,
config_entry,
config,
):
"""Test playing media via the Plex integration.""" """Test playing media via the Plex integration."""
await setup_platform(hass, config_entry, config)
media_player = "media_player.zone_a" media_player = "media_player.zone_a"
media_content_id = ( media_content_id = (
'{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}' '{"library_name": "Music", "artist_name": "Artist", "album_name": "Album"}'

View file

@ -1,48 +1,36 @@
"""Tests for the Sonos battery sensor platform.""" """Tests for the Sonos battery sensor platform."""
from soco.exceptions import NotSupportedException from soco.exceptions import NotSupportedException
from homeassistant.components.sonos import DOMAIN
from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE from homeassistant.components.sonos.binary_sensor import ATTR_BATTERY_POWER_SOURCE
from homeassistant.const import STATE_OFF, STATE_ON 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): async def test_entity_registry_unsupported(hass, async_setup_sonos, soco):
"""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):
"""Test sonos device without battery registered in the device registry.""" """Test sonos device without battery registered in the device registry."""
soco.get_battery_info.side_effect = NotSupportedException 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 "media_player.zone_a" in entity_registry.entities
assert "sensor.zone_a_battery" not 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 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.""" """Test sonos device with battery registered in the device registry."""
await setup_platform(hass, config_entry, config) entity_registry = ent_reg.async_get(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
assert "media_player.zone_a" in entity_registry.entities assert "media_player.zone_a" in entity_registry.entities
assert "sensor.zone_a_battery" in entity_registry.entities assert "sensor.zone_a_battery" in entity_registry.entities
assert "binary_sensor.zone_a_power" 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.""" """Test sonos device with battery state."""
await setup_platform(hass, config_entry, config) entity_registry = ent_reg.async_get(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
battery = entity_registry.entities["sensor.zone_a_battery"] battery = entity_registry.entities["sensor.zone_a_battery"]
battery_state = hass.states.get(battery.entity_id) 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.""" """Test battery state updates on a Sonos S1 device."""
soco.get_battery_info.return_value = {} soco.get_battery_info.return_value = {}
await setup_platform(hass, config_entry, config) await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback 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 "sensor.zone_a_battery" not in entity_registry.entities
assert "binary_sensor.zone_a_power" 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( 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.""" """Test device properties event update without battery info."""
soco.get_battery_info.return_value = None soco.get_battery_info.return_value = None
await setup_platform(hass, config_entry, config) await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback 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( 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.""" """Test device properties event update without battery info and ignored keys."""
soco.get_battery_info.return_value = None soco.get_battery_info.return_value = None
await setup_platform(hass, config_entry, config) await async_setup_sonos()
subscription = soco.deviceProperties.subscribe.return_value subscription = soco.deviceProperties.subscribe.return_value
sub_callback = subscription.callback 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 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.""" """Test sonos device with battery state."""
await setup_platform(hass, config_entry, config) entity_registry = ent_reg.async_get(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"] audio_input_sensor = entity_registry.entities["sensor.zone_a_audio_input_format"]
audio_input_state = hass.states.get(audio_input_sensor.entity_id) 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 datetime import timedelta
from unittest.mock import patch 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.const import DATA_SONOS_DISCOVERY_MANAGER
from homeassistant.components.sonos.switch import ( from homeassistant.components.sonos.switch import (
ATTR_DURATION, ATTR_DURATION,
@ -15,8 +14,7 @@ from homeassistant.components.sonos.switch import (
) )
from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY from homeassistant.config_entries import RELOAD_AFTER_UPDATE_DELAY
from homeassistant.const import ATTR_TIME, STATE_OFF, STATE_ON 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.helpers import entity_registry as ent_reg
from homeassistant.setup import async_setup_component
from homeassistant.util import dt from homeassistant.util import dt
from .conftest import SonosMockEvent from .conftest import SonosMockEvent
@ -24,18 +22,9 @@ from .conftest import SonosMockEvent
from tests.common import async_fire_time_changed from tests.common import async_fire_time_changed
async def setup_platform(hass, config_entry, config): async def test_entity_registry(hass, async_autosetup_sonos):
"""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):
"""Test sonos device with alarm registered in the device registry.""" """Test sonos device with alarm registered in the device registry."""
await setup_platform(hass, config_entry, config) entity_registry = ent_reg.async_get(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
assert "media_player.zone_a" in entity_registry.entities assert "media_player.zone_a" in entity_registry.entities
assert "switch.sonos_alarm_14" 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 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.""" """Test for correct Sonos switch states."""
await setup_platform(hass, config_entry, config) entity_registry = ent_reg.async_get(hass)
entity_registry = await hass.helpers.entity_registry.async_get_registry()
alarm = entity_registry.entities["switch.sonos_alarm_14"] alarm = entity_registry.entities["switch.sonos_alarm_14"]
alarm_state = hass.states.get(alarm.entity_id) 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( 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.""" """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) one_alarm = copy(alarm_clock.ListAlarms.return_value)
two_alarms = copy(alarm_clock_extended.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_14" in entity_registry.entities
assert "switch.sonos_alarm_15" not in entity_registry.entities assert "switch.sonos_alarm_15" not in entity_registry.entities