Improve test coverage for WiiM (#177545)

Co-authored-by: Tao Jiang <tao.jiang@linkplay.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
Linkplay2020
2026-08-01 23:19:29 +02:00
committed by GitHub
co-authored by Tao Jiang Copilot Autofix powered by AI
parent 41951c9613
commit ae9052304d
4 changed files with 186 additions and 5 deletions
@@ -45,10 +45,7 @@ rules:
log-when-unavailable: done
parallel-updates: todo
reauthentication-flow: todo
test-coverage:
status: todo
comment: |
- Increase test coverage for the media_player platform
test-coverage: done
# Gold
devices: done
+32
View File
@@ -0,0 +1,32 @@
"""Tests for WiiM entities."""
from unittest.mock import MagicMock
import pytest
from homeassistant.components.wiim.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from . import setup_integration
from tests.common import MockConfigEntry
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_device_info_uses_http_api_url(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test the HTTP API URL is used when no presentation URL is available."""
mock_wiim_device.presentation_url = None
await setup_integration(hass, mock_config_entry)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, mock_wiim_device.udn)}
)
assert device_entry is not None
assert device_entry.configuration_url == mock_wiim_device.http_api_url
+54
View File
@@ -6,6 +6,7 @@ import pytest
from wiim.exceptions import WiimDeviceException, WiimRequestException
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import HomeAssistant
from homeassistant.core_config import async_process_ha_core_config
@@ -31,6 +32,39 @@ async def test_load_unload_entry(
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_shutdown_disconnects_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wiim_device: AsyncMock,
) -> None:
"""Test the device is disconnected when Home Assistant stops."""
await setup_integration(hass, mock_config_entry)
hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
await hass.async_block_till_done()
mock_wiim_device.disconnect.assert_awaited_once_with()
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_unload_entry_fails_when_platform_cannot_unload(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the entry reports a failed unload when its platform cannot unload."""
await setup_integration(hass, mock_config_entry)
with patch.object(
hass.config_entries,
"async_unload_platforms",
return_value=False,
):
assert not await hass.config_entries.async_unload(mock_config_entry.entry_id)
assert mock_config_entry.state is ConfigEntryState.FAILED_UNLOAD
@pytest.mark.parametrize(
("exc", "translation_key"),
[
@@ -82,6 +116,26 @@ async def test_setup_raises_config_entry_not_ready_when_no_url(
assert mock_config_entry.error_reason_translation_placeholders is None
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_setup_retries_when_url_has_no_hostname(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test a Home Assistant URL without a hostname causes setup to retry."""
mock_config_entry.add_to_hass(hass)
with patch(
"homeassistant.components.wiim.util.get_url",
return_value="not-a-url",
):
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
assert mock_config_entry.error_reason_translation_key == "missing_homeassistant_url"
assert mock_config_entry.error_reason_translation_placeholders is None
async def test_setup_no_url_after_core_config(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
+99 -1
View File
@@ -34,9 +34,12 @@ from homeassistant.components.media_player import (
DOMAIN as MEDIA_PLAYER_DOMAIN,
SERVICE_BROWSE_MEDIA,
SERVICE_JOIN,
SERVICE_MEDIA_NEXT_TRACK,
SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_PLAY,
SERVICE_MEDIA_PREVIOUS_TRACK,
SERVICE_MEDIA_SEEK,
SERVICE_MEDIA_STOP,
SERVICE_PLAY_MEDIA,
SERVICE_REPEAT_SET,
SERVICE_SELECT_SOURCE,
@@ -54,7 +57,7 @@ from homeassistant.components.media_player import (
)
import homeassistant.components.wiim as wiim_component
from homeassistant.components.wiim.const import DOMAIN
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST
from homeassistant.const import ATTR_ENTITY_ID, CONF_HOST, STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
@@ -190,6 +193,39 @@ async def test_state_machine_updates_from_device_callbacks(
)
async def test_general_update_handles_offline_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
mock_wiim_controller: MagicMock,
) -> None:
"""Test an offline update marks the media player unavailable."""
await setup_integration(hass, mock_config_entry)
mock_wiim_device.available = False
await fire_general_update(hass, mock_wiim_device)
state = hass.states.get(MEDIA_PLAYER_ENTITY_ID)
assert state is not None
assert state.state == STATE_UNAVAILABLE
mock_wiim_controller.async_update_all_multiroom_status.assert_awaited_once_with()
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_general_update_renews_http_subscriptions(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
) -> None:
"""Test an HTTP-capable device renews subscriptions on a general update."""
await setup_integration(hass, mock_config_entry)
mock_wiim_device.supports_http_api = True
await fire_general_update(hass, mock_wiim_device)
mock_wiim_device.ensure_subscriptions.assert_awaited_once_with()
async def test_state_machine_updates_from_transport_events(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
@@ -219,6 +255,52 @@ async def test_state_machine_updates_from_transport_events(
assert state.state == MediaPlayerState.IDLE
assert state.attributes.get(ATTR_MEDIA_TITLE) is None
mock_wiim_device.event_data = {"TransportState": "unknown"}
mock_wiim_device.av_transport_event_callback(MagicMock(), [])
await hass.async_block_till_done()
state = hass.states.get(MEDIA_PLAYER_ENTITY_ID)
assert state is not None
assert state.state == MediaPlayerState.IDLE
@pytest.mark.parametrize(
("service", "device_method"),
[
pytest.param(SERVICE_MEDIA_STOP, "async_stop", id="stop"),
pytest.param(SERVICE_MEDIA_NEXT_TRACK, "async_next", id="next"),
pytest.param(SERVICE_MEDIA_PREVIOUS_TRACK, "async_previous", id="previous"),
],
)
@pytest.mark.usefixtures("mock_wiim_controller")
async def test_transport_services_call_device(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
*,
service: str,
device_method: str,
) -> None:
"""Test transport services call the matching device command."""
mock_wiim_device.async_get_transport_capabilities.return_value = (
WiimTransportCapabilities(
can_next=True,
can_previous=True,
can_repeat=False,
can_shuffle=False,
)
)
await setup_integration(hass, mock_config_entry)
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
service,
{ATTR_ENTITY_ID: MEDIA_PLAYER_ENTITY_ID},
blocking=True,
)
getattr(mock_wiim_device, device_method).assert_awaited_once_with()
@pytest.mark.parametrize(
(
@@ -265,6 +347,7 @@ async def test_control_services_update_state_machine(
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
mock_wiim_controller: MagicMock,
*,
service: str,
service_data: dict[str, object],
device_method: str,
@@ -697,6 +780,19 @@ async def test_play_media_services_call_device_commands(
assert state.state == MediaPlayerState.PLAYING
assert state.attributes[ATTR_MEDIA_TITLE] == "Preset 1"
mock_wiim_device.play_preset.reset_mock()
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
{
ATTR_ENTITY_ID: MEDIA_PLAYER_ENTITY_ID,
ATTR_MEDIA_CONTENT_TYPE: "wiim_library",
ATTR_MEDIA_CONTENT_ID: "2",
},
blocking=True,
)
mock_wiim_device.play_preset.assert_awaited_once_with(2)
await hass.services.async_call(
MEDIA_PLAYER_DOMAIN,
SERVICE_PLAY_MEDIA,
@@ -744,6 +840,7 @@ async def test_play_media_validation_error_uses_translation(
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
mock_wiim_controller: MagicMock,
*,
media_type: MediaType | str,
media_id: str,
translation_key: str,
@@ -975,6 +1072,7 @@ async def test_browse_media_error_uses_translation(
mock_config_entry: MockConfigEntry,
mock_wiim_device: MagicMock,
mock_wiim_controller: MagicMock,
*,
media_content_type: MediaType,
media_content_id: str,
translation_key: str,