From ea456893f94c7dc88b0cc28f92dadf240fbb1fe7 Mon Sep 17 00:00:00 2001 From: ollo69 <60491700+ollo69@users.noreply.github.com> Date: Tue, 3 May 2022 00:18:38 +0200 Subject: [PATCH] Review AndroidTV tests for media player entity (#71168) --- .coveragerc | 1 - tests/components/androidtv/patchers.py | 84 +- .../components/androidtv/test_config_flow.py | 12 +- .../components/androidtv/test_media_player.py | 743 ++++++++---------- 4 files changed, 372 insertions(+), 468 deletions(-) diff --git a/.coveragerc b/.coveragerc index b7433ecf58a..3464b0df1be 100644 --- a/.coveragerc +++ b/.coveragerc @@ -58,7 +58,6 @@ omit = homeassistant/components/amcrest/* homeassistant/components/ampio/* homeassistant/components/android_ip_webcam/* - homeassistant/components/androidtv/__init__.py homeassistant/components/androidtv/diagnostics.py homeassistant/components/anel_pwrctrl/switch.py homeassistant/components/anthemav/media_player.py diff --git a/tests/components/androidtv/patchers.py b/tests/components/androidtv/patchers.py index 7cc14bbd7b5..31e9a9c82c3 100644 --- a/tests/components/androidtv/patchers.py +++ b/tests/components/androidtv/patchers.py @@ -1,8 +1,15 @@ """Define patches used for androidtv tests.""" -from unittest.mock import mock_open, patch +from unittest.mock import patch from androidtv.constants import CMD_DEVICE_PROPERTIES, CMD_MAC_ETH0, CMD_MAC_WLAN0 +from homeassistant.components.androidtv.const import ( + DEFAULT_ADB_SERVER_PORT, + DEVICE_ANDROIDTV, + DEVICE_FIRETV, +) + +ADB_SERVER_HOST = "127.0.0.1" KEY_PYTHON = "python" KEY_SERVER = "server" @@ -36,7 +43,7 @@ class AdbDeviceTcpAsyncFake: class ClientAsyncFakeSuccess: """A fake of the `ClientAsync` class when the connection and shell commands succeed.""" - def __init__(self, host="127.0.0.1", port=5037): + def __init__(self, host=ADB_SERVER_HOST, port=DEFAULT_ADB_SERVER_PORT): """Initialize a `ClientAsyncFakeSuccess` instance.""" self._devices = [] @@ -50,7 +57,7 @@ class ClientAsyncFakeSuccess: class ClientAsyncFakeFail: """A fake of the `ClientAsync` class when the connection and shell commands fail.""" - def __init__(self, host="127.0.0.1", port=5037): + def __init__(self, host=ADB_SERVER_HOST, port=DEFAULT_ADB_SERVER_PORT): """Initialize a `ClientAsyncFakeFail` instance.""" self._devices = [] @@ -143,17 +150,34 @@ def patch_shell(response=None, error=False, mac_eth=False): } -PATCH_ADB_DEVICE_TCP = patch( - "androidtv.adb_manager.adb_manager_async.AdbDeviceTcpAsync", AdbDeviceTcpAsyncFake -) -PATCH_ANDROIDTV_OPEN = patch( - "homeassistant.components.androidtv.media_player.open", mock_open() -) -PATCH_KEYGEN = patch("homeassistant.components.androidtv.keygen") -PATCH_SIGNER = patch( - "homeassistant.components.androidtv.ADBPythonSync.load_adbkey", - return_value="signer for testing", -) +def patch_androidtv_update( + state, + current_app, + running_apps, + device, + is_volume_muted, + volume_level, + hdmi_input, +): + """Patch the `AndroidTV.update()` method.""" + return { + DEVICE_ANDROIDTV: patch( + "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", + return_value=( + state, + current_app, + running_apps, + device, + is_volume_muted, + volume_level, + hdmi_input, + ), + ), + DEVICE_FIRETV: patch( + "androidtv.firetv.firetv_async.FireTVAsync.update", + return_value=(state, current_app, running_apps, hdmi_input), + ), + } def isfile(filepath): @@ -161,32 +185,12 @@ def isfile(filepath): return filepath.endswith("adbkey") -def patch_firetv_update(state, current_app, running_apps, hdmi_input): - """Patch the `FireTV.update()` method.""" - return patch( - "androidtv.firetv.firetv_async.FireTVAsync.update", - return_value=(state, current_app, running_apps, hdmi_input), - ) - - -def patch_androidtv_update( - state, current_app, running_apps, device, is_volume_muted, volume_level, hdmi_input -): - """Patch the `AndroidTV.update()` method.""" - return patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.update", - return_value=( - state, - current_app, - running_apps, - device, - is_volume_muted, - volume_level, - hdmi_input, - ), - ) - - +PATCH_SETUP_ENTRY = patch( + "homeassistant.components.androidtv.async_setup_entry", + return_value=True, +) +PATCH_ACCESS = patch("homeassistant.components.androidtv.os.access", return_value=True) +PATCH_ISFILE = patch("homeassistant.components.androidtv.os.path.isfile", isfile) PATCH_LAUNCH_APP = patch("androidtv.basetv.basetv_async.BaseTVAsync.launch_app") PATCH_STOP_APP = patch("androidtv.basetv.basetv_async.BaseTVAsync.stop_app") diff --git a/tests/components/androidtv/test_config_flow.py b/tests/components/androidtv/test_config_flow.py index aca308fd0e5..d5301f7ada3 100644 --- a/tests/components/androidtv/test_config_flow.py +++ b/tests/components/androidtv/test_config_flow.py @@ -36,7 +36,7 @@ from homeassistant.components.androidtv.const import ( from homeassistant.config_entries import SOURCE_USER from homeassistant.const import CONF_DEVICE_CLASS, CONF_HOST, CONF_PORT -from .patchers import isfile +from .patchers import PATCH_ACCESS, PATCH_ISFILE, PATCH_SETUP_ENTRY from tests.common import MockConfigEntry @@ -66,16 +66,6 @@ CONFIG_ADB_SERVER = { CONNECT_METHOD = ( "homeassistant.components.androidtv.config_flow.async_connect_androidtv" ) -PATCH_ACCESS = patch( - "homeassistant.components.androidtv.config_flow.os.access", return_value=True -) -PATCH_ISFILE = patch( - "homeassistant.components.androidtv.config_flow.os.path.isfile", isfile -) -PATCH_SETUP_ENTRY = patch( - "homeassistant.components.androidtv.async_setup_entry", - return_value=True, -) class MockConfigDevice: diff --git a/tests/components/androidtv/test_media_player.py b/tests/components/androidtv/test_media_player.py index aa27ee8f564..73f8de55cc9 100644 --- a/tests/components/androidtv/test_media_player.py +++ b/tests/components/androidtv/test_media_player.py @@ -1,8 +1,7 @@ """The tests for the androidtv platform.""" import base64 -import copy import logging -from unittest.mock import patch +from unittest.mock import Mock, patch from androidtv.constants import APPS as ANDROIDTV_APPS, KEYS from androidtv.exceptions import LockNotAcquiredException @@ -14,6 +13,8 @@ from homeassistant.components.androidtv.const import ( CONF_ADBKEY, CONF_APPS, CONF_EXCLUDE_UNNAMED_APPS, + CONF_SCREENCAP, + CONF_STATE_DETECTION_RULES, CONF_TURN_OFF_COMMAND, CONF_TURN_ON_COMMAND, DEFAULT_ADB_SERVER_PORT, @@ -52,6 +53,7 @@ from homeassistant.components.media_player import ( SERVICE_VOLUME_UP, ) from homeassistant.components.websocket_api.const import TYPE_RESULT +from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ( ATTR_COMMAND, ATTR_ENTITY_ID, @@ -72,16 +74,15 @@ from . import patchers from tests.common import MockConfigEntry -CONF_OPTIONS = "options" HOST = "127.0.0.1" ADB_PATCH_KEY = "patch_key" TEST_ENTITY_NAME = "entity_name" -PATCH_ACCESS = patch("homeassistant.components.androidtv.os.access", return_value=True) -PATCH_ISFILE = patch( - "homeassistant.components.androidtv.os.path.isfile", patchers.isfile -) +MSG_RECONNECT = { + patchers.KEY_PYTHON: f"ADB connection to {HOST}:{DEFAULT_PORT} successfully established", + patchers.KEY_SERVER: f"ADB connection to {HOST}:{DEFAULT_PORT} via ADB server {patchers.ADB_SERVER_HOST}:{DEFAULT_ADB_SERVER_PORT} successfully established", +} SHELL_RESPONSE_OFF = "" SHELL_RESPONSE_STANDBY = "1" @@ -107,6 +108,16 @@ CONFIG_ANDROIDTV_PYTHON_ADB_YAML = { }, } +# Android TV device with Python ADB implementation with custom adbkey +CONFIG_ANDROIDTV_PYTHON_ADB_KEY = { + ADB_PATCH_KEY: patchers.KEY_PYTHON, + TEST_ENTITY_NAME: CONFIG_ANDROIDTV_PYTHON_ADB[TEST_ENTITY_NAME], + DOMAIN: { + **CONFIG_ANDROIDTV_PYTHON_ADB[DOMAIN], + CONF_ADBKEY: "user_provided_adbkey", + }, +} + # Android TV device with ADB server CONFIG_ANDROIDTV_ADB_SERVER = { ADB_PATCH_KEY: patchers.KEY_SERVER, @@ -115,7 +126,7 @@ CONFIG_ANDROIDTV_ADB_SERVER = { CONF_HOST: HOST, CONF_PORT: DEFAULT_PORT, CONF_DEVICE_CLASS: DEVICE_ANDROIDTV, - CONF_ADB_SERVER_IP: HOST, + CONF_ADB_SERVER_IP: patchers.ADB_SERVER_HOST, CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT, }, } @@ -139,11 +150,44 @@ CONFIG_FIRETV_ADB_SERVER = { CONF_HOST: HOST, CONF_PORT: DEFAULT_PORT, CONF_DEVICE_CLASS: DEVICE_FIRETV, - CONF_ADB_SERVER_IP: HOST, + CONF_ADB_SERVER_IP: patchers.ADB_SERVER_HOST, CONF_ADB_SERVER_PORT: DEFAULT_ADB_SERVER_PORT, }, } +CONFIG_ANDROIDTV_DEFAULT = CONFIG_ANDROIDTV_PYTHON_ADB +CONFIG_FIRETV_DEFAULT = CONFIG_FIRETV_PYTHON_ADB + + +@pytest.fixture(autouse=True) +def adb_device_tcp_fixture() -> None: + """Patch ADB Device TCP.""" + with patch( + "androidtv.adb_manager.adb_manager_async.AdbDeviceTcpAsync", + patchers.AdbDeviceTcpAsyncFake, + ): + yield + + +@pytest.fixture(autouse=True) +def load_adbkey_fixture() -> None: + """Patch load_adbkey.""" + with patch( + "homeassistant.components.androidtv.ADBPythonSync.load_adbkey", + return_value="signer for testing", + ): + yield + + +@pytest.fixture(autouse=True) +def keygen_fixture() -> None: + """Patch keygen.""" + with patch( + "homeassistant.components.androidtv.keygen", + return_value=Mock(), + ): + yield + def _setup(config): """Perform common setup tasks for the tests.""" @@ -153,7 +197,6 @@ def _setup(config): domain=DOMAIN, data=config[DOMAIN], unique_id="a1:b1:c1:d1:e1:f1", - options=config[DOMAIN].get(CONF_OPTIONS), ) return patch_key, entity_id, config_entry @@ -180,11 +223,9 @@ async def test_reconnect(hass, caplog, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -198,7 +239,7 @@ async def test_reconnect(hass, caplog, config): with patchers.patch_connect(False)[patch_key], patchers.patch_shell(error=True)[ patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + ]: for _ in range(5): await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) @@ -212,23 +253,13 @@ async def test_reconnect(hass, caplog, config): caplog.set_level(logging.DEBUG) with patchers.patch_connect(True)[patch_key], patchers.patch_shell( SHELL_RESPONSE_STANDBY - )[patch_key], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + )[patch_key]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_STANDBY - - if patch_key == "python": - assert ( - "ADB connection to 127.0.0.1:5555 successfully established" - in caplog.record_tuples[2] - ) - else: - assert ( - "ADB connection to 127.0.0.1:5555 via ADB server 127.0.0.1:5037 successfully established" - in caplog.record_tuples[2] - ) + assert MSG_RECONNECT[patch_key] in caplog.record_tuples[2] @pytest.mark.parametrize( @@ -248,11 +279,9 @@ async def test_adb_shell_returns_none(hass, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -263,7 +292,7 @@ async def test_adb_shell_returns_none(hass, config): with patchers.patch_shell(None)[patch_key], patchers.patch_shell(error=True)[ patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + ]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -272,16 +301,12 @@ async def test_adb_shell_returns_none(hass, config): async def test_setup_with_adbkey(hass): """Test that setup succeeds when using an ADB key.""" - config = copy.deepcopy(CONFIG_ANDROIDTV_PYTHON_ADB) - config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey") - patch_key, entity_id, config_entry = _setup(config) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_PYTHON_ADB_KEY) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, PATCH_ISFILE, PATCH_ACCESS: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key], patchers.PATCH_ISFILE: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -292,30 +317,26 @@ async def test_setup_with_adbkey(hass): @pytest.mark.parametrize( - "config0", + "config", [ - CONFIG_ANDROIDTV_ADB_SERVER, - CONFIG_FIRETV_ADB_SERVER, + CONFIG_ANDROIDTV_DEFAULT, + CONFIG_FIRETV_DEFAULT, ], ) -async def test_sources(hass, config0): +async def test_sources(hass, config): """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.app.test4": SHELL_RESPONSE_OFF, - } - } - ) + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + "com.app.test4": SHELL_RESPONSE_OFF, + } patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -324,25 +345,17 @@ async def test_sources(hass, config0): assert state is not None assert state.state == STATE_OFF - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", - "com.app.test1", - ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], - "hdmi", - False, - 1, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test1", - ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], - "HW5", - ) + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test1", + ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], + "hdmi", + False, + 1, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -350,25 +363,17 @@ async def test_sources(hass, config0): assert state.attributes["source"] == "TEST 1" assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", - "com.app.test2", - ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], - "hdmi", - True, - 0, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test2", - ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], - "HW5", - ) + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test2", + ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], + "hdmi", + True, + 0, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -377,24 +382,29 @@ async def test_sources(hass, config0): assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] -async def _test_exclude_sources(hass, config0, expected_sources): +@pytest.mark.parametrize( + ["config", "expected_sources"], + [ + (CONFIG_ANDROIDTV_DEFAULT, ["TEST 1"]), + (CONFIG_FIRETV_DEFAULT, ["TEST 1"]), + ], +) +async def test_exclude_sources(hass, config, expected_sources): """Test that sources (i.e., apps) are handled correctly when the `exclude_unnamed_apps` config parameter is provided.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.app.test4": SHELL_RESPONSE_OFF, - } - } - ) + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + "com.app.test4": SHELL_RESPONSE_OFF, + } patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, options={CONF_EXCLUDE_UNNAMED_APPS: True, CONF_APPS: conf_apps} + ) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -403,37 +413,23 @@ async def _test_exclude_sources(hass, config0, expected_sources): assert state is not None assert state.state == STATE_OFF - if config[DOMAIN].get(CONF_DEVICE_CLASS) != DEVICE_FIRETV: - patch_update = patchers.patch_androidtv_update( - "playing", + patch_update = patchers.patch_androidtv_update( + "playing", + "com.app.test1", + [ "com.app.test1", - [ - "com.app.test1", - "com.app.test2", - "com.app.test3", - "com.app.test4", - "com.app.test5", - ], - "hdmi", - False, - 1, - "HW5", - ) - else: - patch_update = patchers.patch_firetv_update( - "playing", - "com.app.test1", - [ - "com.app.test1", - "com.app.test2", - "com.app.test3", - "com.app.test4", - "com.app.test5", - ], - "HW5", - ) + "com.app.test2", + "com.app.test3", + "com.app.test4", + "com.app.test5", + ], + "hdmi", + False, + 1, + "HW5", + ) - with patch_update: + with patch_update[config[DOMAIN][CONF_DEVICE_CLASS]]: await async_update_entity(hass, entity_id) state = hass.states.get(entity_id) assert state is not None @@ -441,41 +437,18 @@ async def _test_exclude_sources(hass, config0, expected_sources): assert state.attributes["source"] == "TEST 1" assert sorted(state.attributes["source_list"]) == expected_sources - return True - -async def test_androidtv_exclude_sources(hass): - """Test that sources (i.e., apps) are handled correctly for Android TV devices when the `exclude_unnamed_apps` config parameter is provided as true.""" - config = copy.deepcopy(CONFIG_ANDROIDTV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = {CONF_EXCLUDE_UNNAMED_APPS: True} - assert await _test_exclude_sources(hass, config, ["TEST 1"]) - - -async def test_firetv_exclude_sources(hass): - """Test that sources (i.e., apps) are handled correctly for Fire TV devices when the `exclude_unnamed_apps` config parameter is provided as true.""" - config = copy.deepcopy(CONFIG_FIRETV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = {CONF_EXCLUDE_UNNAMED_APPS: True} - assert await _test_exclude_sources(hass, config, ["TEST 1"]) - - -async def _test_select_source(hass, config0, source, expected_arg, method_patch): +async def _test_select_source( + hass, config, conf_apps, source, expected_arg, method_patch +): """Test that the methods for launching and stopping apps are called correctly when selecting a source.""" - config = copy.deepcopy(config0) - config[DOMAIN].setdefault(CONF_OPTIONS, {}).update( - { - CONF_APPS: { - "com.app.test1": "TEST 1", - "com.app.test3": None, - "com.youtube.test": "YouTube", - } - } - ) patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry(config_entry, options={CONF_APPS: conf_apps}) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -484,213 +457,87 @@ async def _test_select_source(hass, config0, source, expected_arg, method_patch) assert state is not None assert state.state == STATE_OFF - with method_patch as method_patch_: + with method_patch as method_patch_used: await hass.services.async_call( MP_DOMAIN, SERVICE_SELECT_SOURCE, {ATTR_ENTITY_ID: entity_id, ATTR_INPUT_SOURCE: source}, blocking=True, ) - method_patch_.assert_called_with(expected_arg) - - return True + method_patch_used.assert_called_with(expected_arg) -async def test_androidtv_select_source_launch_app_id(hass): - """Test that an app can be launched using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_name(hass): - """Test that an app can be launched using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "TEST 1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_id_no_name(hass): - """Test that an app can be launched using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test2", - "com.app.test2", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_androidtv_select_source_launch_app_hidden(hass): - """Test that an app can be launched using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "com.app.test3", - "com.app.test3", - patchers.PATCH_LAUNCH_APP, +@pytest.mark.parametrize( + ["source", "expected_arg", "method_patch"], + [ + ("com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP), + ("com.app.test3", "com.app.test3", patchers.PATCH_LAUNCH_APP), + ("!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP), + ("!com.app.test3", "com.app.test3", patchers.PATCH_STOP_APP), + ], +) +async def test_select_source_androidtv(hass, source, expected_arg, method_patch): + """Test that an app can be launched for AndroidTV.""" + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + } + await _test_select_source( + hass, CONFIG_ANDROIDTV_DEFAULT, conf_apps, source, expected_arg, method_patch ) async def test_androidtv_select_source_overridden_app_name(hass): """Test that when an app name is overridden via the `apps` configuration parameter, the app is launched correctly.""" # Evidence that the default YouTube app ID will be overridden + conf_apps = { + "com.youtube.test": "YouTube", + } assert "YouTube" in ANDROIDTV_APPS.values() assert "com.youtube.test" not in ANDROIDTV_APPS - assert await _test_select_source( + await _test_select_source( hass, - CONFIG_ANDROIDTV_ADB_SERVER, + CONFIG_ANDROIDTV_PYTHON_ADB, + conf_apps, "YouTube", "com.youtube.test", patchers.PATCH_LAUNCH_APP, ) -async def test_androidtv_select_source_stop_app_id(hass): - """Test that an app can be stopped using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_name(hass): - """Test that an app can be stopped using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!TEST 1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_id_no_name(hass): - """Test that an app can be stopped using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test2", - "com.app.test2", - patchers.PATCH_STOP_APP, - ) - - -async def test_androidtv_select_source_stop_app_hidden(hass): - """Test that an app can be stopped using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_ANDROIDTV_ADB_SERVER, - "!com.app.test3", - "com.app.test3", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_launch_app_id(hass): - """Test that an app can be launched using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_name(hass): - """Test that an app can be launched using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "TEST 1", - "com.app.test1", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_id_no_name(hass): - """Test that an app can be launched using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test2", - "com.app.test2", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_launch_app_hidden(hass): - """Test that an app can be launched using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "com.app.test3", - "com.app.test3", - patchers.PATCH_LAUNCH_APP, - ) - - -async def test_firetv_select_source_stop_app_id(hass): - """Test that an app can be stopped using its app ID.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_app_name(hass): - """Test that an app can be stopped using its friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!TEST 1", - "com.app.test1", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_app_id_no_name(hass): - """Test that an app can be stopped using its app ID when it has no friendly name.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test2", - "com.app.test2", - patchers.PATCH_STOP_APP, - ) - - -async def test_firetv_select_source_stop_hidden(hass): - """Test that an app can be stopped using its app ID when it is hidden from the sources list.""" - assert await _test_select_source( - hass, - CONFIG_FIRETV_ADB_SERVER, - "!com.app.test3", - "com.app.test3", - patchers.PATCH_STOP_APP, +@pytest.mark.parametrize( + ["source", "expected_arg", "method_patch"], + [ + ("com.app.test1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("TEST 1", "com.app.test1", patchers.PATCH_LAUNCH_APP), + ("com.app.test2", "com.app.test2", patchers.PATCH_LAUNCH_APP), + ("com.app.test3", "com.app.test3", patchers.PATCH_LAUNCH_APP), + ("!com.app.test1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!TEST 1", "com.app.test1", patchers.PATCH_STOP_APP), + ("!com.app.test2", "com.app.test2", patchers.PATCH_STOP_APP), + ("!com.app.test3", "com.app.test3", patchers.PATCH_STOP_APP), + ], +) +async def test_select_source_firetv(hass, source, expected_arg, method_patch): + """Test that an app can be launched for FireTV.""" + conf_apps = { + "com.app.test1": "TEST 1", + "com.app.test3": None, + } + await _test_select_source( + hass, CONFIG_FIRETV_DEFAULT, conf_apps, source, expected_arg, method_patch ) @pytest.mark.parametrize( "config", [ - CONFIG_ANDROIDTV_PYTHON_ADB, - CONFIG_FIRETV_PYTHON_ADB, + CONFIG_ANDROIDTV_DEFAULT, + CONFIG_FIRETV_DEFAULT, ], ) async def test_setup_fail(hass, config): @@ -698,11 +545,9 @@ async def test_setup_fail(hass, config): patch_key, entity_id, config_entry = _setup(config) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(False)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) is False await hass.async_block_till_done() @@ -713,14 +558,14 @@ async def test_setup_fail(hass, config): async def test_adb_command(hass): """Test sending a command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "test command" response = "test response" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -742,14 +587,14 @@ async def test_adb_command(hass): async def test_adb_command_unicode_decode_error(hass): """Test sending a command via the `androidtv.adb_command` service that raises a UnicodeDecodeError exception.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "test command" response = b"test response" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -771,14 +616,14 @@ async def test_adb_command_unicode_decode_error(hass): async def test_adb_command_key(hass): """Test sending a key command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "HOME" response = None - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -800,14 +645,14 @@ async def test_adb_command_key(hass): async def test_adb_command_get_properties(hass): """Test sending the "GET_PROPERTIES" command via the `androidtv.adb_command` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) command = "GET_PROPERTIES" response = {"test key": "test value"} - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -830,13 +675,13 @@ async def test_adb_command_get_properties(hass): async def test_learn_sendevent(hass): """Test the `androidtv.learn_sendevent` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) response = "sendevent 1 2 3 4" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -859,12 +704,12 @@ async def test_learn_sendevent(hass): async def test_update_lock_not_acquired(hass): """Test that the state does not get updated when a `LockNotAcquiredException` is raised.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -892,14 +737,14 @@ async def test_update_lock_not_acquired(hass): async def test_download(hass): """Test the `androidtv.download` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) device_path = "device/path" local_path = "local/path" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -938,14 +783,14 @@ async def test_download(hass): async def test_upload(hass): """Test the `androidtv.upload` service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) device_path = "device/path" local_path = "local/path" - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -984,12 +829,12 @@ async def test_upload(hass): async def test_androidtv_volume_set(hass): """Test setting the volume for an Android TV device.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1011,12 +856,12 @@ async def test_get_image(hass, hass_ws_client): This is based on `test_get_image` in tests/components/media_player/test_init.py. """ - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1042,7 +887,7 @@ async def test_get_image(hass, hass_ws_client): with patch( "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", - side_effect=RuntimeError, + side_effect=ConnectionResetError, ): await client.send_json( {"id": 6, "type": "media_player_thumbnail", "entity_id": entity_id} @@ -1056,6 +901,39 @@ async def test_get_image(hass, hass_ws_client): assert state.state == STATE_UNAVAILABLE +async def test_get_image_disabled(hass, hass_ws_client): + """Test taking a screen capture with screencap option disabled. + + This is based on `test_get_image` in tests/components/media_player/test_init.py. + """ + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) + config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, options={CONF_SCREENCAP: False} + ) + + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + with patchers.patch_shell("11")[patch_key]: + await async_update_entity(hass, entity_id) + + client = await hass_ws_client(hass) + + with patch( + "androidtv.basetv.basetv_async.BaseTVAsync.adb_screencap", return_value=b"image" + ) as screen_cap: + await client.send_json( + {"id": 5, "type": "media_player_thumbnail", "entity_id": entity_id} + ) + + await client.receive_json() + assert not screen_cap.called + + async def _test_service( hass, entity_id, @@ -1088,10 +966,10 @@ async def _test_service( async def test_services_androidtv(hass): """Test media player services for an Android TV device.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1129,15 +1007,17 @@ async def test_services_androidtv(hass): async def test_services_firetv(hass): """Test media player services for a Fire TV device.""" - config = copy.deepcopy(CONFIG_FIRETV_ADB_SERVER) - config[DOMAIN][CONF_OPTIONS] = { - CONF_TURN_OFF_COMMAND: "test off", - CONF_TURN_ON_COMMAND: "test on", - } - patch_key, entity_id, config_entry = _setup(config) + patch_key, entity_id, config_entry = _setup(CONFIG_FIRETV_DEFAULT) config_entry.add_to_hass(hass) + hass.config_entries.async_update_entry( + config_entry, + options={ + CONF_TURN_OFF_COMMAND: "test off", + CONF_TURN_ON_COMMAND: "test on", + }, + ) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1150,10 +1030,10 @@ async def test_services_firetv(hass): async def test_volume_mute(hass): """Test the volume mute service.""" - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[patch_key]: + with patchers.patch_connect(True)[patch_key]: with patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1193,18 +1073,16 @@ async def test_volume_mute(hass): async def test_connection_closed_on_ha_stop(hass): """Test that the ADB socket connection is closed when HA stops.""" - patch_key, _, config_entry = _setup(CONFIG_ANDROIDTV_ADB_SERVER) + patch_key, _, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[patch_key]: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() - with patch( - "androidtv.androidtv.androidtv_async.AndroidTVAsync.adb_close" - ) as adb_close: + with patch("androidtv.basetv.basetv_async.BaseTVAsync.adb_close") as adb_close: hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP) await hass.async_block_till_done() assert adb_close.called @@ -1215,14 +1093,12 @@ async def test_exception(hass): HA will attempt to reconnect on the next update. """ - patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_PYTHON_ADB) + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) config_entry.add_to_hass(hass) - with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ - patch_key - ], patchers.patch_shell(SHELL_RESPONSE_OFF)[ - patch_key - ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() @@ -1243,3 +1119,38 @@ async def test_exception(hass): state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF + + +async def test_options_reload(hass): + """Test changing an option that will cause integration reload.""" + patch_key, entity_id, config_entry = _setup(CONFIG_ANDROIDTV_DEFAULT) + config_entry.add_to_hass(hass) + + with patchers.patch_connect(True)[patch_key], patchers.patch_shell( + SHELL_RESPONSE_OFF + )[patch_key]: + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + await async_update_entity(hass, entity_id) + state = hass.states.get(entity_id) + assert state is not None + assert state.state == STATE_OFF + + with patchers.PATCH_SETUP_ENTRY as setup_entry_call: + # change an option that not require integration reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_SCREENCAP: False} + ) + await hass.async_block_till_done() + + assert not setup_entry_call.called + + # change an option that require integration reload + hass.config_entries.async_update_entry( + config_entry, options={CONF_STATE_DETECTION_RULES: {}} + ) + await hass.async_block_till_done() + + assert setup_entry_call.called + assert config_entry.state is ConfigEntryState.LOADED