Refactor Vizio tests: shared fixtures, snapshot_platform, reduced parametrize (#167935)

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Raman Gupta
2026-04-10 16:19:25 -04:00
committed by GitHub
parent 2f91c6b050
commit 53ed4b2c77
6 changed files with 513 additions and 208 deletions

View File

@@ -7,6 +7,9 @@ import pytest
from pyvizio.api.apps import AppConfig
from pyvizio.const import DEVICE_CLASS_SPEAKER, MAX_VOLUME
from homeassistant.components.vizio.const import DOMAIN
from homeassistant.core import HomeAssistant
from .const import (
ACCESS_TOKEN,
APP_LIST,
@@ -17,6 +20,8 @@ from .const import (
EQ_LIST,
INPUT_LIST,
INPUT_LIST_WITH_APPS,
MOCK_SPEAKER_CONFIG,
MOCK_USER_VALID_TV_CONFIG,
MODEL,
RESPONSE_TOKEN,
UNIQUE_ID,
@@ -26,6 +31,8 @@ from .const import (
MockStartPairingResponse,
)
from tests.common import MockConfigEntry
class MockInput:
"""Mock Vizio device input."""
@@ -41,6 +48,33 @@ def get_mock_inputs(input_list) -> list[MockInput]:
return [MockInput(device_input) for device_input in input_list]
@pytest.fixture
def mock_tv_config_entry() -> MockConfigEntry:
"""Return a mock TV config entry."""
return MockConfigEntry(
domain=DOMAIN,
data=MOCK_USER_VALID_TV_CONFIG,
unique_id=UNIQUE_ID,
)
@pytest.fixture
def mock_speaker_config_entry() -> MockConfigEntry:
"""Return a mock speaker config entry."""
return MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
unique_id=UNIQUE_ID,
)
async def setup_integration(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Add config entry to hass and set up the integration."""
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
@pytest.fixture(name="vizio_get_unique_id", autouse=True)
def vizio_get_unique_id_fixture() -> Generator[None]:
"""Mock get vizio unique ID."""

View File

@@ -0,0 +1,157 @@
# serializer version: 1
# name: test_media_player_entity_setup[speaker][media_player.vizio-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'sound_mode_list': list([
'Music',
'Movie',
]),
'source_list': list([
'HDMI',
'USB',
'Bluetooth',
'AUX',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.vizio',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.SPEAKER: 'speaker'>,
'original_icon': None,
'original_name': None,
'platform': 'vizio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <MediaPlayerEntityFeature: 85389>,
'translation_key': None,
'unique_id': 'testid',
'unit_of_measurement': None,
})
# ---
# name: test_media_player_entity_setup[speaker][media_player.vizio-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'speaker',
'friendly_name': 'Vizio',
'is_volume_muted': False,
'last_non_buffering_state': <MediaPlayerState.ON: 'on'>,
'sound_mode': 'Music',
'sound_mode_list': list([
'Music',
'Movie',
]),
'source': 'HDMI',
'source_list': list([
'HDMI',
'USB',
'Bluetooth',
'AUX',
]),
'supported_features': <MediaPlayerEntityFeature: 85389>,
'volume_level': 0.4838709677419355,
}),
'context': <ANY>,
'entity_id': 'media_player.vizio',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_media_player_entity_setup[tv][media_player.vizio-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'sound_mode_list': list([
'Music',
'Movie',
]),
'source_list': list([
'HDMI',
'USB',
'Bluetooth',
'AUX',
]),
}),
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'media_player',
'entity_category': None,
'entity_id': 'media_player.vizio',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': <MediaPlayerDeviceClass.TV: 'tv'>,
'original_icon': None,
'original_name': None,
'platform': 'vizio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <MediaPlayerEntityFeature: 85437>,
'translation_key': None,
'unique_id': 'testid',
'unit_of_measurement': None,
})
# ---
# name: test_media_player_entity_setup[tv][media_player.vizio-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'tv',
'friendly_name': 'Vizio',
'is_volume_muted': False,
'last_non_buffering_state': <MediaPlayerState.ON: 'on'>,
'sound_mode': 'Music',
'sound_mode_list': list([
'Music',
'Movie',
]),
'source': 'HDMI',
'source_list': list([
'HDMI',
'USB',
'Bluetooth',
'AUX',
]),
'supported_features': <MediaPlayerEntityFeature: 85437>,
'volume_level': 0.15,
}),
'context': <ANY>,
'entity_id': 'media_player.vizio',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@@ -0,0 +1,103 @@
# serializer version: 1
# name: test_remote_entity_setup[speaker][remote.vizio-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'remote',
'entity_category': None,
'entity_id': 'remote.vizio',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'vizio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'testid',
'unit_of_measurement': None,
})
# ---
# name: test_remote_entity_setup[speaker][remote.vizio-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Vizio',
'supported_features': <RemoteEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'remote.vizio',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---
# name: test_remote_entity_setup[tv][remote.vizio-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'remote',
'entity_category': None,
'entity_id': 'remote.vizio',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'object_id_base': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': None,
'platform': 'vizio',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'testid',
'unit_of_measurement': None,
})
# ---
# name: test_remote_entity_setup[tv][remote.vizio-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Vizio',
'supported_features': <RemoteEntityFeature: 0>,
}),
'context': <ANY>,
'entity_id': 'remote.vizio',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'on',
})
# ---

View File

@@ -20,33 +20,22 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from .const import (
APP_LIST,
HOST2,
MOCK_SPEAKER_CONFIG,
MOCK_USER_VALID_TV_CONFIG,
MODEL,
NAME2,
UNIQUE_ID,
VERSION,
)
from .conftest import setup_integration
from .const import APP_LIST, HOST2, MODEL, NAME2, UNIQUE_ID, VERSION
from tests.common import MockConfigEntry, async_fire_time_changed
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_tv_load_and_unload(hass: HomeAssistant) -> None:
async def test_tv_load_and_unload(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test loading and unloading TV entry."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_tv_config_entry)
assert len(hass.states.async_entity_ids(Platform.MEDIA_PLAYER)) == 1
assert DATA_APPS in hass.data
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert await hass.config_entries.async_unload(mock_tv_config_entry.entry_id)
await hass.async_block_till_done()
entities = hass.states.async_entity_ids(Platform.MEDIA_PLAYER)
assert len(entities) == 1
@@ -56,17 +45,14 @@ async def test_tv_load_and_unload(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_speaker_load_and_unload(hass: HomeAssistant) -> None:
async def test_speaker_load_and_unload(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test loading and unloading speaker entry."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_speaker_config_entry)
assert len(hass.states.async_entity_ids(Platform.MEDIA_PLAYER)) == 1
assert await hass.config_entries.async_unload(config_entry.entry_id)
assert await hass.config_entries.async_unload(mock_speaker_config_entry.entry_id)
await hass.async_block_till_done()
entities = hass.states.async_entity_ids(Platform.MEDIA_PLAYER)
assert len(entities) == 1
@@ -79,16 +65,12 @@ async def test_speaker_load_and_unload(hass: HomeAssistant) -> None:
)
async def test_coordinator_update_failure(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test coordinator update failure after 10 days."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_tv_config_entry)
assert len(hass.states.async_entity_ids(Platform.MEDIA_PLAYER)) == 1
assert DATA_APPS in hass.data
@@ -105,12 +87,11 @@ async def test_coordinator_update_failure(
@pytest.mark.usefixtures("vizio_connect", "vizio_bypass_update")
async def test_apps_coordinator_persists_until_last_tv_unloads(
hass: HomeAssistant, freezer: FrozenDateTimeFactory
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test shared apps coordinator is not shut down until the last TV entry unloads."""
config_entry_1 = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry_2 = MockConfigEntry(
domain=DOMAIN,
data={
@@ -121,9 +102,7 @@ async def test_apps_coordinator_persists_until_last_tv_unloads(
},
unique_id="testid2",
)
config_entry_1.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry_1.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_tv_config_entry)
config_entry_2.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry_2.entry_id)
@@ -131,7 +110,7 @@ async def test_apps_coordinator_persists_until_last_tv_unloads(
assert len(hass.states.async_entity_ids(Platform.MEDIA_PLAYER)) == 2
# Unload first TV — coordinator should still be fetching apps
assert await hass.config_entries.async_unload(config_entry_1.entry_id)
assert await hass.config_entries.async_unload(mock_tv_config_entry.entry_id)
await hass.async_block_till_done()
with patch(
@@ -159,15 +138,12 @@ async def test_apps_coordinator_persists_until_last_tv_unloads(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_device_registry_model_and_version(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test that coordinator populates device registry with model and version."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_tv_config_entry)
device = device_registry.async_get_device(identifiers={(DOMAIN, UNIQUE_ID)})
assert device is not None
@@ -178,15 +154,12 @@ async def test_device_registry_model_and_version(
@pytest.mark.usefixtures("vizio_connect", "vizio_bypass_update")
async def test_device_registry_without_model_or_version(
hass: HomeAssistant, device_registry: dr.DeviceRegistry
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
device_registry: dr.DeviceRegistry,
) -> None:
"""Test device registry when model and version are unavailable."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
await setup_integration(hass, mock_tv_config_entry)
device = device_registry.async_get_device(identifiers={(DOMAIN, UNIQUE_ID)})
assert device is not None

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from collections.abc import AsyncIterator, Generator
from contextlib import asynccontextmanager
from datetime import timedelta
from typing import Any
@@ -19,6 +19,7 @@ from pyvizio.const import (
MAX_VOLUME,
UNKNOWN_APP,
)
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
@@ -51,10 +52,18 @@ from homeassistant.components.vizio.const import (
)
from homeassistant.components.vizio.services import SERVICE_UPDATE_SETTING
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
from homeassistant.const import (
ATTR_ENTITY_ID,
STATE_OFF,
STATE_ON,
STATE_UNAVAILABLE,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.util import dt as dt_util
from .conftest import setup_integration
from .const import (
ADDITIONAL_APP_CONFIG,
APP_LIST,
@@ -68,26 +77,45 @@ from .const import (
EQ_LIST,
INPUT_LIST,
INPUT_LIST_WITH_APPS,
MOCK_SPEAKER_CONFIG,
MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG,
MOCK_TV_WITH_EXCLUDE_CONFIG,
MOCK_TV_WITH_INCLUDE_CONFIG,
MOCK_USER_VALID_TV_CONFIG,
NAME,
UNIQUE_ID,
UNKNOWN_APP_CONFIG,
VOLUME_STEP,
)
from tests.common import MockConfigEntry, async_fire_time_changed
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
async def _add_config_entry_to_hass(
hass: HomeAssistant, config_entry: MockConfigEntry
@pytest.fixture(autouse=True)
def media_player_only() -> Generator[None]:
"""Only set up the media_player platform."""
with patch(
"homeassistant.components.vizio.PLATFORMS",
[Platform.MEDIA_PLAYER],
):
yield
@pytest.mark.parametrize(
"mock_config_entry_fixture",
["mock_tv_config_entry", "mock_speaker_config_entry"],
ids=["tv", "speaker"],
)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_media_player_entity_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_config_entry_fixture: str,
request: pytest.FixtureRequest,
) -> None:
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
"""Test media player entity is created for TV and speaker."""
config_entry: MockConfigEntry = request.getfixturevalue(mock_config_entry_fixture)
await setup_integration(hass, config_entry)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
def _get_ha_power_state(vizio_power_state: bool) -> str:
@@ -143,16 +171,12 @@ async def _cm_for_test_setup_without_apps(
yield
async def _test_setup_tv(hass: HomeAssistant, vizio_power_state: bool) -> None:
async def _test_setup_tv(
hass: HomeAssistant, config_entry: MockConfigEntry, vizio_power_state: bool
) -> None:
"""Test Vizio TV entity setup."""
ha_power_state = _get_ha_power_state(vizio_power_state)
config_entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_USER_VALID_TV_CONFIG,
unique_id=UNIQUE_ID,
)
async with _cm_for_test_setup_without_apps(
{
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2),
@@ -161,7 +185,7 @@ async def _test_setup_tv(hass: HomeAssistant, vizio_power_state: bool) -> None:
},
vizio_power_state,
):
await _add_config_entry_to_hass(hass, config_entry)
await setup_integration(hass, config_entry)
attr = _get_attr_and_assert_base_attr(
hass, MediaPlayerDeviceClass.TV, ha_power_state
@@ -171,16 +195,12 @@ async def _test_setup_tv(hass: HomeAssistant, vizio_power_state: bool) -> None:
assert attr[ATTR_SOUND_MODE] == CURRENT_EQ
async def _test_setup_speaker(hass: HomeAssistant, vizio_power_state: bool) -> None:
async def _test_setup_speaker(
hass: HomeAssistant, config_entry: MockConfigEntry, vizio_power_state: bool
) -> None:
"""Test Vizio Speaker entity setup."""
ha_power_state = _get_ha_power_state(vizio_power_state)
config_entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_SPEAKER_CONFIG,
unique_id=UNIQUE_ID,
)
audio_settings = {
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_SPEAKER] / 2),
"mute": "Off",
@@ -191,7 +211,7 @@ async def _test_setup_speaker(hass: HomeAssistant, vizio_power_state: bool) -> N
audio_settings,
vizio_power_state,
):
await _add_config_entry_to_hass(hass, config_entry)
await setup_integration(hass, config_entry)
attr = _get_attr_and_assert_base_attr(
hass, MediaPlayerDeviceClass.SPEAKER, ha_power_state
@@ -203,13 +223,9 @@ async def _test_setup_speaker(hass: HomeAssistant, vizio_power_state: bool) -> N
@asynccontextmanager
async def _cm_for_test_setup_tv_with_apps(
hass: HomeAssistant, device_config: dict[str, Any], app_config: dict[str, Any]
hass: HomeAssistant, config_entry: MockConfigEntry, app_config: dict[str, Any]
) -> AsyncIterator[None]:
"""Context manager to setup test for Vizio TV with support for apps."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=device_config, unique_id=UNIQUE_ID
)
async with _cm_for_test_setup_without_apps(
{"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2), "mute": "Off"},
True,
@@ -218,7 +234,7 @@ async def _cm_for_test_setup_tv_with_apps(
"homeassistant.components.vizio.VizioAsync.get_current_app_config",
return_value=AppConfig(**app_config),
):
await _add_config_entry_to_hass(hass, config_entry)
await setup_integration(hass, config_entry)
attr = _get_attr_and_assert_base_attr(
hass, MediaPlayerDeviceClass.TV, STATE_ON
@@ -274,57 +290,65 @@ async def _test_service(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_speaker_on(hass: HomeAssistant) -> None:
async def test_speaker_on(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test Vizio Speaker entity setup when on."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_speaker_off(hass: HomeAssistant) -> None:
async def test_speaker_off(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test Vizio Speaker entity setup when off."""
await _test_setup_speaker(hass, False)
await _test_setup_speaker(hass, mock_speaker_config_entry, False)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_init_tv_on(hass: HomeAssistant) -> None:
async def test_init_tv_on(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test Vizio TV entity setup when on."""
await _test_setup_tv(hass, True)
await _test_setup_tv(hass, mock_tv_config_entry, True)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_init_tv_off(hass: HomeAssistant) -> None:
async def test_init_tv_off(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test Vizio TV entity setup when off."""
await _test_setup_tv(hass, False)
await _test_setup_tv(hass, mock_tv_config_entry, False)
@pytest.mark.usefixtures("vizio_cant_connect")
async def test_setup_unavailable_speaker(hass: HomeAssistant) -> None:
async def test_setup_unavailable_speaker(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test speaker config entry retries setup when device is unavailable."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
mock_speaker_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_speaker_config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_RETRY
assert mock_speaker_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("vizio_cant_connect")
async def test_setup_unavailable_tv(hass: HomeAssistant) -> None:
async def test_setup_unavailable_tv(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test TV config entry retries setup when device is unavailable."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
)
config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(config_entry.entry_id)
mock_tv_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_tv_config_entry.entry_id)
await hass.async_block_till_done()
assert config_entry.state is ConfigEntryState.SETUP_RETRY
assert mock_tv_config_entry.state is ConfigEntryState.SETUP_RETRY
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_services(hass: HomeAssistant) -> None:
async def test_services(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test all Vizio media player entity services."""
await _test_setup_tv(hass, True)
await _test_setup_tv(hass, mock_tv_config_entry, True)
await _test_service(hass, MP_DOMAIN, "pow_on", SERVICE_TURN_ON, None)
await _test_service(hass, MP_DOMAIN, "pow_off", SERVICE_TURN_OFF, None)
@@ -410,9 +434,11 @@ async def test_services(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_options_update(hass: HomeAssistant) -> None:
async def test_options_update(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test when config entry update event fires."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
assert config_entry.options
new_options = config_entry.options.copy()
@@ -432,10 +458,11 @@ async def test_options_update(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_update_available_to_unavailable(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test device becomes unavailable after being available."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
# Simulate device becoming unreachable
with patch(
@@ -451,10 +478,11 @@ async def test_update_available_to_unavailable(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_update_unavailable_to_available(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test device becomes available after being unavailable."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
# First, make device unavailable
with patch(
@@ -480,11 +508,12 @@ async def test_update_unavailable_to_available(
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
async def test_setup_with_apps(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps."""
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, CURRENT_APP_CONFIG
hass, mock_tv_config_entry, CURRENT_APP_CONFIG
):
attr = hass.states.get(ENTITY_ID).attributes
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
@@ -510,9 +539,10 @@ async def test_setup_with_apps_include(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps and apps["include"] in config."""
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_TV_WITH_INCLUDE_CONFIG, CURRENT_APP_CONFIG
):
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_TV_WITH_INCLUDE_CONFIG, unique_id=UNIQUE_ID
)
async with _cm_for_test_setup_tv_with_apps(hass, config_entry, CURRENT_APP_CONFIG):
attr = hass.states.get(ENTITY_ID).attributes
_assert_source_list_with_apps([*INPUT_LIST_WITH_APPS, CURRENT_APP], attr)
assert CURRENT_APP in attr[ATTR_INPUT_SOURCE_LIST]
@@ -527,9 +557,10 @@ async def test_setup_with_apps_exclude(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps and apps["exclude"] in config."""
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_TV_WITH_EXCLUDE_CONFIG, CURRENT_APP_CONFIG
):
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_TV_WITH_EXCLUDE_CONFIG, unique_id=UNIQUE_ID
)
async with _cm_for_test_setup_tv_with_apps(hass, config_entry, CURRENT_APP_CONFIG):
attr = hass.states.get(ENTITY_ID).attributes
_assert_source_list_with_apps([*INPUT_LIST_WITH_APPS, CURRENT_APP], attr)
assert CURRENT_APP in attr[ATTR_INPUT_SOURCE_LIST]
@@ -544,9 +575,12 @@ async def test_setup_with_apps_additional_apps_config(
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps and apps["additional_configs"] in config."""
config_entry = MockConfigEntry(
domain=DOMAIN, data=MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG, unique_id=UNIQUE_ID
)
async with _cm_for_test_setup_tv_with_apps(
hass,
MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG,
config_entry,
ADDITIONAL_APP_CONFIG["config"],
):
attr = hass.states.get(ENTITY_ID).attributes
@@ -608,11 +642,12 @@ async def test_setup_with_apps_additional_apps_config(
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
async def test_setup_with_unknown_app_config(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps where app config returned is unknown."""
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, UNKNOWN_APP_CONFIG
hass, mock_tv_config_entry, UNKNOWN_APP_CONFIG
):
attr = hass.states.get(ENTITY_ID).attributes
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
@@ -624,11 +659,12 @@ async def test_setup_with_unknown_app_config(
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
async def test_setup_with_no_running_app(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps where no app is running."""
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, vars(AppConfig())
hass, mock_tv_config_entry, vars(AppConfig())
):
attr = hass.states.get(ENTITY_ID).attributes
_assert_source_list_with_apps(list(INPUT_LIST_WITH_APPS + APP_NAME_LIST), attr)
@@ -638,19 +674,15 @@ async def test_setup_with_no_running_app(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_setup_tv_without_mute(hass: HomeAssistant) -> None:
async def test_setup_tv_without_mute(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test Vizio TV entity setup when mute property isn't returned by Vizio API."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_USER_VALID_TV_CONFIG,
unique_id=UNIQUE_ID,
)
async with _cm_for_test_setup_without_apps(
{"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2)},
True,
):
await _add_config_entry_to_hass(hass, config_entry)
await setup_integration(hass, mock_tv_config_entry)
attr = _get_attr_and_assert_base_attr(hass, MediaPlayerDeviceClass.TV, STATE_ON)
_assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV)
@@ -661,6 +693,7 @@ async def test_setup_tv_without_mute(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
async def test_apps_update(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test device setup with apps where no app is running."""
@@ -669,7 +702,7 @@ async def test_apps_update(
return_value=None,
):
async with _cm_for_test_setup_tv_with_apps(
hass, MOCK_USER_VALID_TV_CONFIG, vars(AppConfig())
hass, mock_tv_config_entry, vars(AppConfig())
):
# Check source list, remove TV inputs, and verify that the integration is
# using the default APPS list
@@ -693,14 +726,11 @@ async def test_apps_update(
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps_on_input")
async def test_vizio_update_with_apps_on_input(hass: HomeAssistant) -> None:
async def test_vizio_update_with_apps_on_input(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test a vizio TV with apps that is on a TV input."""
config_entry = MockConfigEntry(
domain=DOMAIN,
data=MOCK_USER_VALID_TV_CONFIG,
unique_id=UNIQUE_ID,
)
await _add_config_entry_to_hass(hass, config_entry)
await setup_integration(hass, mock_tv_config_entry)
attr = _get_attr_and_assert_base_attr(hass, MediaPlayerDeviceClass.TV, STATE_ON)
# app ID should not be in the attributes
assert "app_id" not in attr
@@ -709,10 +739,11 @@ async def test_vizio_update_with_apps_on_input(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_coordinator_update_on_to_off(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test device transitions from on to off during coordinator refresh."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
attr = _get_attr_and_assert_base_attr(
hass, MediaPlayerDeviceClass.SPEAKER, STATE_ON
)
@@ -738,10 +769,11 @@ async def test_coordinator_update_on_to_off(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_coordinator_update_off_to_on(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test device transitions from off to on during coordinator refresh."""
await _test_setup_speaker(hass, False)
await _test_setup_speaker(hass, mock_speaker_config_entry, False)
assert hass.states.get(ENTITY_ID).state == STATE_OFF
# Device turns on
@@ -758,10 +790,11 @@ async def test_coordinator_update_off_to_on(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_sound_mode_feature_toggling(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test sound mode feature is added when present and removed when absent."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
attr = _get_attr_and_assert_base_attr(
hass, MediaPlayerDeviceClass.SPEAKER, STATE_ON
)
@@ -798,10 +831,11 @@ async def test_sound_mode_feature_toggling(
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_sound_mode_list_cached(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test sound mode list is cached after first retrieval."""
await _test_setup_speaker(hass, True)
await _test_setup_speaker(hass, mock_speaker_config_entry, True)
attr = hass.states.get(ENTITY_ID).attributes
assert attr["sound_mode_list"] == EQ_LIST

View File

@@ -2,9 +2,11 @@
from __future__ import annotations
from collections.abc import Generator
from unittest.mock import patch
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.remote import (
ATTR_COMMAND,
@@ -13,67 +15,65 @@ from homeassistant.components.remote import (
DOMAIN as REMOTE_DOMAIN,
SERVICE_SEND_COMMAND,
)
from homeassistant.components.vizio.const import DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_OFF,
STATE_ON,
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import entity_registry as er
from homeassistant.util import slugify
from .const import MOCK_SPEAKER_CONFIG, MOCK_USER_VALID_TV_CONFIG, NAME, UNIQUE_ID
from .conftest import setup_integration
from .const import NAME
from tests.common import MockConfigEntry
from tests.common import MockConfigEntry, snapshot_platform
REMOTE_ENTITY_ID = f"{REMOTE_DOMAIN}.{slugify(NAME)}"
async def _setup_entry(
hass: HomeAssistant, config: dict, unique_id: str = UNIQUE_ID
) -> MockConfigEntry:
"""Set up a Vizio config entry and return it."""
config_entry = MockConfigEntry(domain=DOMAIN, data=config, unique_id=unique_id)
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return config_entry
@pytest.fixture(autouse=True)
def remote_only() -> Generator[None]:
"""Only set up the remote platform."""
with patch(
"homeassistant.components.vizio.PLATFORMS",
[Platform.REMOTE],
):
yield
@pytest.mark.parametrize(
"config",
[MOCK_USER_VALID_TV_CONFIG, MOCK_SPEAKER_CONFIG],
"config_entry_fixture",
["mock_tv_config_entry", "mock_speaker_config_entry"],
ids=["tv", "speaker"],
)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_remote_entity_setup(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
config: dict,
snapshot: SnapshotAssertion,
config_entry_fixture: str,
request: pytest.FixtureRequest,
) -> None:
"""Test remote entity is created for TV and speaker."""
await _setup_entry(hass, config)
state = hass.states.get(REMOTE_ENTITY_ID)
assert state is not None
assert state.state == STATE_ON
entry = entity_registry.async_get(REMOTE_ENTITY_ID)
assert entry is not None
assert entry.unique_id == UNIQUE_ID
config_entry: MockConfigEntry = request.getfixturevalue(config_entry_fixture)
await setup_integration(hass, config_entry)
await snapshot_platform(hass, entity_registry, snapshot, config_entry.entry_id)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_remote_is_off_when_device_off(hass: HomeAssistant) -> None:
async def test_remote_is_off_when_device_off(
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
) -> None:
"""Test remote state is off when device is off."""
with patch(
"homeassistant.components.vizio.VizioAsync.get_power_state",
return_value=False,
):
await _setup_entry(hass, MOCK_SPEAKER_CONFIG)
await setup_integration(hass, mock_speaker_config_entry)
state = hass.states.get(REMOTE_ENTITY_ID)
assert state.state == STATE_OFF
@@ -86,9 +86,14 @@ async def test_remote_is_off_when_device_off(hass: HomeAssistant) -> None:
],
)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_turn_on_off(hass: HomeAssistant, service: str, mock_method: str) -> None:
async def test_turn_on_off(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
service: str,
mock_method: str,
) -> None:
"""Test turning on/off the remote sends the correct power command."""
await _setup_entry(hass, MOCK_SPEAKER_CONFIG)
await setup_integration(hass, mock_speaker_config_entry)
with patch(
f"homeassistant.components.vizio.VizioAsync.{mock_method}",
) as mock_power:
@@ -104,20 +109,20 @@ async def test_turn_on_off(hass: HomeAssistant, service: str, mock_method: str)
@pytest.mark.parametrize(
("command", "expected_key"),
[
# Native keys (lowercase tested for a few to verify case-insensitivity)
("BACK", "BACK"),
("CC_TOGGLE", "CC_TOGGLE"),
("ch_up", "CH_UP"),
("menu", "MENU"),
("SMARTCAST", "SMARTCAST"),
],
)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_tv_valid(
hass: HomeAssistant, command: str, expected_key: str
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
command: str,
expected_key: str,
) -> None:
"""Test send_command resolves valid TV commands."""
await _setup_entry(hass, MOCK_USER_VALID_TV_CONFIG)
await setup_integration(hass, mock_tv_config_entry)
with patch(
"homeassistant.components.vizio.VizioAsync.remote",
) as mock_remote:
@@ -135,9 +140,13 @@ async def test_send_command_tv_valid(
@pytest.mark.parametrize("command", ["INVALID_KEY", "not_a_key"])
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_tv_invalid(hass: HomeAssistant, command: str) -> None:
async def test_send_command_tv_invalid(
hass: HomeAssistant,
mock_tv_config_entry: MockConfigEntry,
command: str,
) -> None:
"""Test send_command raises error for invalid TV commands."""
await _setup_entry(hass, MOCK_USER_VALID_TV_CONFIG)
await setup_integration(hass, mock_tv_config_entry)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
REMOTE_DOMAIN,
@@ -153,25 +162,20 @@ async def test_send_command_tv_invalid(hass: HomeAssistant, command: str) -> Non
@pytest.mark.parametrize(
("command", "expected_key"),
[
# Native keys (lowercase tested for a couple)
("MUTE_OFF", "MUTE_OFF"),
("MUTE_ON", "MUTE_ON"),
("MUTE_TOGGLE", "MUTE_TOGGLE"),
("pause", "PAUSE"),
("PLAY", "PLAY"),
("POW_OFF", "POW_OFF"),
("POW_ON", "POW_ON"),
("POW_TOGGLE", "POW_TOGGLE"),
("vol_down", "VOL_DOWN"),
("VOL_UP", "VOL_UP"),
],
)
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_speaker_valid(
hass: HomeAssistant, command: str, expected_key: str
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
command: str,
expected_key: str,
) -> None:
"""Test send_command resolves valid speaker commands."""
await _setup_entry(hass, MOCK_SPEAKER_CONFIG)
await setup_integration(hass, mock_speaker_config_entry)
with patch(
"homeassistant.components.vizio.VizioAsync.remote",
) as mock_remote:
@@ -187,21 +191,15 @@ async def test_send_command_speaker_valid(
mock_remote.assert_called_once_with(expected_key, log_api_exception=False)
@pytest.mark.parametrize(
"command",
[
# TV-only native keys
"MENU",
"CH_UP",
"INPUT_NEXT",
# Completely invalid
"INVALID_KEY",
],
)
@pytest.mark.parametrize("command", ["MENU", "CH_UP", "INVALID_KEY"])
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_speaker_invalid(hass: HomeAssistant, command: str) -> None:
async def test_send_command_speaker_invalid(
hass: HomeAssistant,
mock_speaker_config_entry: MockConfigEntry,
command: str,
) -> None:
"""Test speaker remote rejects TV-only and invalid keys."""
await _setup_entry(hass, MOCK_SPEAKER_CONFIG)
await setup_integration(hass, mock_speaker_config_entry)
with pytest.raises(ServiceValidationError):
await hass.services.async_call(
REMOTE_DOMAIN,
@@ -215,9 +213,11 @@ async def test_send_command_speaker_invalid(hass: HomeAssistant, command: str) -
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_multiple(hass: HomeAssistant) -> None:
async def test_send_command_multiple(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test send_command with multiple commands in one call."""
await _setup_entry(hass, MOCK_USER_VALID_TV_CONFIG)
await setup_integration(hass, mock_tv_config_entry)
with patch(
"homeassistant.components.vizio.VizioAsync.remote",
) as mock_remote:
@@ -236,9 +236,11 @@ async def test_send_command_multiple(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_invalid_skips_valid(hass: HomeAssistant) -> None:
async def test_send_command_invalid_skips_valid(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test that no commands are sent when one command in the list is invalid."""
await _setup_entry(hass, MOCK_USER_VALID_TV_CONFIG)
await setup_integration(hass, mock_tv_config_entry)
with (
patch(
"homeassistant.components.vizio.VizioAsync.remote",
@@ -258,9 +260,11 @@ async def test_send_command_invalid_skips_valid(hass: HomeAssistant) -> None:
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
async def test_send_command_delay_between_repeats(hass: HomeAssistant) -> None:
async def test_send_command_delay_between_repeats(
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
) -> None:
"""Test delay is applied between repeats but not after the last one."""
await _setup_entry(hass, MOCK_USER_VALID_TV_CONFIG)
await setup_integration(hass, mock_tv_config_entry)
with (
patch(
"homeassistant.components.vizio.VizioAsync.remote",