mirror of
https://github.com/home-assistant/core.git
synced 2026-07-10 08:15:14 +01:00
860 lines
29 KiB
Python
860 lines
29 KiB
Python
"""Tests for Vizio config flow."""
|
|
|
|
from collections.abc import AsyncIterator, Generator
|
|
from contextlib import asynccontextmanager
|
|
from datetime import timedelta
|
|
from typing import Any
|
|
from unittest.mock import call, patch
|
|
|
|
from freezegun.api import FrozenDateTimeFactory
|
|
import pytest
|
|
from pyvizio.api.apps import AppConfig
|
|
from pyvizio.const import (
|
|
APPS,
|
|
DEVICE_CLASS_SPEAKER as VIZIO_DEVICE_CLASS_SPEAKER,
|
|
DEVICE_CLASS_TV as VIZIO_DEVICE_CLASS_TV,
|
|
INPUT_APPS,
|
|
MAX_VOLUME,
|
|
UNKNOWN_APP,
|
|
)
|
|
from syrupy.assertion import SnapshotAssertion
|
|
|
|
from homeassistant.components.media_player import (
|
|
ATTR_INPUT_SOURCE,
|
|
ATTR_INPUT_SOURCE_LIST,
|
|
ATTR_MEDIA_VOLUME_LEVEL,
|
|
ATTR_MEDIA_VOLUME_MUTED,
|
|
ATTR_SOUND_MODE,
|
|
DOMAIN as MP_DOMAIN,
|
|
SERVICE_MEDIA_NEXT_TRACK,
|
|
SERVICE_MEDIA_PAUSE,
|
|
SERVICE_MEDIA_PLAY,
|
|
SERVICE_MEDIA_PREVIOUS_TRACK,
|
|
SERVICE_SELECT_SOUND_MODE,
|
|
SERVICE_SELECT_SOURCE,
|
|
SERVICE_TURN_OFF,
|
|
SERVICE_TURN_ON,
|
|
SERVICE_VOLUME_DOWN,
|
|
SERVICE_VOLUME_MUTE,
|
|
SERVICE_VOLUME_SET,
|
|
SERVICE_VOLUME_UP,
|
|
MediaPlayerDeviceClass,
|
|
MediaPlayerEntityFeature,
|
|
)
|
|
from homeassistant.components.vizio.const import (
|
|
CONF_ADDITIONAL_CONFIGS,
|
|
CONF_APPS,
|
|
CONF_VOLUME_STEP,
|
|
DEFAULT_VOLUME_STEP,
|
|
DOMAIN,
|
|
)
|
|
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,
|
|
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,
|
|
APP_NAME_LIST,
|
|
CURRENT_APP,
|
|
CURRENT_APP_CONFIG,
|
|
CURRENT_EQ,
|
|
CURRENT_INPUT,
|
|
CUSTOM_CONFIG,
|
|
ENTITY_ID,
|
|
EQ_LIST,
|
|
INPUT_LIST,
|
|
INPUT_LIST_WITH_APPS,
|
|
MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG,
|
|
MOCK_TV_WITH_EXCLUDE_CONFIG,
|
|
MOCK_TV_WITH_INCLUDE_CONFIG,
|
|
NAME,
|
|
UNIQUE_ID,
|
|
UNKNOWN_APP_CONFIG,
|
|
VOLUME_STEP,
|
|
)
|
|
|
|
from tests.common import MockConfigEntry, async_fire_time_changed, snapshot_platform
|
|
|
|
|
|
@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:
|
|
"""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:
|
|
"""Return HA power state given Vizio power state."""
|
|
if vizio_power_state:
|
|
return STATE_ON
|
|
|
|
return STATE_OFF
|
|
|
|
|
|
def _assert_sources_and_volume(attr: dict[str, Any], vizio_device_class: str) -> None:
|
|
"""Assert source list, source, and volume level based on device class."""
|
|
assert attr[ATTR_INPUT_SOURCE_LIST] == INPUT_LIST
|
|
assert attr[ATTR_INPUT_SOURCE] == CURRENT_INPUT
|
|
assert (
|
|
attr["volume_level"]
|
|
== float(int(MAX_VOLUME[vizio_device_class] / 2))
|
|
/ MAX_VOLUME[vizio_device_class]
|
|
)
|
|
|
|
|
|
def _get_attr_and_assert_base_attr(
|
|
hass: HomeAssistant, device_class: str, power_state: str
|
|
) -> dict[str, Any]:
|
|
"""Return entity attributes after asserting name, device class, and power state."""
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
assert attr["friendly_name"] == NAME
|
|
assert attr["device_class"] == device_class
|
|
|
|
assert hass.states.get(ENTITY_ID).state == power_state
|
|
return attr
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _cm_for_test_setup_without_apps(
|
|
all_settings: dict[str, Any], vizio_power_state: bool
|
|
) -> AsyncIterator[None]:
|
|
"""Context manager to setup test for Vizio devices without app patches."""
|
|
with (
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_all_settings",
|
|
return_value=all_settings,
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_setting_options",
|
|
return_value=EQ_LIST,
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=vizio_power_state,
|
|
),
|
|
):
|
|
yield
|
|
|
|
|
|
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)
|
|
|
|
async with _cm_for_test_setup_without_apps(
|
|
{
|
|
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2),
|
|
"mute": "Off",
|
|
"eq": CURRENT_EQ,
|
|
},
|
|
vizio_power_state,
|
|
):
|
|
await setup_integration(hass, config_entry)
|
|
|
|
attr = _get_attr_and_assert_base_attr(
|
|
hass, MediaPlayerDeviceClass.TV, ha_power_state
|
|
)
|
|
if ha_power_state == STATE_ON:
|
|
_assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_TV)
|
|
assert attr[ATTR_SOUND_MODE] == CURRENT_EQ
|
|
|
|
|
|
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)
|
|
|
|
audio_settings = {
|
|
"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_SPEAKER] / 2),
|
|
"mute": "Off",
|
|
"eq": CURRENT_EQ,
|
|
}
|
|
|
|
async with _cm_for_test_setup_without_apps(
|
|
audio_settings,
|
|
vizio_power_state,
|
|
):
|
|
await setup_integration(hass, config_entry)
|
|
|
|
attr = _get_attr_and_assert_base_attr(
|
|
hass, MediaPlayerDeviceClass.SPEAKER, ha_power_state
|
|
)
|
|
if ha_power_state == STATE_ON:
|
|
_assert_sources_and_volume(attr, VIZIO_DEVICE_CLASS_SPEAKER)
|
|
assert "sound_mode" in attr
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _cm_for_test_setup_tv_with_apps(
|
|
hass: HomeAssistant, config_entry: MockConfigEntry, app_config: dict[str, Any]
|
|
) -> AsyncIterator[None]:
|
|
"""Context manager to setup test for Vizio TV with support for apps."""
|
|
async with _cm_for_test_setup_without_apps(
|
|
{"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2), "mute": "Off"},
|
|
True,
|
|
):
|
|
with patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_current_app_config",
|
|
return_value=AppConfig(**app_config),
|
|
):
|
|
await setup_integration(hass, config_entry)
|
|
|
|
attr = _get_attr_and_assert_base_attr(
|
|
hass, MediaPlayerDeviceClass.TV, STATE_ON
|
|
)
|
|
assert (
|
|
attr["volume_level"]
|
|
== float(int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2))
|
|
/ MAX_VOLUME[VIZIO_DEVICE_CLASS_TV]
|
|
)
|
|
|
|
yield
|
|
|
|
|
|
def _assert_source_list_with_apps(
|
|
list_to_test: list[str], attr: dict[str, Any]
|
|
) -> None:
|
|
"""Assert source list matches list_to_test after removing INPUT_APPS from list."""
|
|
for app_to_remove in INPUT_APPS:
|
|
if app_to_remove in list_to_test:
|
|
list_to_test.remove(app_to_remove)
|
|
|
|
assert attr[ATTR_INPUT_SOURCE_LIST] == list_to_test
|
|
|
|
|
|
async def _test_service(
|
|
hass: HomeAssistant,
|
|
domain: str,
|
|
vizio_func_name: str,
|
|
ha_service_name: str,
|
|
additional_service_data: dict[str, Any] | None,
|
|
*args,
|
|
**kwargs,
|
|
) -> None:
|
|
"""Test generic Vizio media player entity service."""
|
|
kwargs["log_api_exception"] = False
|
|
service_data = {ATTR_ENTITY_ID: ENTITY_ID}
|
|
if additional_service_data:
|
|
service_data.update(additional_service_data)
|
|
|
|
with patch(
|
|
f"homeassistant.components.vizio.VizioAsync.{vizio_func_name}"
|
|
) as service_call:
|
|
await hass.services.async_call(
|
|
domain,
|
|
ha_service_name,
|
|
service_data=service_data,
|
|
blocking=True,
|
|
)
|
|
assert service_call.called
|
|
|
|
if args or kwargs:
|
|
assert service_call.call_args == call(*args, **kwargs)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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, mock_speaker_config_entry, True)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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, mock_speaker_config_entry, False)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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, mock_tv_config_entry, True)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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, mock_tv_config_entry, False)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_cant_connect")
|
|
async def test_setup_unavailable_speaker(
|
|
hass: HomeAssistant, mock_speaker_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test speaker config entry retries setup when device is unavailable."""
|
|
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 mock_speaker_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_cant_connect")
|
|
async def test_setup_unavailable_tv(
|
|
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test TV config entry retries setup when device is unavailable."""
|
|
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 mock_tv_config_entry.state is ConfigEntryState.SETUP_RETRY
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_services(
|
|
hass: HomeAssistant, mock_tv_config_entry: MockConfigEntry
|
|
) -> None:
|
|
"""Test all Vizio media player entity services."""
|
|
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)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"mute_on",
|
|
SERVICE_VOLUME_MUTE,
|
|
{ATTR_MEDIA_VOLUME_MUTED: True},
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"mute_off",
|
|
SERVICE_VOLUME_MUTE,
|
|
{ATTR_MEDIA_VOLUME_MUTED: False},
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"set_input",
|
|
SERVICE_SELECT_SOURCE,
|
|
{ATTR_INPUT_SOURCE: "USB"},
|
|
"USB",
|
|
)
|
|
await _test_service(
|
|
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None, num=DEFAULT_VOLUME_STEP
|
|
)
|
|
await _test_service(
|
|
hass, MP_DOMAIN, "vol_down", SERVICE_VOLUME_DOWN, None, num=DEFAULT_VOLUME_STEP
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"vol_up",
|
|
SERVICE_VOLUME_SET,
|
|
{ATTR_MEDIA_VOLUME_LEVEL: 1},
|
|
num=50, # From 50% to 100% = 50 steps (TV max volume 100, starting at 50)
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"vol_down",
|
|
SERVICE_VOLUME_SET,
|
|
{ATTR_MEDIA_VOLUME_LEVEL: 0},
|
|
num=100, # From 100% (after previous vol_up) to 0% = 100 steps
|
|
)
|
|
await _test_service(hass, MP_DOMAIN, "ch_up", SERVICE_MEDIA_NEXT_TRACK, None)
|
|
await _test_service(hass, MP_DOMAIN, "ch_down", SERVICE_MEDIA_PREVIOUS_TRACK, None)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"set_setting",
|
|
SERVICE_SELECT_SOUND_MODE,
|
|
{ATTR_SOUND_MODE: "Music"},
|
|
"audio",
|
|
"eq",
|
|
"Music",
|
|
)
|
|
# Test that the update_setting service does config
|
|
# validation/transformation correctly
|
|
await _test_service(
|
|
hass,
|
|
DOMAIN,
|
|
"set_setting",
|
|
SERVICE_UPDATE_SETTING,
|
|
{"setting_type": "Audio", "setting_name": "AV Delay", "new_value": "0"},
|
|
"audio",
|
|
"av_delay",
|
|
0,
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
DOMAIN,
|
|
"set_setting",
|
|
SERVICE_UPDATE_SETTING,
|
|
{"setting_type": "Audio", "setting_name": "EQ", "new_value": "Music"},
|
|
"audio",
|
|
"eq",
|
|
"Music",
|
|
)
|
|
await _test_service(hass, MP_DOMAIN, "play", SERVICE_MEDIA_PLAY, None)
|
|
await _test_service(hass, MP_DOMAIN, "pause", SERVICE_MEDIA_PAUSE, None)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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, mock_speaker_config_entry, True)
|
|
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
|
assert config_entry.options
|
|
new_options = config_entry.options.copy()
|
|
updated_options = {CONF_VOLUME_STEP: VOLUME_STEP}
|
|
new_options.update(updated_options)
|
|
hass.config_entries.async_update_entry(
|
|
entry=config_entry,
|
|
options=new_options,
|
|
)
|
|
assert config_entry.options == updated_options
|
|
await hass.async_block_till_done()
|
|
await _test_service(
|
|
hass, MP_DOMAIN, "vol_up", SERVICE_VOLUME_UP, None, num=VOLUME_STEP
|
|
)
|
|
|
|
|
|
@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, mock_speaker_config_entry, True)
|
|
|
|
# Simulate device becoming unreachable
|
|
with patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=None,
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get(ENTITY_ID).state == STATE_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, mock_speaker_config_entry, True)
|
|
|
|
# First, make device unavailable
|
|
with patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=None,
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
|
|
|
|
# Then, make device available again
|
|
with patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=True,
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
assert hass.states.get(ENTITY_ID).state != STATE_UNAVAILABLE
|
|
|
|
|
|
@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_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)
|
|
assert CURRENT_APP in attr[ATTR_INPUT_SOURCE_LIST]
|
|
assert attr[ATTR_INPUT_SOURCE] == CURRENT_APP
|
|
assert attr["app_name"] == CURRENT_APP
|
|
assert "app_id" not in attr
|
|
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"launch_app",
|
|
SERVICE_SELECT_SOURCE,
|
|
{ATTR_INPUT_SOURCE: CURRENT_APP},
|
|
CURRENT_APP,
|
|
APP_LIST,
|
|
)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
|
|
async def test_setup_with_apps_include(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test device setup with apps and apps["include"] in 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]
|
|
assert attr[ATTR_INPUT_SOURCE] == CURRENT_APP
|
|
assert attr["app_name"] == CURRENT_APP
|
|
assert "app_id" not in attr
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
|
|
async def test_setup_with_apps_exclude(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test device setup with apps and apps["exclude"] in 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]
|
|
assert attr[ATTR_INPUT_SOURCE] == CURRENT_APP
|
|
assert attr["app_name"] == CURRENT_APP
|
|
assert "app_id" not in attr
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
|
|
async def test_setup_with_apps_additional_apps_config(
|
|
hass: HomeAssistant,
|
|
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,
|
|
config_entry,
|
|
ADDITIONAL_APP_CONFIG["config"],
|
|
):
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
assert attr[ATTR_INPUT_SOURCE_LIST].count(CURRENT_APP) == 1
|
|
_assert_source_list_with_apps(
|
|
list(
|
|
INPUT_LIST_WITH_APPS
|
|
+ APP_NAME_LIST
|
|
+ [
|
|
app["name"]
|
|
for app in MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG[CONF_APPS][
|
|
CONF_ADDITIONAL_CONFIGS
|
|
]
|
|
if app["name"] not in APP_NAME_LIST
|
|
]
|
|
),
|
|
attr,
|
|
)
|
|
assert ADDITIONAL_APP_CONFIG["name"] in attr[ATTR_INPUT_SOURCE_LIST]
|
|
assert attr[ATTR_INPUT_SOURCE] == ADDITIONAL_APP_CONFIG["name"]
|
|
assert attr["app_name"] == ADDITIONAL_APP_CONFIG["name"]
|
|
assert "app_id" not in attr
|
|
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"launch_app",
|
|
SERVICE_SELECT_SOURCE,
|
|
{ATTR_INPUT_SOURCE: "Netflix"},
|
|
"Netflix",
|
|
APP_LIST,
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"launch_app_config",
|
|
SERVICE_SELECT_SOURCE,
|
|
{ATTR_INPUT_SOURCE: CURRENT_APP},
|
|
**CUSTOM_CONFIG,
|
|
)
|
|
|
|
# Test that invalid app does nothing
|
|
with (
|
|
patch("homeassistant.components.vizio.VizioAsync.launch_app") as service_call1,
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.launch_app_config"
|
|
) as service_call2,
|
|
):
|
|
await hass.services.async_call(
|
|
MP_DOMAIN,
|
|
SERVICE_SELECT_SOURCE,
|
|
service_data={ATTR_ENTITY_ID: ENTITY_ID, ATTR_INPUT_SOURCE: "_"},
|
|
blocking=True,
|
|
)
|
|
assert not service_call1.called
|
|
assert not service_call2.called
|
|
|
|
|
|
@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_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)
|
|
assert attr[ATTR_INPUT_SOURCE] == UNKNOWN_APP
|
|
assert attr["app_name"] == UNKNOWN_APP
|
|
assert attr["app_id"] == 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_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)
|
|
assert attr[ATTR_INPUT_SOURCE] == "CAST"
|
|
assert "app_id" not in attr
|
|
assert "app_name" not in attr
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
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."""
|
|
async with _cm_for_test_setup_without_apps(
|
|
{"volume": int(MAX_VOLUME[VIZIO_DEVICE_CLASS_TV] / 2)},
|
|
True,
|
|
):
|
|
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)
|
|
assert "sound_mode" not in attr
|
|
assert "is_volume_muted" not in attr
|
|
|
|
|
|
@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."""
|
|
with patch(
|
|
"homeassistant.components.vizio.coordinator.gen_apps_list_from_url",
|
|
return_value=None,
|
|
):
|
|
async with _cm_for_test_setup_tv_with_apps(
|
|
hass, mock_tv_config_entry, vars(AppConfig())
|
|
):
|
|
# Check source list, remove TV inputs, and verify that the integration is
|
|
# using the default APPS list
|
|
sources = hass.states.get(ENTITY_ID).attributes[ATTR_INPUT_SOURCE_LIST]
|
|
apps = list(set(sources) - set(INPUT_LIST))
|
|
assert len(apps) == len(APPS)
|
|
|
|
with patch(
|
|
"homeassistant.components.vizio.coordinator.gen_apps_list_from_url",
|
|
return_value=APP_LIST,
|
|
):
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(days=2))
|
|
await hass.async_block_till_done()
|
|
async_fire_time_changed(hass, dt_util.now() + timedelta(days=2))
|
|
await hass.async_block_till_done()
|
|
# Check source list, remove TV inputs, and verify that
|
|
# the integration is
|
|
# now using the APP_LIST list
|
|
sources = hass.states.get(ENTITY_ID).attributes[ATTR_INPUT_SOURCE_LIST]
|
|
apps = list(set(sources) - set(INPUT_LIST))
|
|
assert len(apps) == len(APP_LIST)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps_on_input")
|
|
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."""
|
|
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
|
|
|
|
|
|
@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, mock_speaker_config_entry, True)
|
|
attr = _get_attr_and_assert_base_attr(
|
|
hass, MediaPlayerDeviceClass.SPEAKER, STATE_ON
|
|
)
|
|
assert attr[ATTR_MEDIA_VOLUME_LEVEL] is not None
|
|
assert ATTR_SOUND_MODE in attr
|
|
|
|
# Device turns off
|
|
with patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=False,
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.states.get(ENTITY_ID).state == STATE_OFF
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
assert attr.get(ATTR_MEDIA_VOLUME_LEVEL) is None
|
|
assert attr.get(ATTR_MEDIA_VOLUME_MUTED) is None
|
|
assert attr.get(ATTR_SOUND_MODE) is None
|
|
|
|
|
|
@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, mock_speaker_config_entry, False)
|
|
assert hass.states.get(ENTITY_ID).state == STATE_OFF
|
|
|
|
# Device turns on
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
assert hass.states.get(ENTITY_ID).state == STATE_ON
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
assert attr[ATTR_MEDIA_VOLUME_LEVEL] is not None
|
|
assert ATTR_SOUND_MODE in attr
|
|
|
|
|
|
@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, mock_speaker_config_entry, True)
|
|
attr = _get_attr_and_assert_base_attr(
|
|
hass, MediaPlayerDeviceClass.SPEAKER, STATE_ON
|
|
)
|
|
assert ATTR_SOUND_MODE in attr
|
|
state = hass.states.get(ENTITY_ID)
|
|
assert (
|
|
state.attributes["supported_features"]
|
|
& MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
|
)
|
|
|
|
# Update with audio settings that have no sound mode
|
|
with (
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_all_settings",
|
|
return_value={"volume": 50, "mute": "Off"},
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=True,
|
|
),
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
state = hass.states.get(ENTITY_ID)
|
|
assert state.state == STATE_ON
|
|
assert not (
|
|
state.attributes["supported_features"]
|
|
& MediaPlayerEntityFeature.SELECT_SOUND_MODE
|
|
)
|
|
|
|
|
|
@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, mock_speaker_config_entry, True)
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
assert attr["sound_mode_list"] == EQ_LIST
|
|
|
|
# Update with different sound mode options — cached list should persist
|
|
with (
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_setting_options",
|
|
return_value=["Different1", "Different2"],
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.VizioAsync.get_power_state",
|
|
return_value=True,
|
|
),
|
|
):
|
|
freezer.tick(timedelta(minutes=1))
|
|
async_fire_time_changed(hass)
|
|
await hass.async_block_till_done()
|
|
|
|
attr = hass.states.get(ENTITY_ID).attributes
|
|
# Sound mode list should still be the original cached list
|
|
assert attr["sound_mode_list"] == EQ_LIST
|