Register 'androidtv.download' and 'androidtv.upload' services (#30086)

* Add tests

* Add FileSync test

* Fill in services.yaml for 'androidtv.adb_filesync' service

* Update example paths in services.yaml

* Bump androidtv to 0.0.37

* Bump androidtv to 0.0.37

* Bump androidtv to 0.0.37

* Import LockNotAcquiredException

* Import LockNotAcquiredException from androidtv.exceptions

* Rename 'host' to 'address'

* Add a logging statement when an ADB command is skipped

* Check hass.config.is_allowed_path(local_path)

* Add return

* Fix pylint

* Reduce duplicated code (AndroidTVDevice vs. FireTVDevice)

* Split 'adb_filesync' service into 'download' and 'upload' services

* Don't use '.get()' for required data; return if the services are already registered

* Replace "command" with ATTR_COMMAND

* Don't try to connect to a device if it is a duplicate
This commit is contained in:
Jeff Irion 2020-01-06 14:10:13 -08:00 committed by Paulus Schoutsen
parent e88bfda2a8
commit 5ec5df77cc
6 changed files with 358 additions and 40 deletions

View File

@ -4,7 +4,7 @@
"documentation": "https://www.home-assistant.io/integrations/androidtv", "documentation": "https://www.home-assistant.io/integrations/androidtv",
"requirements": [ "requirements": [
"adb-shell==0.1.0", "adb-shell==0.1.0",
"androidtv==0.0.36", "androidtv==0.0.37",
"pure-python-adb==0.2.2.dev0" "pure-python-adb==0.2.2.dev0"
], ],
"dependencies": [], "dependencies": [],

View File

@ -12,6 +12,7 @@ from adb_shell.exceptions import (
) )
from androidtv import ha_state_detection_rules_validator, setup from androidtv import ha_state_detection_rules_validator, setup
from androidtv.constants import APPS, KEYS from androidtv.constants import APPS, KEYS
from androidtv.exceptions import LockNotAcquiredException
import voluptuous as vol import voluptuous as vol
from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice from homeassistant.components.media_player import PLATFORM_SCHEMA, MediaPlayerDevice
@ -72,6 +73,9 @@ SUPPORT_FIRETV = (
| SUPPORT_STOP | SUPPORT_STOP
) )
ATTR_DEVICE_PATH = "device_path"
ATTR_LOCAL_PATH = "local_path"
CONF_ADBKEY = "adbkey" CONF_ADBKEY = "adbkey"
CONF_ADB_SERVER_IP = "adb_server_ip" CONF_ADB_SERVER_IP = "adb_server_ip"
CONF_ADB_SERVER_PORT = "adb_server_port" CONF_ADB_SERVER_PORT = "adb_server_port"
@ -92,11 +96,29 @@ DEVICE_FIRETV = "firetv"
DEVICE_CLASSES = [DEFAULT_DEVICE_CLASS, DEVICE_ANDROIDTV, DEVICE_FIRETV] DEVICE_CLASSES = [DEFAULT_DEVICE_CLASS, DEVICE_ANDROIDTV, DEVICE_FIRETV]
SERVICE_ADB_COMMAND = "adb_command" SERVICE_ADB_COMMAND = "adb_command"
SERVICE_DOWNLOAD = "download"
SERVICE_UPLOAD = "upload"
SERVICE_ADB_COMMAND_SCHEMA = vol.Schema( SERVICE_ADB_COMMAND_SCHEMA = vol.Schema(
{vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_COMMAND): cv.string} {vol.Required(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_COMMAND): cv.string}
) )
SERVICE_DOWNLOAD_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_DEVICE_PATH): cv.string,
vol.Required(ATTR_LOCAL_PATH): cv.string,
}
)
SERVICE_UPLOAD_SCHEMA = vol.Schema(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_ids,
vol.Required(ATTR_DEVICE_PATH): cv.string,
vol.Required(ATTR_LOCAL_PATH): cv.string,
}
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
@ -133,7 +155,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Android TV / Fire TV platform.""" """Set up the Android TV / Fire TV platform."""
hass.data.setdefault(ANDROIDTV_DOMAIN, {}) hass.data.setdefault(ANDROIDTV_DOMAIN, {})
host = f"{config[CONF_HOST]}:{config[CONF_PORT]}" address = f"{config[CONF_HOST]}:{config[CONF_PORT]}"
if address in hass.data[ANDROIDTV_DOMAIN]:
_LOGGER.warning("Platform already setup on %s, skipping", address)
return
if CONF_ADB_SERVER_IP not in config: if CONF_ADB_SERVER_IP not in config:
# Use "adb_shell" (Python ADB implementation) # Use "adb_shell" (Python ADB implementation)
@ -192,44 +218,38 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
else: else:
device_name = "Android TV / Fire TV device" device_name = "Android TV / Fire TV device"
_LOGGER.warning("Could not connect to %s at %s %s", device_name, host, adb_log) _LOGGER.warning(
"Could not connect to %s at %s %s", device_name, address, adb_log
)
raise PlatformNotReady raise PlatformNotReady
if host in hass.data[ANDROIDTV_DOMAIN]: device_args = [
_LOGGER.warning("Platform already setup on %s, skipping", host) aftv,
else: config[CONF_NAME],
if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV: config[CONF_APPS],
device = AndroidTVDevice( config[CONF_GET_SOURCES],
aftv, config.get(CONF_TURN_ON_COMMAND),
config[CONF_NAME], config.get(CONF_TURN_OFF_COMMAND),
config[CONF_APPS], ]
config[CONF_GET_SOURCES],
config.get(CONF_TURN_ON_COMMAND),
config.get(CONF_TURN_OFF_COMMAND),
)
device_name = config[CONF_NAME] if CONF_NAME in config else "Android TV"
else:
device = FireTVDevice(
aftv,
config[CONF_NAME],
config[CONF_APPS],
config[CONF_GET_SOURCES],
config.get(CONF_TURN_ON_COMMAND),
config.get(CONF_TURN_OFF_COMMAND),
)
device_name = config[CONF_NAME] if CONF_NAME in config else "Fire TV"
add_entities([device]) if aftv.DEVICE_CLASS == DEVICE_ANDROIDTV:
_LOGGER.debug("Setup %s at %s %s", device_name, host, adb_log) device = AndroidTVDevice(*device_args)
hass.data[ANDROIDTV_DOMAIN][host] = device device_name = config.get(CONF_NAME, "Android TV")
else:
device = FireTVDevice(*device_args)
device_name = config.get(CONF_NAME, "Fire TV")
add_entities([device])
_LOGGER.debug("Setup %s at %s %s", device_name, address, adb_log)
hass.data[ANDROIDTV_DOMAIN][address] = device
if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND): if hass.services.has_service(ANDROIDTV_DOMAIN, SERVICE_ADB_COMMAND):
return return
def service_adb_command(service): def service_adb_command(service):
"""Dispatch service calls to target entities.""" """Dispatch service calls to target entities."""
cmd = service.data.get(ATTR_COMMAND) cmd = service.data[ATTR_COMMAND]
entity_id = service.data.get(ATTR_ENTITY_ID) entity_id = service.data[ATTR_ENTITY_ID]
target_devices = [ target_devices = [
dev dev
for dev in hass.data[ANDROIDTV_DOMAIN].values() for dev in hass.data[ANDROIDTV_DOMAIN].values()
@ -255,6 +275,52 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
schema=SERVICE_ADB_COMMAND_SCHEMA, schema=SERVICE_ADB_COMMAND_SCHEMA,
) )
def service_download(service):
"""Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
local_path = service.data[ATTR_LOCAL_PATH]
if not hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
return
device_path = service.data[ATTR_DEVICE_PATH]
entity_id = service.data[ATTR_ENTITY_ID]
target_device = [
dev
for dev in hass.data[ANDROIDTV_DOMAIN].values()
if dev.entity_id in entity_id
][0]
target_device.adb_pull(local_path, device_path)
hass.services.register(
ANDROIDTV_DOMAIN,
SERVICE_DOWNLOAD,
service_download,
schema=SERVICE_DOWNLOAD_SCHEMA,
)
def service_upload(service):
"""Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
local_path = service.data[ATTR_LOCAL_PATH]
if not hass.config.is_allowed_path(local_path):
_LOGGER.warning("'%s' is not secure to load data from!", local_path)
return
device_path = service.data[ATTR_DEVICE_PATH]
entity_id = service.data[ATTR_ENTITY_ID]
target_devices = [
dev
for dev in hass.data[ANDROIDTV_DOMAIN].values()
if dev.entity_id in entity_id
]
for target_device in target_devices:
target_device.adb_push(local_path, device_path)
hass.services.register(
ANDROIDTV_DOMAIN, SERVICE_UPLOAD, service_upload, schema=SERVICE_UPLOAD_SCHEMA,
)
def adb_decorator(override_available=False): def adb_decorator(override_available=False):
"""Wrap ADB methods and catch exceptions. """Wrap ADB methods and catch exceptions.
@ -274,6 +340,12 @@ def adb_decorator(override_available=False):
try: try:
return func(self, *args, **kwargs) return func(self, *args, **kwargs)
except LockNotAcquiredException:
# If the ADB lock could not be acquired, skip this command
_LOGGER.info(
"ADB command not executed because the connection is currently in use"
)
return
except self.exceptions as err: except self.exceptions as err:
_LOGGER.error( _LOGGER.error(
"Failed to execute an ADB command. ADB connection re-" "Failed to execute an ADB command. ADB connection re-"
@ -465,6 +537,16 @@ class ADBDevice(MediaPlayerDevice):
self.schedule_update_ha_state() self.schedule_update_ha_state()
return self._adb_response return self._adb_response
@adb_decorator()
def adb_pull(self, local_path, device_path):
"""Download a file from your Android TV / Fire TV device to your Home Assistant instance."""
self.aftv.adb_pull(local_path, device_path)
@adb_decorator()
def adb_push(self, local_path, device_path):
"""Upload a file from your Home Assistant instance to an Android TV / Fire TV device."""
self.aftv.adb_push(local_path, device_path)
class AndroidTVDevice(ADBDevice): class AndroidTVDevice(ADBDevice):
"""Representation of an Android TV device.""" """Representation of an Android TV device."""

View File

@ -9,3 +9,27 @@ adb_command:
command: command:
description: Either a key command or an ADB shell command. description: Either a key command or an ADB shell command.
example: 'HOME' example: 'HOME'
download:
description: Download a file from your Android TV / Fire TV device to your Home Assistant instance.
fields:
entity_id:
description: Name of Android TV / Fire TV entity.
example: 'media_player.android_tv_living_room'
device_path:
description: The filepath on the Android TV / Fire TV device.
example: '/storage/emulated/0/Download/example.txt'
local_path:
description: The filepath on your Home Assistant instance.
example: '/config/example.txt'
upload:
description: Upload a file from your Home Assistant instance to an Android TV / Fire TV device.
fields:
entity_id:
description: Name(s) of Android TV / Fire TV entities.
example: 'media_player.android_tv_living_room'
device_path:
description: The filepath on the Android TV / Fire TV device.
example: '/storage/emulated/0/Download/example.txt'
local_path:
description: The filepath on your Home Assistant instance.
example: '/config/example.txt'

View File

@ -220,7 +220,7 @@ ambiclimate==0.2.1
amcrest==1.5.3 amcrest==1.5.3
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv==0.0.36 androidtv==0.0.37
# homeassistant.components.anel_pwrctrl # homeassistant.components.anel_pwrctrl
anel_pwrctrl-homeassistant==0.0.1.dev2 anel_pwrctrl-homeassistant==0.0.1.dev2

View File

@ -87,7 +87,7 @@ airly==0.0.2
ambiclimate==0.2.1 ambiclimate==0.2.1
# homeassistant.components.androidtv # homeassistant.components.androidtv
androidtv==0.0.36 androidtv==0.0.37
# homeassistant.components.apns # homeassistant.components.apns
apns2==0.3.0 apns2==0.3.0

View File

@ -1,11 +1,21 @@
"""The tests for the androidtv platform.""" """The tests for the androidtv platform."""
import logging import logging
from unittest.mock import patch
from androidtv.exceptions import LockNotAcquiredException
from homeassistant.components.androidtv.media_player import ( from homeassistant.components.androidtv.media_player import (
ANDROIDTV_DOMAIN, ANDROIDTV_DOMAIN,
ATTR_COMMAND,
ATTR_DEVICE_PATH,
ATTR_LOCAL_PATH,
CONF_ADB_SERVER_IP, CONF_ADB_SERVER_IP,
CONF_ADBKEY, CONF_ADBKEY,
CONF_APPS, CONF_APPS,
KEYS,
SERVICE_ADB_COMMAND,
SERVICE_DOWNLOAD,
SERVICE_UPLOAD,
) )
from homeassistant.components.media_player.const import ( from homeassistant.components.media_player.const import (
ATTR_INPUT_SOURCE, ATTR_INPUT_SOURCE,
@ -70,7 +80,7 @@ CONFIG_FIRETV_ADB_SERVER = {
} }
def _setup(hass, config): def _setup(config):
"""Perform common setup tasks for the tests.""" """Perform common setup tasks for the tests."""
if CONF_ADB_SERVER_IP not in config[DOMAIN]: if CONF_ADB_SERVER_IP not in config[DOMAIN]:
patch_key = "python" patch_key = "python"
@ -93,7 +103,7 @@ async def _test_reconnect(hass, caplog, config):
https://developers.home-assistant.io/docs/en/integration_quality_scale_index.html https://developers.home-assistant.io/docs/en/integration_quality_scale_index.html
""" """
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
@ -164,7 +174,7 @@ async def _test_adb_shell_returns_none(hass, config):
The state should be `None` and the device should be unavailable. The state should be `None` and the device should be unavailable.
""" """
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
@ -272,7 +282,7 @@ async def test_setup_with_adbkey(hass):
"""Test that setup succeeds when using an ADB key.""" """Test that setup succeeds when using an ADB key."""
config = CONFIG_ANDROIDTV_PYTHON_ADB.copy() config = CONFIG_ANDROIDTV_PYTHON_ADB.copy()
config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey") config[DOMAIN][CONF_ADBKEY] = hass.config.path("user_provided_adbkey")
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
@ -290,7 +300,7 @@ async def _test_sources(hass, config0):
"""Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices.""" """Test that sources (i.e., apps) are handled correctly for Android TV and Fire TV devices."""
config = config0.copy() config = config0.copy()
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
@ -362,7 +372,7 @@ 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.""" """Test that the methods for launching and stopping apps are called correctly when selecting a source."""
config = config0.copy() config = config0.copy()
config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"} config[DOMAIN][CONF_APPS] = {"com.app.test1": "TEST 1"}
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
@ -519,7 +529,7 @@ async def test_firetv_select_source_stop_app_id_no_name(hass):
async def _test_setup_fail(hass, config): async def _test_setup_fail(hass, config):
"""Test that the entity is not created when the ADB connection is not established.""" """Test that the entity is not created when the ADB connection is not established."""
patch_key, entity_id = _setup(hass, config) patch_key, entity_id = _setup(config)
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(False)[
patch_key patch_key
@ -569,14 +579,216 @@ async def test_setup_two_devices(hass):
async def test_setup_same_device_twice(hass): async def test_setup_same_device_twice(hass):
"""Test that setup succeeds with a duplicated config entry.""" """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_key(hass):
"""Test sending a key command via the `androidtv.adb_command` service."""
patch_key = "server" patch_key = "server"
entity_id = "media_player.android_tv"
command = "HOME"
response = None
with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
], patchers.patch_shell("")[patch_key]: ], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) 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)[ with patchers.PATCH_ADB_DEVICE_TCP, patchers.patch_connect(True)[
patch_key patch_key
], patchers.patch_shell("")[patch_key]: ], patchers.patch_shell("")[patch_key]:
assert await async_setup_component(hass, DOMAIN, CONFIG_ANDROIDTV_ADB_SERVER) 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)