core/tests/components/heos/test_init.py

211 lines
8.0 KiB
Python

"""Tests for the init module."""
import asyncio
from typing import cast
from pyheos import (
CommandFailedError,
Heos,
HeosError,
HeosOptions,
SignalHeosEvent,
SignalType,
const,
)
import pytest
from homeassistant.components.heos.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from tests.common import MockConfigEntry
async def test_async_setup_entry_loads_platforms(
hass: HomeAssistant,
config_entry: MockConfigEntry,
controller: Heos,
) -> None:
"""Test load connects to heos, retrieves players, and loads platforms."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
assert config_entry.state == ConfigEntryState.LOADED
assert hass.states.get("media_player.test_player") is not None
assert controller.connect.call_count == 1
assert controller.get_players.call_count == 1
assert controller.get_favorites.call_count == 1
assert controller.get_input_sources.call_count == 1
controller.disconnect.assert_not_called()
async def test_async_setup_entry_with_options_loads_platforms(
hass: HomeAssistant, config_entry_options: MockConfigEntry, controller: Heos
) -> None:
"""Test load connects to heos with options, retrieves players, and loads platforms."""
config_entry_options.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry_options.entry_id)
# Assert options passed and methods called
assert config_entry_options.state is ConfigEntryState.LOADED
options = cast(HeosOptions, controller.new_mock.call_args[0][0])
assert options.host == config_entry_options.data[CONF_HOST]
assert options.credentials.username == config_entry_options.options[CONF_USERNAME]
assert options.credentials.password == config_entry_options.options[CONF_PASSWORD]
assert controller.connect.call_count == 1
assert controller.get_players.call_count == 1
assert controller.get_favorites.call_count == 1
assert controller.get_input_sources.call_count == 1
controller.disconnect.assert_not_called()
async def test_async_setup_entry_auth_failure_starts_reauth(
hass: HomeAssistant,
config_entry_options: MockConfigEntry,
controller: Heos,
) -> None:
"""Test load with auth failure starts reauth, loads platforms."""
config_entry_options.add_to_hass(hass)
# Simulates what happens when the controller can't sign-in during connection
async def connect_send_auth_failure() -> None:
controller._signed_in_username = None
controller.dispatcher.send(
SignalType.HEOS_EVENT, SignalHeosEvent.USER_CREDENTIALS_INVALID
)
controller.connect.side_effect = connect_send_auth_failure
assert await hass.config_entries.async_setup(config_entry_options.entry_id)
# Assert entry loaded and reauth flow started
assert controller.connect.call_count == 1
assert controller.get_favorites.call_count == 0
controller.disconnect.assert_not_called()
assert config_entry_options.state is ConfigEntryState.LOADED
assert any(
config_entry_options.async_get_active_flows(hass, sources=[SOURCE_REAUTH])
)
async def test_async_setup_entry_not_signed_in_loads_platforms(
hass: HomeAssistant,
config_entry: MockConfigEntry,
controller: Heos,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test setup does not retrieve favorites when not logged in."""
config_entry.add_to_hass(hass)
controller._signed_in_username = None
assert await hass.config_entries.async_setup(config_entry.entry_id)
assert controller.connect.call_count == 1
assert controller.get_players.call_count == 1
assert controller.get_favorites.call_count == 0
assert controller.get_input_sources.call_count == 1
controller.disconnect.assert_not_called()
assert (
"The HEOS System is not logged in: Enter credentials in the integration options to access favorites and streaming services"
in caplog.text
)
async def test_async_setup_entry_connect_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos
) -> None:
"""Connection failure raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass)
controller.connect.side_effect = HeosError()
assert not await hass.config_entries.async_setup(config_entry.entry_id)
assert config_entry.state == ConfigEntryState.SETUP_RETRY
assert controller.connect.call_count == 1
assert controller.disconnect.call_count == 1
controller.connect.reset_mock()
controller.disconnect.reset_mock()
async def test_async_setup_entry_player_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos
) -> None:
"""Failure to retrieve players/sources raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass)
controller.get_players.side_effect = HeosError()
assert not await hass.config_entries.async_setup(config_entry.entry_id)
assert controller.connect.call_count == 1
assert controller.disconnect.call_count == 1
controller.connect.reset_mock()
controller.disconnect.reset_mock()
async def test_unload_entry(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos
) -> None:
"""Test entries are unloaded correctly."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert controller.disconnect.call_count == 1
async def test_update_sources_retry(
hass: HomeAssistant,
config_entry: MockConfigEntry,
controller: Heos,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test update sources retries on failures to max attempts."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
controller.get_favorites.reset_mock()
controller.get_input_sources.reset_mock()
source_manager = config_entry.runtime_data.source_manager
source_manager.retry_delay = 0
source_manager.max_retry_attempts = 1
controller.get_favorites.side_effect = CommandFailedError("Test", "test", 0)
controller.dispatcher.send(
SignalType.CONTROLLER_EVENT, const.EVENT_SOURCES_CHANGED, {}
)
# Wait until it's finished
while "Unable to update sources" not in caplog.text:
await asyncio.sleep(0.1)
assert controller.get_favorites.call_count == 2
async def test_device_info(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
config_entry: MockConfigEntry,
) -> None:
"""Test device information populates correctly."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
device = device_registry.async_get_device({(DOMAIN, "1")})
assert device.manufacturer == "HEOS"
assert device.model == "Drive HS2"
assert device.name == "Test Player"
assert device.serial_number == "123456"
assert device.sw_version == "1.0.0"
device = device_registry.async_get_device({(DOMAIN, "2")})
assert device.manufacturer == "HEOS"
assert device.model == "Speaker"
async def test_device_id_migration(
hass: HomeAssistant,
device_registry: dr.DeviceRegistry,
config_entry: MockConfigEntry,
) -> None:
"""Test that legacy non-string device identifiers are migrated to strings."""
config_entry.add_to_hass(hass)
# Create a device with a legacy identifier
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, 1)}
)
device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, identifiers={("Other", 1)}
)
assert await hass.config_entries.async_setup(config_entry.entry_id)
assert device_registry.async_get_device({("Other", 1)}) is not None
assert device_registry.async_get_device({(DOMAIN, 1)}) is None
assert device_registry.async_get_device({(DOMAIN, "1")}) is not None