211 lines
8.0 KiB
Python
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
|