mirror of https://github.com/home-assistant/core
752 lines
25 KiB
Python
752 lines
25 KiB
Python
"""Tests for Vizio config flow."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import AsyncIterator
|
|
from contextlib import asynccontextmanager
|
|
from datetime import timedelta
|
|
from typing import Any
|
|
from unittest.mock import call, patch
|
|
|
|
from freezegun import freeze_time
|
|
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,
|
|
)
|
|
import voluptuous as vol
|
|
|
|
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,
|
|
)
|
|
from homeassistant.components.vizio import validate_apps
|
|
from homeassistant.components.vizio.const import (
|
|
CONF_ADDITIONAL_CONFIGS,
|
|
CONF_APPS,
|
|
CONF_VOLUME_STEP,
|
|
DEFAULT_VOLUME_STEP,
|
|
DOMAIN,
|
|
SERVICE_UPDATE_SETTING,
|
|
VIZIO_SCHEMA,
|
|
)
|
|
from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON, STATE_UNAVAILABLE
|
|
from homeassistant.core import HomeAssistant
|
|
from homeassistant.util import dt as dt_util
|
|
|
|
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_SPEAKER_APPS_FAILURE,
|
|
MOCK_SPEAKER_CONFIG,
|
|
MOCK_TV_APPS_FAILURE,
|
|
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
|
|
|
|
|
|
async def _add_config_entry_to_hass(
|
|
hass: HomeAssistant, config_entry: MockConfigEntry
|
|
) -> None:
|
|
config_entry.add_to_hass(hass)
|
|
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
|
await hass.async_block_till_done()
|
|
|
|
|
|
def _get_ha_power_state(vizio_power_state: bool | None) -> str:
|
|
"""Return HA power state given Vizio power state."""
|
|
if vizio_power_state:
|
|
return STATE_ON
|
|
|
|
if vizio_power_state is False:
|
|
return STATE_OFF
|
|
|
|
return STATE_UNAVAILABLE
|
|
|
|
|
|
def _assert_sources_and_volume(attr: dict[str, Any], vizio_device_class: str) -> None:
|
|
"""Assert source list, source, and volume level based on attr dict and 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 | None
|
|
) -> AsyncIterator[None]:
|
|
"""Context manager to setup test for Vizio devices without including app specific patches."""
|
|
with (
|
|
patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_all_settings",
|
|
return_value=all_settings,
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_setting_options",
|
|
return_value=EQ_LIST,
|
|
),
|
|
patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_power_state",
|
|
return_value=vizio_power_state,
|
|
),
|
|
):
|
|
yield
|
|
|
|
|
|
async def _test_setup_tv(hass: HomeAssistant, vizio_power_state: bool | None) -> None:
|
|
"""Test Vizio TV entity setup."""
|
|
ha_power_state = _get_ha_power_state(vizio_power_state)
|
|
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data=vol.Schema(VIZIO_SCHEMA)(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), "mute": "Off"},
|
|
vizio_power_state,
|
|
):
|
|
await _add_config_entry_to_hass(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 "sound_mode" not in attr
|
|
|
|
|
|
async def _test_setup_speaker(
|
|
hass: HomeAssistant, vizio_power_state: bool | None
|
|
) -> None:
|
|
"""Test Vizio Speaker entity setup."""
|
|
ha_power_state = _get_ha_power_state(vizio_power_state)
|
|
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data=vol.Schema(VIZIO_SCHEMA)(MOCK_SPEAKER_CONFIG),
|
|
unique_id=UNIQUE_ID,
|
|
)
|
|
|
|
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,
|
|
):
|
|
with patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
|
|
) as service_call:
|
|
await _add_config_entry_to_hass(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 not service_call.called
|
|
assert "sound_mode" in attr
|
|
|
|
|
|
@asynccontextmanager
|
|
async def _cm_for_test_setup_tv_with_apps(
|
|
hass: HomeAssistant, device_config: dict[str, Any], 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=vol.Schema(VIZIO_SCHEMA)(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,
|
|
):
|
|
with patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_current_app_config",
|
|
return_value=AppConfig(**app_config),
|
|
):
|
|
await _add_config_entry_to_hass(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.media_player.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) -> None:
|
|
"""Test Vizio Speaker entity setup when on."""
|
|
await _test_setup_speaker(hass, True)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_speaker_off(hass: HomeAssistant) -> None:
|
|
"""Test Vizio Speaker entity setup when off."""
|
|
await _test_setup_speaker(hass, False)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_speaker_unavailable(
|
|
hass: HomeAssistant,
|
|
) -> None:
|
|
"""Test Vizio Speaker entity setup when unavailable."""
|
|
await _test_setup_speaker(hass, None)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_init_tv_on(hass: HomeAssistant) -> None:
|
|
"""Test Vizio TV entity setup when on."""
|
|
await _test_setup_tv(hass, True)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_init_tv_off(hass: HomeAssistant) -> None:
|
|
"""Test Vizio TV entity setup when off."""
|
|
await _test_setup_tv(hass, False)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_init_tv_unavailable(hass: HomeAssistant) -> None:
|
|
"""Test Vizio TV entity setup when unavailable."""
|
|
await _test_setup_tv(hass, None)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_cant_connect")
|
|
async def test_setup_unavailable_speaker(hass: HomeAssistant) -> None:
|
|
"""Test speaker entity sets up as unavailable."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN, data=MOCK_SPEAKER_CONFIG, unique_id=UNIQUE_ID
|
|
)
|
|
await _add_config_entry_to_hass(hass, config_entry)
|
|
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1
|
|
assert hass.states.get("media_player.vizio").state == STATE_UNAVAILABLE
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_cant_connect")
|
|
async def test_setup_unavailable_tv(hass: HomeAssistant) -> None:
|
|
"""Test TV entity sets up as unavailable."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN, data=MOCK_USER_VALID_TV_CONFIG, unique_id=UNIQUE_ID
|
|
)
|
|
await _add_config_entry_to_hass(hass, config_entry)
|
|
assert len(hass.states.async_entity_ids(MP_DOMAIN)) == 1
|
|
assert hass.states.get("media_player.vizio").state == STATE_UNAVAILABLE
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_services(hass: HomeAssistant) -> None:
|
|
"""Test all Vizio media player entity services."""
|
|
await _test_setup_tv(hass, 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=(100 - 15),
|
|
)
|
|
await _test_service(
|
|
hass,
|
|
MP_DOMAIN,
|
|
"vol_down",
|
|
SERVICE_VOLUME_SET,
|
|
{ATTR_MEDIA_VOLUME_LEVEL: 0},
|
|
num=(15 - 0),
|
|
)
|
|
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) -> None:
|
|
"""Test when config entry update event fires."""
|
|
await _test_setup_speaker(hass, 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
|
|
)
|
|
|
|
|
|
async def _test_update_availability_switch(
|
|
hass: HomeAssistant,
|
|
initial_power_state: bool | None,
|
|
final_power_state: bool | None,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
now = dt_util.utcnow()
|
|
future_interval = timedelta(minutes=1)
|
|
|
|
# Setup device as if time is right now
|
|
with freeze_time(now):
|
|
await _test_setup_speaker(hass, initial_power_state)
|
|
|
|
# Clear captured logs so that only availability state changes are captured for
|
|
# future assertion
|
|
caplog.clear()
|
|
|
|
# Fast forward time to future twice to trigger update and assert vizio log message
|
|
for i in range(1, 3):
|
|
future = now + (future_interval * i)
|
|
with (
|
|
patch(
|
|
"homeassistant.components.vizio.media_player.VizioAsync.get_power_state",
|
|
return_value=final_power_state,
|
|
),
|
|
freeze_time(future),
|
|
):
|
|
async_fire_time_changed(hass, future)
|
|
await hass.async_block_till_done()
|
|
if final_power_state is None:
|
|
assert hass.states.get(ENTITY_ID).state == STATE_UNAVAILABLE
|
|
else:
|
|
assert hass.states.get(ENTITY_ID).state != STATE_UNAVAILABLE
|
|
|
|
# Ensure connection status messages from vizio.media_player appear exactly once
|
|
# (on availability state change)
|
|
vizio_log_list = [
|
|
log
|
|
for log in caplog.records
|
|
if log.name == "homeassistant.components.vizio.media_player"
|
|
]
|
|
assert len(vizio_log_list) == 1
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_update_unavailable_to_available(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test device becomes available after being unavailable."""
|
|
await _test_update_availability_switch(hass, None, True, caplog)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update")
|
|
async def test_update_available_to_unavailable(
|
|
hass: HomeAssistant,
|
|
caplog: pytest.LogCaptureFixture,
|
|
) -> None:
|
|
"""Test device becomes unavailable after being available."""
|
|
await _test_update_availability_switch(hass, True, None, caplog)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
|
|
async def test_setup_with_apps(
|
|
hass: HomeAssistant,
|
|
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
|
|
):
|
|
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."""
|
|
async with _cm_for_test_setup_tv_with_apps(
|
|
hass, MOCK_TV_WITH_INCLUDE_CONFIG, 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."""
|
|
async with _cm_for_test_setup_tv_with_apps(
|
|
hass, MOCK_TV_WITH_EXCLUDE_CONFIG, 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."""
|
|
async with _cm_for_test_setup_tv_with_apps(
|
|
hass,
|
|
MOCK_TV_WITH_ADDITIONAL_APPS_CONFIG,
|
|
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.media_player.VizioAsync.launch_app"
|
|
) as service_call1,
|
|
patch(
|
|
"homeassistant.components.vizio.media_player.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
|
|
|
|
|
|
def test_invalid_apps_config(hass: HomeAssistant) -> None:
|
|
"""Test that schema validation fails on certain conditions."""
|
|
with pytest.raises(vol.Invalid):
|
|
vol.Schema(vol.All(VIZIO_SCHEMA, validate_apps))(MOCK_TV_APPS_FAILURE)
|
|
|
|
with pytest.raises(vol.Invalid):
|
|
vol.Schema(vol.All(VIZIO_SCHEMA, validate_apps))(MOCK_SPEAKER_APPS_FAILURE)
|
|
|
|
|
|
@pytest.mark.usefixtures("vizio_connect", "vizio_update_with_apps")
|
|
async def test_setup_with_unknown_app_config(
|
|
hass: HomeAssistant,
|
|
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
|
|
):
|
|
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,
|
|
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())
|
|
):
|
|
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) -> None:
|
|
"""Test Vizio TV entity setup when mute property isn't returned by Vizio API."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data=vol.Schema(VIZIO_SCHEMA)(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)},
|
|
STATE_ON,
|
|
):
|
|
await _add_config_entry_to_hass(hass, 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,
|
|
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_USER_VALID_TV_CONFIG, 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) -> None:
|
|
"""Test a vizio TV with apps that is on a TV input."""
|
|
config_entry = MockConfigEntry(
|
|
domain=DOMAIN,
|
|
data=vol.Schema(VIZIO_SCHEMA)(MOCK_USER_VALID_TV_CONFIG),
|
|
unique_id=UNIQUE_ID,
|
|
)
|
|
await _add_config_entry_to_hass(hass, 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
|