hass-core/tests/components/bang_olufsen/conftest.py
Markus Jacobsen e3dfa84d65
Bang & Olufsen add beolink grouping (#113438)
* Add Beolink custom services
Add support for media player grouping via beolink
Give media player entity name

* Fix progress not being set to None as Beolink listener
Revert naming changes

* Update API
simplify Beolink attributes

* Improve beolink custom services

* Fix Beolink expandable source check
Add unexpand return value
Set entity name on initialization

* Handle entity naming as intended

* Fix "null" Beolink self friendly name

* Add regex service input validation
Add all_discovered to beolink_expand service
Improve beolink_expand response

* Add service icons

* Fix merge
Remove unnecessary assignment

* Remove invalid typing
Update response typing for updated API

* Revert to old typed response dict method
Remove mypy ignore line
Fix jid possibly used before assignment

* Re add debugging logging

* Fix coroutine
Fix formatting

* Remove unnecessary update control

* Make tests pass
Fix remote leader media position bug
Improve remote leader BangOlufsenSource comparison

* Fix naming and add callback decorators

* Move regex service check to variable
Suppress KeyError
Update tests

* Re-add hass running check

* Improve comments, naming and type hinting

* Remove old temporary fix

* Convert logged warning to raised exception for invalid media_player
Simplify code using walrus operator

* Fix test for invalid media_player grouping

* Improve method naming

* Improve _beolink_sources explanation

* Improve _beolink_sources explanation

* Fix tests

* Remove service responses
Fix and add tests

* Change service to action where applicable

* Show playback progress for listeners

* Fix testing

* Remove useless initialization

* Fix allstandby name

* Fix various casts with assertions
Fix comment placement
Fix group leader group_members rebase error
Replace entity_id method call with attribute

* Add syrupy snapshots for Beolink tests, checking entity states
Use test JIDs 3 and 4 instead of 2 and 3 to avoid invalid attributes in testing

* Add sections for fields using Beolink JIDs directly

* Fix typo

* FIx rebase mistake

* Sort actions alphabetically
2024-11-08 12:06:29 +01:00

368 lines
13 KiB
Python

"""Test fixtures for bang_olufsen."""
from collections.abc import Generator
from unittest.mock import AsyncMock, Mock, patch
from mozart_api.models import (
Action,
BeolinkPeer,
BeolinkSelf,
ContentItem,
ListeningMode,
ListeningModeFeatures,
ListeningModeRef,
ListeningModeTrigger,
PlaybackContentMetadata,
PlaybackProgress,
PlaybackState,
PlayQueueSettings,
ProductState,
RemoteMenuItem,
RenderingState,
SoftwareUpdateState,
SoftwareUpdateStatus,
Source,
SourceArray,
SourceTypeEnum,
VolumeState,
)
import pytest
from homeassistant.components.bang_olufsen.const import DOMAIN
from homeassistant.core import HomeAssistant
from .const import (
TEST_DATA_CREATE_ENTRY,
TEST_DATA_CREATE_ENTRY_2,
TEST_FRIENDLY_NAME,
TEST_FRIENDLY_NAME_3,
TEST_FRIENDLY_NAME_4,
TEST_HOST_3,
TEST_HOST_4,
TEST_JID_1,
TEST_JID_3,
TEST_JID_4,
TEST_NAME,
TEST_NAME_2,
TEST_SERIAL_NUMBER,
TEST_SERIAL_NUMBER_2,
TEST_SOUND_MODE,
TEST_SOUND_MODE_2,
TEST_SOUND_MODE_NAME,
)
from tests.common import MockConfigEntry
@pytest.fixture
def mock_config_entry() -> MockConfigEntry:
"""Mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id=TEST_SERIAL_NUMBER,
data=TEST_DATA_CREATE_ENTRY,
title=TEST_NAME,
)
@pytest.fixture
def mock_config_entry_2() -> MockConfigEntry:
"""Mock config entry."""
return MockConfigEntry(
domain=DOMAIN,
unique_id=TEST_SERIAL_NUMBER_2,
data=TEST_DATA_CREATE_ENTRY_2,
title=TEST_NAME_2,
)
@pytest.fixture
async def mock_media_player(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_mozart_client: AsyncMock,
) -> None:
"""Mock media_player entity."""
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
@pytest.fixture
def mock_mozart_client() -> Generator[AsyncMock]:
"""Mock MozartClient."""
with (
patch(
"homeassistant.components.bang_olufsen.MozartClient", autospec=True
) as mock_client,
patch(
"homeassistant.components.bang_olufsen.config_flow.MozartClient",
new=mock_client,
),
):
client = mock_client.return_value
# REST API client methods
client.get_beolink_self = AsyncMock()
client.get_beolink_self.return_value = BeolinkSelf(
friendly_name=TEST_FRIENDLY_NAME, jid=TEST_JID_1
)
client.get_softwareupdate_status = AsyncMock()
client.get_softwareupdate_status.return_value = SoftwareUpdateStatus(
software_version="1.0.0", state=SoftwareUpdateState()
)
client.get_product_state = AsyncMock()
client.get_product_state.return_value = ProductState(
volume=VolumeState(),
playback=PlaybackState(
metadata=PlaybackContentMetadata(),
progress=PlaybackProgress(),
source=Source(),
state=RenderingState(value="started"),
),
)
client.get_available_sources = AsyncMock()
client.get_available_sources.return_value = SourceArray(
items=[
# Is not playable, so should not be user selectable
Source(
name="AirPlay",
id="airPlay",
is_enabled=True,
is_multiroom_available=False,
),
# The only available beolink source
Source(
name="Tidal",
id="tidal",
is_enabled=True,
is_multiroom_available=True,
is_playable=True,
),
Source(
name="Line-In",
id="lineIn",
is_enabled=True,
is_multiroom_available=False,
is_playable=True,
),
# Is disabled and not playable, so should not be user selectable
Source(
name="Powerlink",
id="pl",
is_enabled=False,
),
]
)
client.get_remote_menu = AsyncMock()
client.get_remote_menu.return_value = {
# Music category, so shouldn't be included in video sources
"b355888b-2cde-5f94-8592-d47b71d52a27": RemoteMenuItem(
action_list=[
Action(
button_name=None,
content_id="netRadio://6629967157728971",
deezer_user_id=None,
gain_db=None,
listening_mode_id=None,
preset_key=None,
queue_item=None,
queue_settings=None,
radio_station_id=None,
source=None,
speaker_group_id=None,
stand_position=None,
stop_duration=None,
tone_name=None,
type="triggerContent",
volume_level=None,
)
],
scene_list=None,
disabled=None,
dynamic_list=None,
first_child_menu_item_id=None,
label="Yle Radio Suomi Helsinki",
next_sibling_menu_item_id="0b4552f8-7ac6-5046-9d44-5410a815b8d6",
parent_menu_item_id="eee0c2d0-2b3a-4899-a708-658475c38926",
available=None,
content=ContentItem(
categories=["music"],
content_uri="netRadio://6629967157728971",
label="Yle Radio Suomi Helsinki",
source=SourceTypeEnum(value="netRadio"),
),
fixed=True,
id="b355888b-2cde-5f94-8592-d47b71d52a27",
),
# Has "hdmi" as category, so should be included in video sources
"b6591565-80f4-4356-bcd9-c92ca247f0a9": RemoteMenuItem(
action_list=[
Action(
button_name=None,
content_id="tv://hdmi_1",
deezer_user_id=None,
gain_db=None,
listening_mode_id=None,
preset_key=None,
queue_item=None,
queue_settings=None,
radio_station_id=None,
source=None,
speaker_group_id=None,
stand_position=None,
stop_duration=None,
tone_name=None,
type="triggerContent",
volume_level=None,
)
],
scene_list=None,
disabled=False,
dynamic_list="none",
first_child_menu_item_id=None,
label="HDMI A",
next_sibling_menu_item_id="0ba98974-7b1f-40dc-bc48-fbacbb0f1793",
parent_menu_item_id="b66c835b-6b98-4400-8f84-6348043792c7",
available=True,
content=ContentItem(
categories=["hdmi"],
content_uri="tv://hdmi_1",
label="HDMI A",
source=SourceTypeEnum(value="tv"),
),
fixed=False,
id="b6591565-80f4-4356-bcd9-c92ca247f0a9",
),
# The parent remote menu item. Has the TV label and should therefore not be included in video sources
"b66c835b-6b98-4400-8f84-6348043792c7": RemoteMenuItem(
action_list=[],
scene_list=None,
disabled=False,
dynamic_list="none",
first_child_menu_item_id="b6591565-80f4-4356-bcd9-c92ca247f0a9",
label="TV",
next_sibling_menu_item_id="0c4547fe-d3cc-4348-a425-473595b8c9fb",
parent_menu_item_id=None,
available=True,
content=None,
fixed=True,
id="b66c835b-6b98-4400-8f84-6348043792c7",
),
# Has an empty content, so should not be included
"64c9da45-3682-44a4-8030-09ed3ef44160": RemoteMenuItem(
action_list=[],
scene_list=None,
disabled=False,
dynamic_list="none",
first_child_menu_item_id=None,
label="ListeningPosition",
next_sibling_menu_item_id=None,
parent_menu_item_id="0c4547fe-d3cc-4348-a425-473595b8c9fb",
available=True,
content=None,
fixed=True,
id="64c9da45-3682-44a4-8030-09ed3ef44160",
),
}
client.get_beolink_peers = AsyncMock()
client.get_beolink_peers.return_value = [
BeolinkPeer(
friendly_name=TEST_FRIENDLY_NAME_3,
jid=TEST_JID_3,
ip_address=TEST_HOST_3,
),
BeolinkPeer(
friendly_name=TEST_FRIENDLY_NAME_4,
jid=TEST_JID_4,
ip_address=TEST_HOST_4,
),
]
client.get_beolink_listeners = AsyncMock()
client.get_beolink_listeners.return_value = [
BeolinkPeer(
friendly_name=TEST_FRIENDLY_NAME_3,
jid=TEST_JID_3,
ip_address=TEST_HOST_3,
),
BeolinkPeer(
friendly_name=TEST_FRIENDLY_NAME_4,
jid=TEST_JID_4,
ip_address=TEST_HOST_4,
),
]
client.get_listening_mode_set = AsyncMock()
client.get_listening_mode_set.return_value = [
ListeningMode(
id=TEST_SOUND_MODE,
name=TEST_SOUND_MODE_NAME,
features=ListeningModeFeatures(),
triggers=[ListeningModeTrigger()],
),
ListeningMode(
id=TEST_SOUND_MODE_2,
name=TEST_SOUND_MODE_NAME,
features=ListeningModeFeatures(),
triggers=[ListeningModeTrigger()],
),
ListeningMode(
id=345,
name=f"{TEST_SOUND_MODE_NAME} 2",
features=ListeningModeFeatures(),
triggers=[ListeningModeTrigger()],
),
]
client.get_active_listening_mode = AsyncMock()
client.get_active_listening_mode.return_value = ListeningModeRef(
href="",
id=123,
)
client.get_settings_queue = AsyncMock()
client.get_settings_queue.return_value = PlayQueueSettings(
repeat="none",
shuffle=False,
)
client.post_standby = AsyncMock()
client.set_current_volume_level = AsyncMock()
client.set_volume_mute = AsyncMock()
client.post_playback_command = AsyncMock()
client.seek_to_position = AsyncMock()
client.post_clear_queue = AsyncMock()
client.post_overlay_play = AsyncMock()
client.post_uri_source = AsyncMock()
client.run_provided_scene = AsyncMock()
client.activate_preset = AsyncMock()
client.start_deezer_flow = AsyncMock()
client.add_to_queue = AsyncMock()
client.post_remote_trigger = AsyncMock()
client.set_active_source = AsyncMock()
client.post_beolink_expand = AsyncMock()
client.join_beolink_peer = AsyncMock()
client.post_beolink_unexpand = AsyncMock()
client.post_beolink_leave = AsyncMock()
client.post_beolink_allstandby = AsyncMock()
client.join_latest_beolink_experience = AsyncMock()
client.activate_listening_mode = AsyncMock()
client.set_settings_queue = AsyncMock()
# Non-REST API client methods
client.check_device_connection = AsyncMock()
client.close_api_client = AsyncMock()
# WebSocket listener
client.connect_notifications = AsyncMock()
client.disconnect_notifications = Mock()
client.websocket_connected = False
yield client
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Mock successful setup entry."""
with patch(
"homeassistant.components.bang_olufsen.async_setup_entry", return_value=True
) as mock_setup_entry:
yield mock_setup_entry