diff --git a/tests/components/vizio/conftest.py b/tests/components/vizio/conftest.py index aec5b5e906f..bfb260a1e78 100644 --- a/tests/components/vizio/conftest.py +++ b/tests/components/vizio/conftest.py @@ -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.""" diff --git a/tests/components/vizio/snapshots/test_media_player.ambr b/tests/components/vizio/snapshots/test_media_player.ambr new file mode 100644 index 00000000000..1e7f3eb6511 --- /dev/null +++ b/tests/components/vizio/snapshots/test_media_player.ambr @@ -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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'media_player', + 'entity_category': None, + 'entity_id': 'media_player.vizio', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'vizio', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + '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': , + 'sound_mode': 'Music', + 'sound_mode_list': list([ + 'Music', + 'Movie', + ]), + 'source': 'HDMI', + 'source_list': list([ + 'HDMI', + 'USB', + 'Bluetooth', + 'AUX', + ]), + 'supported_features': , + 'volume_level': 0.4838709677419355, + }), + 'context': , + 'entity_id': 'media_player.vizio', + 'last_changed': , + 'last_reported': , + 'last_updated': , + '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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'media_player', + 'entity_category': None, + 'entity_id': 'media_player.vizio', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + 'labels': set({ + }), + 'name': None, + 'object_id_base': None, + 'options': dict({ + }), + 'original_device_class': , + 'original_icon': None, + 'original_name': None, + 'platform': 'vizio', + 'previous_unique_id': None, + 'suggested_object_id': None, + 'supported_features': , + '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': , + 'sound_mode': 'Music', + 'sound_mode_list': list([ + 'Music', + 'Movie', + ]), + 'source': 'HDMI', + 'source_list': list([ + 'HDMI', + 'USB', + 'Bluetooth', + 'AUX', + ]), + 'supported_features': , + 'volume_level': 0.15, + }), + 'context': , + 'entity_id': 'media_player.vizio', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/vizio/snapshots/test_remote.ambr b/tests/components/vizio/snapshots/test_remote.ambr new file mode 100644 index 00000000000..d1e008473f8 --- /dev/null +++ b/tests/components/vizio/snapshots/test_remote.ambr @@ -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': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'remote', + 'entity_category': None, + 'entity_id': 'remote.vizio', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + }), + 'context': , + 'entity_id': 'remote.vizio', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- +# name: test_remote_entity_setup[tv][remote.vizio-entry] + EntityRegistryEntrySnapshot({ + 'aliases': list([ + None, + ]), + 'area_id': None, + 'capabilities': None, + 'config_entry_id': , + 'config_subentry_id': , + 'device_class': None, + 'device_id': , + 'disabled_by': None, + 'domain': 'remote', + 'entity_category': None, + 'entity_id': 'remote.vizio', + 'has_entity_name': True, + 'hidden_by': None, + 'icon': None, + 'id': , + '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': , + }), + 'context': , + 'entity_id': 'remote.vizio', + 'last_changed': , + 'last_reported': , + 'last_updated': , + 'state': 'on', + }) +# --- diff --git a/tests/components/vizio/test_init.py b/tests/components/vizio/test_init.py index cea0c06aef6..68c809f6d95 100644 --- a/tests/components/vizio/test_init.py +++ b/tests/components/vizio/test_init.py @@ -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 diff --git a/tests/components/vizio/test_media_player.py b/tests/components/vizio/test_media_player.py index 99c19a6354f..1c893036f52 100644 --- a/tests/components/vizio/test_media_player.py +++ b/tests/components/vizio/test_media_player.py @@ -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 diff --git a/tests/components/vizio/test_remote.py b/tests/components/vizio/test_remote.py index bf7538a10f5..56c5900d186 100644 --- a/tests/components/vizio/test_remote.py +++ b/tests/components/vizio/test_remote.py @@ -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",