"""The tests for the androidtv platform.""" import logging from unittest.mock import patch from androidtv.exceptions import LockNotAcquiredException from homeassistant.components.androidtv.media_player import ( ANDROIDTV_DOMAIN, ATTR_COMMAND, ATTR_DEVICE_PATH, ATTR_LOCAL_PATH, CONF_ADB_SERVER_IP, CONF_ADBKEY, CONF_APPS, CONF_EXCLUDE_UNNAMED_APPS, KEYS, SERVICE_ADB_COMMAND, SERVICE_DOWNLOAD, SERVICE_UPLOAD, ) from homeassistant.components.media_player.const import ( ATTR_INPUT_SOURCE, DOMAIN, SERVICE_SELECT_SOURCE, ) from homeassistant.const import ( ATTR_ENTITY_ID, CONF_DEVICE_CLASS, CONF_HOST, CONF_NAME, CONF_PLATFORM, SERVICE_VOLUME_SET, STATE_IDLE, STATE_OFF, STATE_PLAYING, STATE_UNAVAILABLE, ) from homeassistant.setup import async_setup_component from . import patchers # Android TV device with Python ADB implementation CONFIG_ANDROIDTV_PYTHON_ADB = { DOMAIN: { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Android TV", CONF_DEVICE_CLASS: "androidtv", } } # Android TV device with ADB server CONFIG_ANDROIDTV_ADB_SERVER = { DOMAIN: { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Android TV", CONF_DEVICE_CLASS: "androidtv", CONF_ADB_SERVER_IP: "127.0.0.1", } } # Fire TV device with Python ADB implementation CONFIG_FIRETV_PYTHON_ADB = { DOMAIN: { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Fire TV", CONF_DEVICE_CLASS: "firetv", } } # Fire TV device with ADB server CONFIG_FIRETV_ADB_SERVER = { DOMAIN: { CONF_PLATFORM: ANDROIDTV_DOMAIN, CONF_HOST: "127.0.0.1", CONF_NAME: "Fire TV", CONF_DEVICE_CLASS: "firetv", CONF_ADB_SERVER_IP: "127.0.0.1", } } def _setup(config): """Perform common setup tasks for the tests.""" if CONF_ADB_SERVER_IP not in config[DOMAIN]: patch_key = "python" else: patch_key = "server" if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv": entity_id = "media_player.android_tv" else: entity_id = "media_player.fire_tv" return patch_key, entity_id async def _test_reconnect(hass, caplog, config): """Test that the error and reconnection attempts are logged correctly. "Handles device/service unavailable. Log a warning once when unavailable, log once when reconnected." https://developers.home-assistant.io/docs/en/integration_quality_scale_index.html """ patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF caplog.clear() caplog.set_level(logging.WARNING) 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 hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_UNAVAILABLE assert len(caplog.record_tuples) == 2 assert caplog.record_tuples[0][1] == logging.ERROR assert caplog.record_tuples[1][1] == logging.WARNING caplog.set_level(logging.DEBUG) with patchers.patch_connect(True)[patch_key], patchers.patch_shell("1")[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: # Update 1 will reconnect await hass.helpers.entity_component.async_update_entity(entity_id) # If using an ADB server, the state will get updated; otherwise, the # state will be the last known state state = hass.states.get(entity_id) if patch_key == "server": assert state.state == STATE_IDLE else: assert state.state == STATE_OFF # Update 2 will update the state, regardless of which ADB connection # method is used await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_IDLE 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] ) return True async def _test_adb_shell_returns_none(hass, config): """Test the case that the ADB shell command returns `None`. The state should be `None` and the device should be unavailable. """ patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state != STATE_UNAVAILABLE with patchers.patch_shell(None)[patch_key], patchers.patch_shell(error=True)[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_UNAVAILABLE return True async def test_reconnect_androidtv_python_adb(hass, caplog): """Test that the error and reconnection attempts are logged correctly. * Device type: Android TV * ADB connection method: Python ADB implementation """ assert await _test_reconnect(hass, caplog, CONFIG_ANDROIDTV_PYTHON_ADB) async def test_adb_shell_returns_none_androidtv_python_adb(hass): """Test the case that the ADB shell command returns `None`. * Device type: Android TV * ADB connection method: Python ADB implementation """ assert await _test_adb_shell_returns_none(hass, CONFIG_ANDROIDTV_PYTHON_ADB) async def test_reconnect_firetv_python_adb(hass, caplog): """Test that the error and reconnection attempts are logged correctly. * Device type: Fire TV * ADB connection method: Python ADB implementation """ assert await _test_reconnect(hass, caplog, CONFIG_FIRETV_PYTHON_ADB) async def test_adb_shell_returns_none_firetv_python_adb(hass): """Test the case that the ADB shell command returns `None`. * Device type: Fire TV * ADB connection method: Python ADB implementation """ assert await _test_adb_shell_returns_none(hass, CONFIG_FIRETV_PYTHON_ADB) async def test_reconnect_androidtv_adb_server(hass, caplog): """Test that the error and reconnection attempts are logged correctly. * Device type: Android TV * ADB connection method: ADB server """ assert await _test_reconnect(hass, caplog, CONFIG_ANDROIDTV_ADB_SERVER) async def test_adb_shell_returns_none_androidtv_adb_server(hass): """Test the case that the ADB shell command returns `None`. * Device type: Android TV * ADB connection method: ADB server """ assert await _test_adb_shell_returns_none(hass, CONFIG_ANDROIDTV_ADB_SERVER) async def test_reconnect_firetv_adb_server(hass, caplog): """Test that the error and reconnection attempts are logged correctly. * Device type: Fire TV * ADB connection method: ADB server """ assert await _test_reconnect(hass, caplog, CONFIG_FIRETV_ADB_SERVER) async def test_adb_shell_returns_none_firetv_adb_server(hass): """Test the case that the ADB shell command returns `None`. * Device type: Fire TV * ADB connection method: ADB server """ assert await _test_adb_shell_returns_none(hass, CONFIG_FIRETV_ADB_SERVER) async def test_setup_with_adbkey(hass): """Test that setup succeeds when using an ADB key.""" config = CONFIG_ANDROIDTV_PYTHON_ADB.copy() config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey") patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[ patch_key ], patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER, patchers.PATCH_ISFILE, patchers.PATCH_ACCESS: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF async def _test_sources(hass, config0): """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices.""" config = config0.copy() config[DOMAIN][CONF_APPS] = { "com.app.test1": "TEST 1", "com.app.test3": None, "com.app.test4": "", } patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF if config[DOMAIN].get(CONF_DEVICE_CLASS) != "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, ) else: patch_update = patchers.patch_firetv_update( "playing", "com.app.test1", ["com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4"], ) with patch_update: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_PLAYING assert state.attributes["source"] == "TEST 1" assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] if config[DOMAIN].get(CONF_DEVICE_CLASS) != "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, ) else: patch_update = patchers.patch_firetv_update( "playing", "com.app.test2", ["com.app.test2", "com.app.test1", "com.app.test3", "com.app.test4"], ) with patch_update: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_PLAYING assert state.attributes["source"] == "com.app.test2" assert sorted(state.attributes["source_list"]) == ["TEST 1", "com.app.test2"] return True async def test_androidtv_sources(hass): """Test that sources (i.e., apps) are handled correctly for Android TV devices.""" assert await _test_sources(hass, CONFIG_ANDROIDTV_ADB_SERVER) async def test_firetv_sources(hass): """Test that sources (i.e., apps) are handled correctly for Fire TV devices.""" assert await _test_sources(hass, CONFIG_FIRETV_ADB_SERVER) async def _test_exclude_sources(hass, config0, expected_sources): """Test that sources (i.e., apps) are handled correctly when the `exclude_unnamed_apps` config parameter is provided.""" config = config0.copy() config[DOMAIN][CONF_APPS] = { "com.app.test1": "TEST 1", "com.app.test3": None, "com.app.test4": "", } patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF if config[DOMAIN].get(CONF_DEVICE_CLASS) != "firetv": patch_update = patchers.patch_androidtv_update( "playing", "com.app.test1", [ "com.app.test1", "com.app.test2", "com.app.test3", "com.app.test4", "com.app.test5", ], "hdmi", False, 1, ) 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", ], ) with patch_update: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_PLAYING 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 = CONFIG_ANDROIDTV_ADB_SERVER.copy() config[DOMAIN][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 = CONFIG_FIRETV_ADB_SERVER.copy() config[DOMAIN][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): """Test that the methods for launching and stopping apps are called correctly when selecting a source.""" config = config0.copy() config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1", "com.app.test3": None} patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF with method_patch as method_patch_: await hass.services.async_call( DOMAIN, SERVICE_SELECT_SOURCE, {ATTR_ENTITY_ID: entity_id, ATTR_INPUT_SOURCE: source}, blocking=True, ) method_patch_.assert_called_with(expected_arg) return True 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, ) 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, ) async def _test_setup_fail(hass, config): """Test that the entity is not created when the ADB connection is not established.""" patch_key, entity_id = _setup(config) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[ patch_key ], patchers.patch_shell("")[ patch_key ], patchers.PATCH_KEYGEN, patchers.PATCH_ANDROIDTV_OPEN, patchers.PATCH_SIGNER: assert await async_setup_component(hass, DOMAIN, config) await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is None return True async def test_setup_fail_androidtv(hass): """Test that the Android TV entity is not created when the ADB connection is not established.""" assert await _test_setup_fail(hass, CONFIG_ANDROIDTV_PYTHON_ADB) async def test_setup_fail_firetv(hass): """Test that the Fire TV entity is not created when the ADB connection is not established.""" assert await _test_setup_fail(hass, CONFIG_FIRETV_PYTHON_ADB) async def test_setup_two_devices(hass): """Test that two devices can be set up.""" config = { DOMAIN: [ CONFIG_ANDROIDTV_ADB_SERVER[DOMAIN], CONFIG_FIRETV_ADB_SERVER[DOMAIN].copy(), ] } config[DOMAIN][1][CONF_HOST] = "127.0.0.2" patch_key = "server" with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, config) for entity_id in ["media_player.android_tv", "media_player.fire_tv"]: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF async def test_setup_same_device_twice(hass): """Test that setup succeeds with a duplicated config entry.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) state = hass.states.get(entity_id) assert state is not None assert hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) async def test_adb_command(hass): """Test sending a command via the `androidtv.adb_command` service.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) command = "test command" response = "test response" with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patch( "androidtv.basetv.BaseTV.adb_shell", return_value=response ) as patch_shell: await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND, {ATTR_ENTITY_ID: entity_id, ATTR_COMMAND: command}, blocking=True, ) patch_shell.assert_called_with(command) state = hass.states.get(entity_id) assert state is not None assert state.attributes["adb_response"] == response 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 = _setup(CONFIG_ANDROIDTV_ADB_SERVER) command = "test command" response = b"test response" with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patch( "androidtv.basetv.BaseTV.adb_shell", side_effect=UnicodeDecodeError("utf-8", response, 0, len(response), "TEST"), ): await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND, {ATTR_ENTITY_ID: entity_id, ATTR_COMMAND: command}, blocking=True, ) # patch_shell.assert_called_with(command) state = hass.states.get(entity_id) assert state is not None assert state.attributes["adb_response"] is None async def test_adb_command_key(hass): """Test sending a key command via the `androidtv.adb_command` service.""" patch_key = "server" entity_id = "media_player.android_tv" command = "HOME" response = None with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patch( "androidtv.basetv.BaseTV.adb_shell", return_value=response ) as patch_shell: await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND, {ATTR_ENTITY_ID: entity_id, ATTR_COMMAND: command}, blocking=True, ) patch_shell.assert_called_with(f"input keyevent {KEYS[command]}") state = hass.states.get(entity_id) assert state is not None assert state.attributes["adb_response"] is None async def test_adb_command_get_properties(hass): """Test sending the "GET_PROPERTIES" command via the `androidtv.adb_command` service.""" patch_key = "server" entity_id = "media_player.android_tv" command = "GET_PROPERTIES" response = {"test key": "test value"} with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patch( "androidtv.androidtv.AndroidTV.get_properties_dict", return_value=response ) as patch_get_props: await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND, {ATTR_ENTITY_ID: entity_id, ATTR_COMMAND: command}, blocking=True, ) patch_get_props.assert_called() state = hass.states.get(entity_id) assert state is not None assert state.attributes["adb_response"] == str(response) 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 = _setup(CONFIG_ANDROIDTV_ADB_SERVER) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patchers.patch_shell("")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF with patch( "androidtv.androidtv.AndroidTV.update", side_effect=LockNotAcquiredException ): with patchers.patch_shell("1")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_OFF with patchers.patch_shell("1")[patch_key]: await hass.helpers.entity_component.async_update_entity(entity_id) state = hass.states.get(entity_id) assert state is not None assert state.state == STATE_IDLE async def test_download(hass): """Test the `androidtv.download` service.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) device_path = "device/path" local_path = "local/path" with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) # Failed download because path is not whitelisted with patch("androidtv.basetv.BaseTV.adb_pull") as patch_pull: await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_DOWNLOAD, { ATTR_ENTITY_ID: entity_id, ATTR_DEVICE_PATH: device_path, ATTR_LOCAL_PATH: local_path, }, blocking=True, ) patch_pull.assert_not_called() # Successful download with patch("androidtv.basetv.BaseTV.adb_pull") as patch_pull, patch.object( hass.config, "is_allowed_path", return_value=True ): await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_DOWNLOAD, { ATTR_ENTITY_ID: entity_id, ATTR_DEVICE_PATH: device_path, ATTR_LOCAL_PATH: local_path, }, blocking=True, ) patch_pull.assert_called_with(local_path, device_path) async def test_upload(hass): """Test the `androidtv.upload` service.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) device_path = "device/path" local_path = "local/path" with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) # Failed upload because path is not whitelisted with patch("androidtv.basetv.BaseTV.adb_push") as patch_push: await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_UPLOAD, { ATTR_ENTITY_ID: entity_id, ATTR_DEVICE_PATH: device_path, ATTR_LOCAL_PATH: local_path, }, blocking=True, ) patch_push.assert_not_called() # Successful upload with patch("androidtv.basetv.BaseTV.adb_push") as patch_push, patch.object( hass.config, "is_allowed_path", return_value=True ): await hass.services.async_call( ANDROIDTV_DOMAIN, SERVICE_UPLOAD, { ATTR_ENTITY_ID: entity_id, ATTR_DEVICE_PATH: device_path, ATTR_LOCAL_PATH: local_path, }, blocking=True, ) patch_push.assert_called_with(local_path, device_path) async def test_androidtv_volume_set(hass): """Test setting the volume for an Android TV device.""" patch_key, entity_id = _setup(CONFIG_ANDROIDTV_ADB_SERVER) with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ patch_key ], patchers.patch_shell("")[patch_key]: assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) with patch( "androidtv.basetv.BaseTV.set_volume_level", return_value=0.5 ) as patch_set_volume_level: await hass.services.async_call( DOMAIN, SERVICE_VOLUME_SET, {ATTR_ENTITY_ID: entity_id, "volume_level": 0.5}, blocking=True, ) patch_set_volume_level.assert_called_with(0.5)