Fix typing errors in HEOS tests (#136795)

* Correct typing errors of mocked heos

* Fix player related typing issues

* Sort mocks
This commit is contained in:
Andrew Sayre 2025-01-28 21:44:57 -06:00 committed by GitHub
parent 9f586ea547
commit bc7c5fbc86
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 343 additions and 270 deletions

View File

@ -1 +1,58 @@
"""Tests for the Heos component.""" """Tests for the Heos component."""
from unittest.mock import AsyncMock
from pyheos import Heos, HeosGroup, HeosOptions, HeosPlayer
class MockHeos(Heos):
"""Defines a mocked HEOS API."""
def __init__(self, options: HeosOptions) -> None:
"""Initialize the mock."""
super().__init__(options)
# Overwrite the methods with async mocks, changing type
self.add_to_queue: AsyncMock = AsyncMock()
self.connect: AsyncMock = AsyncMock()
self.disconnect: AsyncMock = AsyncMock()
self.get_favorites: AsyncMock = AsyncMock()
self.get_groups: AsyncMock = AsyncMock()
self.get_input_sources: AsyncMock = AsyncMock()
self.get_playlists: AsyncMock = AsyncMock()
self.get_players: AsyncMock = AsyncMock()
self.load_players: AsyncMock = AsyncMock()
self.play_media: AsyncMock = AsyncMock()
self.play_preset_station: AsyncMock = AsyncMock()
self.play_url: AsyncMock = AsyncMock()
self.player_clear_queue: AsyncMock = AsyncMock()
self.player_get_quick_selects: AsyncMock = AsyncMock()
self.player_play_next: AsyncMock = AsyncMock()
self.player_play_previous: AsyncMock = AsyncMock()
self.player_play_quick_select: AsyncMock = AsyncMock()
self.player_set_mute: AsyncMock = AsyncMock()
self.player_set_play_mode: AsyncMock = AsyncMock()
self.player_set_play_state: AsyncMock = AsyncMock()
self.player_set_volume: AsyncMock = AsyncMock()
self.set_group: AsyncMock = AsyncMock()
self.sign_in: AsyncMock = AsyncMock()
self.sign_out: AsyncMock = AsyncMock()
def mock_set_players(self, players: dict[int, HeosPlayer]) -> None:
"""Set the players on the mock instance."""
for player in players.values():
player.heos = self
self._players = players
self._players_loaded = bool(players)
self.get_players.return_value = players
def mock_set_groups(self, groups: dict[int, HeosGroup]) -> None:
"""Set the groups on the mock instance."""
for group in groups.values():
group.heos = self
self._groups = groups
self._groups_loaded = bool(groups)
self.get_groups.return_value = groups
def mock_set_signed_in_username(self, signed_in_username: str | None) -> None:
"""Set the signed in status on the mock instance."""
self._signed_in_username = signed_in_username

View File

@ -2,11 +2,10 @@
from __future__ import annotations from __future__ import annotations
from collections.abc import AsyncIterator from collections.abc import Iterator
from unittest.mock import AsyncMock, Mock, patch from unittest.mock import Mock, patch
from pyheos import ( from pyheos import (
Heos,
HeosGroup, HeosGroup,
HeosHost, HeosHost,
HeosNowPlayingMedia, HeosNowPlayingMedia,
@ -38,6 +37,8 @@ from homeassistant.helpers.service_info.ssdp import (
SsdpServiceInfo, SsdpServiceInfo,
) )
from . import MockHeos
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -64,6 +65,17 @@ def config_entry_options_fixture() -> MockConfigEntry:
) )
@pytest.fixture(name="new_mock", autouse=True)
def new_heos_mock_fixture(controller: MockHeos) -> Iterator[Mock]:
"""Patch the Heos class to return the mock instance."""
new_mock = Mock(return_value=controller)
with (
patch("homeassistant.components.heos.coordinator.Heos", new=new_mock),
patch("homeassistant.components.heos.config_flow.Heos", new=new_mock),
):
yield new_mock
@pytest_asyncio.fixture(name="controller", autouse=True) @pytest_asyncio.fixture(name="controller", autouse=True)
async def controller_fixture( async def controller_fixture(
players: dict[int, HeosPlayer], players: dict[int, HeosPlayer],
@ -72,49 +84,38 @@ async def controller_fixture(
playlists: list[MediaItem], playlists: list[MediaItem],
change_data: PlayerUpdateResult, change_data: PlayerUpdateResult,
group: dict[int, HeosGroup], group: dict[int, HeosGroup],
) -> AsyncIterator[Heos]: quick_selects: dict[int, str],
) -> MockHeos:
"""Create a mock Heos controller fixture.""" """Create a mock Heos controller fixture."""
mock_heos = Heos(HeosOptions(host="127.0.0.1"))
for player in players.values(): mock_heos = MockHeos(HeosOptions(host="127.0.0.1"))
player.heos = mock_heos mock_heos.mock_set_signed_in_username("user@user.com")
mock_heos.connect = AsyncMock() mock_heos.mock_set_players(players)
mock_heos.disconnect = AsyncMock() mock_heos.mock_set_groups(group)
mock_heos.sign_in = AsyncMock() mock_heos.get_favorites.return_value = favorites
mock_heos.sign_out = AsyncMock() mock_heos.get_input_sources.return_value = input_sources
mock_heos.get_players = AsyncMock(return_value=players) mock_heos.get_playlists.return_value = playlists
mock_heos._players = players mock_heos.load_players.return_value = change_data
mock_heos.get_favorites = AsyncMock(return_value=favorites) mock_heos.player_get_quick_selects.return_value = quick_selects
mock_heos.get_input_sources = AsyncMock(return_value=input_sources) return mock_heos
mock_heos.get_playlists = AsyncMock(return_value=playlists)
mock_heos.load_players = AsyncMock(return_value=change_data)
mock_heos._signed_in_username = "user@user.com"
mock_heos.get_groups = AsyncMock(return_value=group)
mock_heos._groups = group
mock_heos.set_group = AsyncMock(return_value=None)
new_mock = Mock(return_value=mock_heos)
mock_heos.new_mock = new_mock
with (
patch("homeassistant.components.heos.coordinator.Heos", new=new_mock),
patch("homeassistant.components.heos.config_flow.Heos", new=new_mock),
):
yield mock_heos
@pytest.fixture(name="system") @pytest.fixture(name="system")
def system_info_fixture() -> dict[str, str]: def system_info_fixture() -> HeosSystem:
"""Create a system info fixture.""" """Create a system info fixture."""
main_host = HeosHost(
"Test Player",
"HEOS Drive HS2",
"123456",
"1.0.0",
"127.0.0.1",
NetworkType.WIRED,
)
return HeosSystem( return HeosSystem(
"user@user.com", "user@user.com",
"127.0.0.1", main_host,
hosts=[ hosts=[
HeosHost( main_host,
"Test Player",
"HEOS Drive HS2",
"123456",
"1.0.0",
"127.0.0.1",
NetworkType.WIRED,
),
HeosHost( HeosHost(
"Test Player 2", "Test Player 2",
"Speaker", "Speaker",
@ -128,7 +129,7 @@ def system_info_fixture() -> dict[str, str]:
@pytest.fixture(name="players") @pytest.fixture(name="players")
def players_fixture(quick_selects: dict[int, str]) -> dict[int, HeosPlayer]: def players_fixture() -> dict[int, HeosPlayer]:
"""Create two mock HeosPlayers.""" """Create two mock HeosPlayers."""
players = {} players = {}
for i in (1, 2): for i in (1, 2):
@ -148,7 +149,6 @@ def players_fixture(quick_selects: dict[int, str]) -> dict[int, HeosPlayer]:
shuffle=False, shuffle=False,
repeat=RepeatType.OFF, repeat=RepeatType.OFF,
volume=25, volume=25,
heos=None,
) )
player.now_playing_media = HeosNowPlayingMedia( player.now_playing_media = HeosNowPlayingMedia(
type=MediaType.STATION, type=MediaType.STATION,
@ -162,24 +162,6 @@ def players_fixture(quick_selects: dict[int, str]) -> dict[int, HeosPlayer]:
queue_id=1, queue_id=1,
source_id=10, source_id=10,
) )
player.add_to_queue = AsyncMock()
player.clear_queue = AsyncMock()
player.get_quick_selects = AsyncMock(return_value=quick_selects)
player.mute = AsyncMock()
player.pause = AsyncMock()
player.play = AsyncMock()
player.play_media = AsyncMock()
player.play_next = AsyncMock()
player.play_previous = AsyncMock()
player.play_preset_station = AsyncMock()
player.play_quick_select = AsyncMock()
player.play_url = AsyncMock()
player.set_mute = AsyncMock()
player.set_play_mode = AsyncMock()
player.set_quick_select = AsyncMock()
player.set_volume = AsyncMock()
player.stop = AsyncMock()
player.unmute = AsyncMock()
players[player.player_id] = player players[player.player_id] = player
return players return players

View File

@ -98,8 +98,15 @@
'Speaker - Line In 1', 'Speaker - Line In 1',
]), ]),
'system': dict({ 'system': dict({
'connected_to_preferred_host': False, 'connected_to_preferred_host': True,
'host': '127.0.0.1', 'host': dict({
'ip_address': '127.0.0.1',
'model': 'HEOS Drive HS2',
'name': 'Test Player',
'network': 'wired',
'serial': '**REDACTED**',
'version': '1.0.0',
}),
'hosts': list([ 'hosts': list([
dict({ dict({
'ip_address': '127.0.0.1', 'ip_address': '127.0.0.1',

View File

@ -1,6 +1,8 @@
"""Tests for the Heos config flow module.""" """Tests for the Heos config flow module."""
from pyheos import CommandAuthenticationError, CommandFailedError, Heos, HeosError from typing import Any
from pyheos import CommandAuthenticationError, CommandFailedError, HeosError
import pytest import pytest
from homeassistant.components.heos.const import DOMAIN from homeassistant.components.heos.const import DOMAIN
@ -10,6 +12,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo
from . import MockHeos
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
@ -38,7 +42,7 @@ async def test_no_host_shows_form(hass: HomeAssistant) -> None:
async def test_cannot_connect_shows_error_form( async def test_cannot_connect_shows_error_form(
hass: HomeAssistant, controller: Heos hass: HomeAssistant, controller: MockHeos
) -> None: ) -> None:
"""Test form is shown with error when cannot connect.""" """Test form is shown with error when cannot connect."""
controller.connect.side_effect = HeosError() controller.connect.side_effect = HeosError()
@ -47,13 +51,15 @@ async def test_cannot_connect_shows_error_form(
) )
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user" assert result["step_id"] == "user"
assert result["errors"][CONF_HOST] == "cannot_connect" errors = result["errors"]
assert errors is not None
assert errors[CONF_HOST] == "cannot_connect"
assert controller.connect.call_count == 1 assert controller.connect.call_count == 1
assert controller.disconnect.call_count == 1 assert controller.disconnect.call_count == 1
async def test_create_entry_when_host_valid( async def test_create_entry_when_host_valid(
hass: HomeAssistant, controller: Heos hass: HomeAssistant, controller: MockHeos
) -> None: ) -> None:
"""Test result type is create entry when host is valid.""" """Test result type is create entry when host is valid."""
data = {CONF_HOST: "127.0.0.1"} data = {CONF_HOST: "127.0.0.1"}
@ -70,7 +76,7 @@ async def test_create_entry_when_host_valid(
async def test_create_entry_when_friendly_name_valid( async def test_create_entry_when_friendly_name_valid(
hass: HomeAssistant, controller: Heos hass: HomeAssistant, controller: MockHeos
) -> None: ) -> None:
"""Test result type is create entry when friendly name is valid.""" """Test result type is create entry when friendly name is valid."""
hass.data[DOMAIN] = {"Office (127.0.0.1)": "127.0.0.1"} hass.data[DOMAIN] = {"Office (127.0.0.1)": "127.0.0.1"}
@ -131,7 +137,7 @@ async def test_discovery_flow_aborts_already_setup(
async def test_reconfigure_validates_and_updates_config( async def test_reconfigure_validates_and_updates_config(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test reconfigure validates host and successfully updates.""" """Test reconfigure validates host and successfully updates."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -139,9 +145,9 @@ async def test_reconfigure_validates_and_updates_config(
assert config_entry.data[CONF_HOST] == "127.0.0.1" assert config_entry.data[CONF_HOST] == "127.0.0.1"
# Test reconfigure initially shows form with current host value. # Test reconfigure initially shows form with current host value.
host = next( schema = result["data_schema"]
key.default() for key in result["data_schema"].schema if key == CONF_HOST assert schema is not None
) host = next(key.default() for key in schema.schema if key == CONF_HOST)
assert host == "127.0.0.1" assert host == "127.0.0.1"
assert result["errors"] == {} assert result["errors"] == {}
assert result["step_id"] == "reconfigure" assert result["step_id"] == "reconfigure"
@ -161,7 +167,7 @@ async def test_reconfigure_validates_and_updates_config(
async def test_reconfigure_cannot_connect_recovers( async def test_reconfigure_cannot_connect_recovers(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test reconfigure cannot connect and recovers.""" """Test reconfigure cannot connect and recovers."""
controller.connect.side_effect = HeosError() controller.connect.side_effect = HeosError()
@ -176,11 +182,13 @@ async def test_reconfigure_cannot_connect_recovers(
assert controller.connect.call_count == 1 assert controller.connect.call_count == 1
assert controller.disconnect.call_count == 1 assert controller.disconnect.call_count == 1
host = next( schema = result["data_schema"]
key.default() for key in result["data_schema"].schema if key == CONF_HOST assert schema is not None
) host = next(key.default() for key in schema.schema if key == CONF_HOST)
assert host == "127.0.0.2" assert host == "127.0.0.2"
assert result["errors"][CONF_HOST] == "cannot_connect" errors = result["errors"]
assert errors is not None
assert errors[CONF_HOST] == "cannot_connect"
assert result["step_id"] == "reconfigure" assert result["step_id"] == "reconfigure"
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
@ -214,7 +222,7 @@ async def test_reconfigure_cannot_connect_recovers(
async def test_options_flow_signs_in( async def test_options_flow_signs_in(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
error: HeosError, error: HeosError,
expected_error_key: str, expected_error_key: str,
) -> None: ) -> None:
@ -255,7 +263,7 @@ async def test_options_flow_signs_in(
async def test_options_flow_signs_out( async def test_options_flow_signs_out(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test options flow signs-out when credentials cleared.""" """Test options flow signs-out when credentials cleared."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -268,7 +276,7 @@ async def test_options_flow_signs_out(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
# Fail to sign-out, show error # Fail to sign-out, show error
user_input = {} user_input: dict[str, Any] = {}
controller.sign_out.side_effect = HeosError() controller.sign_out.side_effect = HeosError()
result = await hass.config_entries.options.async_configure( result = await hass.config_entries.options.async_configure(
result["flow_id"], user_input result["flow_id"], user_input
@ -301,7 +309,7 @@ async def test_options_flow_signs_out(
async def test_options_flow_missing_one_param_recovers( async def test_options_flow_missing_one_param_recovers(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
user_input: dict[str, str], user_input: dict[str, str],
expected_errors: dict[str, str], expected_errors: dict[str, str],
) -> None: ) -> None:
@ -350,7 +358,7 @@ async def test_options_flow_missing_one_param_recovers(
async def test_reauth_signs_in_aborts( async def test_reauth_signs_in_aborts(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
error: HeosError, error: HeosError,
expected_error_key: str, expected_error_key: str,
) -> None: ) -> None:
@ -391,7 +399,7 @@ async def test_reauth_signs_in_aborts(
async def test_reauth_signs_out( async def test_reauth_signs_out(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test reauth flow signs-out when credentials cleared and aborts.""" """Test reauth flow signs-out when credentials cleared and aborts."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -404,7 +412,7 @@ async def test_reauth_signs_out(
assert result["type"] is FlowResultType.FORM assert result["type"] is FlowResultType.FORM
# Fail to sign-out, show error # Fail to sign-out, show error
user_input = {} user_input: dict[str, Any] = {}
controller.sign_out.side_effect = HeosError() controller.sign_out.side_effect = HeosError()
result = await hass.config_entries.flow.async_configure( result = await hass.config_entries.flow.async_configure(
result["flow_id"], user_input result["flow_id"], user_input
@ -439,7 +447,7 @@ async def test_reauth_signs_out(
async def test_reauth_flow_missing_one_param_recovers( async def test_reauth_flow_missing_one_param_recovers(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
user_input: dict[str, str], user_input: dict[str, str],
expected_errors: dict[str, str], expected_errors: dict[str, str],
) -> None: ) -> None:

View File

@ -2,15 +2,17 @@
from unittest import mock from unittest import mock
from pyheos import Heos, HeosSystem from pyheos import HeosSystem
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy.assertion import SnapshotAssertion
from syrupy.filters import props from syrupy.filters import props
from homeassistant.components.heos.const import DOMAIN from homeassistant.components.heos.const import DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from . import MockHeos
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
from tests.components.diagnostics import ( from tests.components.diagnostics import (
get_diagnostics_for_config_entry, get_diagnostics_for_config_entry,
@ -23,7 +25,7 @@ async def test_config_entry_diagnostics(
hass: HomeAssistant, hass: HomeAssistant,
hass_client: ClientSessionGenerator, hass_client: ClientSessionGenerator,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
system: HeosSystem, system: HeosSystem,
snapshot: SnapshotAssertion, snapshot: SnapshotAssertion,
) -> None: ) -> None:
@ -77,7 +79,7 @@ async def test_device_diagnostics(
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
device_registry = dr.async_get(hass) device_registry = dr.async_get(hass)
device = device_registry.async_get_device({(DOMAIN, "1")}) device = device_registry.async_get_device({(DOMAIN, "1")})
assert device is not None
diagnostics = await get_diagnostics_for_device( diagnostics = await get_diagnostics_for_device(
hass, hass_client, config_entry, device hass, hass_client, config_entry, device
) )

View File

@ -1,8 +1,9 @@
"""Tests for the init module.""" """Tests for the init module."""
from typing import cast from typing import cast
from unittest.mock import Mock
from pyheos import Heos, HeosError, HeosOptions, SignalHeosEvent, SignalType from pyheos import HeosError, HeosOptions, SignalHeosEvent, SignalType
import pytest import pytest
from homeassistant.components.heos.const import DOMAIN from homeassistant.components.heos.const import DOMAIN
@ -11,13 +12,15 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr from homeassistant.helpers import device_registry as dr
from . import MockHeos
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_async_setup_entry_loads_platforms( async def test_async_setup_entry_loads_platforms(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
) -> None: ) -> None:
"""Test load connects to heos, retrieves players, and loads platforms.""" """Test load connects to heos, retrieves players, and loads platforms."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -32,7 +35,10 @@ async def test_async_setup_entry_loads_platforms(
async def test_async_setup_entry_with_options_loads_platforms( async def test_async_setup_entry_with_options_loads_platforms(
hass: HomeAssistant, config_entry_options: MockConfigEntry, controller: Heos hass: HomeAssistant,
config_entry_options: MockConfigEntry,
controller: MockHeos,
new_mock: Mock,
) -> None: ) -> None:
"""Test load connects to heos with options, retrieves players, and loads platforms.""" """Test load connects to heos with options, retrieves players, and loads platforms."""
config_entry_options.add_to_hass(hass) config_entry_options.add_to_hass(hass)
@ -40,8 +46,9 @@ async def test_async_setup_entry_with_options_loads_platforms(
# Assert options passed and methods called # Assert options passed and methods called
assert config_entry_options.state is ConfigEntryState.LOADED assert config_entry_options.state is ConfigEntryState.LOADED
options = cast(HeosOptions, controller.new_mock.call_args[0][0]) options = cast(HeosOptions, new_mock.call_args[0][0])
assert options.host == config_entry_options.data[CONF_HOST] assert options.host == config_entry_options.data[CONF_HOST]
assert options.credentials is not None
assert options.credentials.username == config_entry_options.options[CONF_USERNAME] assert options.credentials.username == config_entry_options.options[CONF_USERNAME]
assert options.credentials.password == config_entry_options.options[CONF_PASSWORD] assert options.credentials.password == config_entry_options.options[CONF_PASSWORD]
assert controller.connect.call_count == 1 assert controller.connect.call_count == 1
@ -54,14 +61,14 @@ async def test_async_setup_entry_with_options_loads_platforms(
async def test_async_setup_entry_auth_failure_starts_reauth( async def test_async_setup_entry_auth_failure_starts_reauth(
hass: HomeAssistant, hass: HomeAssistant,
config_entry_options: MockConfigEntry, config_entry_options: MockConfigEntry,
controller: Heos, controller: MockHeos,
) -> None: ) -> None:
"""Test load with auth failure starts reauth, loads platforms.""" """Test load with auth failure starts reauth, loads platforms."""
config_entry_options.add_to_hass(hass) config_entry_options.add_to_hass(hass)
# Simulates what happens when the controller can't sign-in during connection # Simulates what happens when the controller can't sign-in during connection
async def connect_send_auth_failure() -> None: async def connect_send_auth_failure() -> None:
controller._signed_in_username = None controller.mock_set_signed_in_username(None)
await controller.dispatcher.wait_send( await controller.dispatcher.wait_send(
SignalType.HEOS_EVENT, SignalHeosEvent.USER_CREDENTIALS_INVALID SignalType.HEOS_EVENT, SignalHeosEvent.USER_CREDENTIALS_INVALID
) )
@ -76,19 +83,19 @@ async def test_async_setup_entry_auth_failure_starts_reauth(
controller.disconnect.assert_not_called() controller.disconnect.assert_not_called()
assert config_entry_options.state is ConfigEntryState.LOADED assert config_entry_options.state is ConfigEntryState.LOADED
assert any( assert any(
config_entry_options.async_get_active_flows(hass, sources=[SOURCE_REAUTH]) config_entry_options.async_get_active_flows(hass, sources={SOURCE_REAUTH})
) )
async def test_async_setup_entry_not_signed_in_loads_platforms( async def test_async_setup_entry_not_signed_in_loads_platforms(
hass: HomeAssistant, hass: HomeAssistant,
config_entry: MockConfigEntry, config_entry: MockConfigEntry,
controller: Heos, controller: MockHeos,
caplog: pytest.LogCaptureFixture, caplog: pytest.LogCaptureFixture,
) -> None: ) -> None:
"""Test setup does not retrieve favorites when not logged in.""" """Test setup does not retrieve favorites when not logged in."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
controller._signed_in_username = None controller.mock_set_signed_in_username(None)
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
assert controller.connect.call_count == 1 assert controller.connect.call_count == 1
assert controller.get_players.call_count == 1 assert controller.get_players.call_count == 1
@ -102,7 +109,7 @@ async def test_async_setup_entry_not_signed_in_loads_platforms(
async def test_async_setup_entry_connect_failure( async def test_async_setup_entry_connect_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Connection failure raises ConfigEntryNotReady.""" """Connection failure raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -114,7 +121,7 @@ async def test_async_setup_entry_connect_failure(
async def test_async_setup_entry_player_failure( async def test_async_setup_entry_player_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Failure to retrieve players raises ConfigEntryNotReady.""" """Failure to retrieve players raises ConfigEntryNotReady."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -126,7 +133,7 @@ async def test_async_setup_entry_player_failure(
async def test_async_setup_entry_favorites_failure( async def test_async_setup_entry_favorites_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Failure to retrieve favorites loads.""" """Failure to retrieve favorites loads."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -136,7 +143,7 @@ async def test_async_setup_entry_favorites_failure(
async def test_async_setup_entry_inputs_failure( async def test_async_setup_entry_inputs_failure(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Failure to retrieve inputs loads.""" """Failure to retrieve inputs loads."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -146,7 +153,7 @@ async def test_async_setup_entry_inputs_failure(
async def test_unload_entry( async def test_unload_entry(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test entries are unloaded correctly.""" """Test entries are unloaded correctly."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -164,12 +171,14 @@ async def test_device_info(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id) assert await hass.config_entries.async_setup(config_entry.entry_id)
device = device_registry.async_get_device({(DOMAIN, "1")}) device = device_registry.async_get_device({(DOMAIN, "1")})
assert device is not None
assert device.manufacturer == "HEOS" assert device.manufacturer == "HEOS"
assert device.model == "Drive HS2" assert device.model == "Drive HS2"
assert device.name == "Test Player" assert device.name == "Test Player"
assert device.serial_number == "123456" assert device.serial_number == "123456"
assert device.sw_version == "1.0.0" assert device.sw_version == "1.0.0"
device = device_registry.async_get_device({(DOMAIN, "2")}) device = device_registry.async_get_device({(DOMAIN, "2")})
assert device is not None
assert device.manufacturer == "HEOS" assert device.manufacturer == "HEOS"
assert device.model == "Speaker" assert device.model == "Speaker"
@ -183,12 +192,14 @@ async def test_device_id_migration(
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
# Create a device with a legacy identifier # Create a device with a legacy identifier
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, identifiers={(DOMAIN, 1)} config_entry_id=config_entry.entry_id,
identifiers={(DOMAIN, 1)}, # type: ignore[arg-type]
) )
device_registry.async_get_or_create( device_registry.async_get_or_create(
config_entry_id=config_entry.entry_id, identifiers={("Other", 1)} config_entry_id=config_entry.entry_id,
identifiers={("Other", 1)}, # type: ignore[arg-type]
) )
assert await hass.config_entries.async_setup(config_entry.entry_id) 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({("Other", 1)}) is not None # type: ignore[arg-type]
assert device_registry.async_get_device({(DOMAIN, 1)}) is None assert device_registry.async_get_device({(DOMAIN, 1)}) is None # type: ignore[arg-type]
assert device_registry.async_get_device({(DOMAIN, "1")}) is not None assert device_registry.async_get_device({(DOMAIN, "1")}) is not None

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
"""Tests for the services module.""" """Tests for the services module."""
from pyheos import CommandAuthenticationError, Heos, HeosError from pyheos import CommandAuthenticationError, HeosError
import pytest import pytest
from homeassistant.components.heos.const import ( from homeassistant.components.heos.const import (
@ -13,11 +13,13 @@ from homeassistant.components.heos.const import (
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from . import MockHeos
from tests.common import MockConfigEntry from tests.common import MockConfigEntry
async def test_sign_in( async def test_sign_in(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test the sign-in service.""" """Test the sign-in service."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -34,7 +36,7 @@ async def test_sign_in(
async def test_sign_in_failed( async def test_sign_in_failed(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test sign-in service logs error when not connected.""" """Test sign-in service logs error when not connected."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -56,7 +58,7 @@ async def test_sign_in_failed(
async def test_sign_in_unknown_error( async def test_sign_in_unknown_error(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test sign-in service logs error for failure.""" """Test sign-in service logs error for failure."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -93,7 +95,7 @@ async def test_sign_in_not_loaded_raises(
async def test_sign_out( async def test_sign_out(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test the sign-out service.""" """Test the sign-out service."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)
@ -117,7 +119,7 @@ async def test_sign_out_not_loaded_raises(
async def test_sign_out_unknown_error( async def test_sign_out_unknown_error(
hass: HomeAssistant, config_entry: MockConfigEntry, controller: Heos hass: HomeAssistant, config_entry: MockConfigEntry, controller: MockHeos
) -> None: ) -> None:
"""Test the sign-out service.""" """Test the sign-out service."""
config_entry.add_to_hass(hass) config_entry.add_to_hass(hass)