Log warning when entities referenced in service call not found (#31427)

* Raise entities not found error

* Make it a warning, not an error

* Add support for MATCH_ENTITY_NONE

* Fix lint

* Fix tests
This commit is contained in:
Paulus Schoutsen 2020-02-04 14:42:07 -08:00 committed by GitHub
parent 201ea2557e
commit f41623ca64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 339 additions and 89 deletions

View file

@ -23,6 +23,7 @@ from homeassistant.const import (
CONF_SENSORS,
CONF_USERNAME,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
HTTP_BASIC_AUTHENTICATION,
)
from homeassistant.exceptions import Unauthorized, UnknownUser
@ -236,6 +237,9 @@ def setup(hass, config):
if have_permission(user, entity_id)
]
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
return []
call_ids = await async_extract_entity_ids(hass, call)
entity_ids = []
for entity_id in hass.data[DATA_AMCREST][CAMERAS]:

View file

@ -30,6 +30,7 @@ from homeassistant.const import (
CONF_TIMEOUT,
CONF_ZONE,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
STATE_OFF,
STATE_ON,
STATE_PAUSED,
@ -201,6 +202,10 @@ class DenonDevice(MediaPlayerDevice):
def signal_handler(self, data):
"""Handle domain-specific signal by calling appropriate method."""
entity_ids = data[ATTR_ENTITY_ID]
if entity_ids == ENTITY_MATCH_NONE:
return
if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids:
params = {
key: value

View file

@ -11,6 +11,7 @@ from homeassistant.const import (
CONF_PLATFORM,
CONF_PORT,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
)
import homeassistant.helpers.config_validation as cv
@ -136,7 +137,9 @@ DEL_ALL_LINK_SCHEMA = vol.Schema(
LOAD_ALDB_SCHEMA = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): vol.Any(cv.entity_id, ENTITY_MATCH_ALL),
vol.Required(CONF_ENTITY_ID): vol.Any(
cv.entity_id, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
),
vol.Optional(SRV_LOAD_DB_RELOAD, default=False): cv.boolean,
}
)

View file

@ -39,6 +39,7 @@ from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_MODE,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
EVENT_HOMEASSISTANT_STOP,
)
from homeassistant.core import callback
@ -374,6 +375,9 @@ class LIFXManager:
async def async_service_to_entities(self, service):
"""Return the known entities that a service call mentions."""
if service.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
return []
if service.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
return self.entities.values()

View file

@ -22,7 +22,7 @@ from homeassistant.components.media_player.const import (
MEDIA_TYPE_MUSIC,
SERVICE_PLAY_MEDIA,
)
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM, ENTITY_MATCH_ALL
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_per_platform, discovery
@ -90,7 +90,7 @@ SCHEMA_SERVICE_SAY = vol.Schema(
{
vol.Required(ATTR_MESSAGE): cv.string,
vol.Optional(ATTR_CACHE): cv.boolean,
vol.Optional(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Required(ATTR_ENTITY_ID): cv.comp_entity_ids,
vol.Optional(ATTR_LANGUAGE): cv.string,
vol.Optional(ATTR_OPTIONS): dict,
}
@ -148,7 +148,7 @@ async def async_setup(hass, config):
async def async_say_handle(service):
"""Service handle for say."""
entity_ids = service.data.get(ATTR_ENTITY_ID, ENTITY_MATCH_ALL)
entity_ids = service.data[ATTR_ENTITY_ID]
message = service.data.get(ATTR_MESSAGE)
cache = service.data.get(ATTR_CACHE)
language = service.data.get(ATTR_LANGUAGE)

View file

@ -29,6 +29,7 @@ from homeassistant.const import (
CONF_HOST,
CONF_NAME,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
STATE_OFF,
STATE_ON,
)
@ -137,6 +138,9 @@ class LgWebOSMediaPlayerEntity(MediaPlayerDevice):
async def async_signal_handler(self, data):
"""Handle domain-specific signal by calling appropriate method."""
entity_ids = data[ATTR_ENTITY_ID]
if entity_ids == ENTITY_MATCH_NONE:
return
if entity_ids == ENTITY_MATCH_ALL or self.entity_id in entity_ids:
params = {
key: value

View file

@ -16,6 +16,7 @@ PLATFORM_FORMAT = "{platform}.{domain}"
MATCH_ALL = "*"
# Entity target all constant
ENTITY_MATCH_NONE = "none"
ENTITY_MATCH_ALL = "all"
# If no name is specified

View file

@ -52,6 +52,7 @@ from homeassistant.const import (
CONF_UNIT_SYSTEM_METRIC,
CONF_VALUE_TEMPLATE,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
SUN_EVENT_SUNRISE,
SUN_EVENT_SUNSET,
TEMP_CELSIUS,
@ -231,7 +232,9 @@ def entity_ids(value: Union[str, List]) -> List[str]:
return [entity_id(ent_id) for ent_id in value]
comp_entity_ids = vol.Any(vol.All(vol.Lower, ENTITY_MATCH_ALL), entity_ids)
comp_entity_ids = vol.Any(
vol.All(vol.Lower, vol.Any(ENTITY_MATCH_ALL, ENTITY_MATCH_NONE)), entity_ids
)
def entity_domain(domain: str) -> Callable[[Any], str]:
@ -736,7 +739,9 @@ def make_entity_service_schema(
{
**schema,
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids,
vol.Optional(ATTR_AREA_ID): vol.All(ensure_list, [str]),
vol.Optional(ATTR_AREA_ID): vol.Any(
ENTITY_MATCH_NONE, vol.All(ensure_list, [str])
),
},
extra=extra,
),

View file

@ -7,7 +7,12 @@ from typing import Callable
import voluptuous as vol
from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL
from homeassistant.const import ATTR_AREA_ID, ATTR_ENTITY_ID, ENTITY_MATCH_ALL
from homeassistant.const import (
ATTR_AREA_ID,
ATTR_ENTITY_ID,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
)
import homeassistant.core as ha
from homeassistant.exceptions import (
HomeAssistantError,
@ -121,11 +126,25 @@ async def async_extract_entities(hass, entities, service_call, expand_group=True
entity_ids = await async_extract_entity_ids(hass, service_call, expand_group)
return [
entity
for entity in entities
if entity.available and entity.entity_id in entity_ids
]
found = []
for entity in entities:
if entity.entity_id not in entity_ids:
continue
entity_ids.remove(entity.entity_id)
if not entity.available:
continue
found.append(entity)
if entity_ids:
_LOGGER.warning(
"Unable to find referenced entities %s", ", ".join(sorted(entity_ids))
)
return found
@bind_hass
@ -137,12 +156,15 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
entity_ids = service_call.data.get(ATTR_ENTITY_ID)
area_ids = service_call.data.get(ATTR_AREA_ID)
if not entity_ids and not area_ids:
return []
extracted = set()
if entity_ids:
if entity_ids in (None, ENTITY_MATCH_NONE) and area_ids in (
None,
ENTITY_MATCH_NONE,
):
return extracted
if entity_ids and entity_ids != ENTITY_MATCH_NONE:
# Entity ID attr can be a list or a string
if isinstance(entity_ids, str):
entity_ids = [entity_ids]
@ -152,7 +174,7 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
extracted.update(entity_ids)
if area_ids:
if area_ids and area_ids != ENTITY_MATCH_NONE:
if isinstance(area_ids, str):
area_ids = [area_ids]
@ -342,6 +364,16 @@ async def entity_service_call(hass, platforms, func, call, required_features=Non
platforms_entities.append(platform_entities)
if not target_all_entities:
for platform_entities in platforms_entities:
for entity in platform_entities:
entity_ids.remove(entity.entity_id)
if entity_ids:
_LOGGER.warning(
"Unable to find referenced entities %s", ", ".join(sorted(entity_ids))
)
tasks = [
_handle_service_platform_call(
hass, func, data, entities, call.context, required_features

View file

@ -65,7 +65,10 @@ class TestTTSGooglePlatform:
self.hass.services.call(
tts.DOMAIN,
"google_translate_say",
{tts.ATTR_MESSAGE: "90% of I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -89,7 +92,10 @@ class TestTTSGooglePlatform:
self.hass.services.call(
tts.DOMAIN,
"google_translate_say",
{tts.ATTR_MESSAGE: "90% of I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -115,6 +121,7 @@ class TestTTSGooglePlatform:
tts.DOMAIN,
"google_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
tts.ATTR_LANGUAGE: "de",
},
@ -139,7 +146,10 @@ class TestTTSGooglePlatform:
self.hass.services.call(
tts.DOMAIN,
"google_translate_say",
{tts.ATTR_MESSAGE: "90% of I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -161,7 +171,10 @@ class TestTTSGooglePlatform:
self.hass.services.call(
tts.DOMAIN,
"google_translate_say",
{tts.ATTR_MESSAGE: "90% of I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "90% of I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -193,6 +206,7 @@ class TestTTSGooglePlatform:
tts.DOMAIN,
"google_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: (
"I person is on front of your door."
"I person is on front of your door."
@ -203,7 +217,7 @@ class TestTTSGooglePlatform:
"I person is on front of your door."
"I person is on front of your door."
"I person is on front of your door."
)
),
},
)
self.hass.block_till_done()

View file

@ -66,7 +66,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call(
tts.DOMAIN, "marytts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"marytts_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "HomeAssistant",
},
)
self.hass.block_till_done()
@ -93,7 +98,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call(
tts.DOMAIN, "marytts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"marytts_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "HomeAssistant",
},
)
self.hass.block_till_done()
@ -123,7 +133,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call(
tts.DOMAIN, "marytts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"marytts_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "HomeAssistant",
},
)
self.hass.block_till_done()

View file

@ -95,7 +95,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -103,13 +106,13 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3".format(
self.hass.config.api.base_url
)
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
)
@ -125,7 +128,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -133,13 +139,13 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de_-_demo.mp3".format(
self.hass.config.api.base_url
)
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_-_demo.mp3",
)
)
@ -163,7 +169,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_LANGUAGE: "de",
},
)
@ -173,13 +180,13 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de_-_demo.mp3".format(
self.hass.config.api.base_url
)
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_-_demo.mp3",
)
)
@ -196,7 +203,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_LANGUAGE: "lang",
},
)
@ -206,7 +214,7 @@ class TestTTS:
assert not os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_lang_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_lang_-_demo.mp3",
)
)
@ -223,7 +231,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_LANGUAGE: "de",
tts.ATTR_OPTIONS: {"voice": "alex"},
},
@ -236,13 +245,13 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_{}_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de_{}_demo.mp3".format(
self.hass.config.api.base_url, opt_hash
)
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_{0}_demo.mp3".format(
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_{0}_demo.mp3".format(
opt_hash
),
)
@ -265,7 +274,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_LANGUAGE: "de",
},
)
@ -277,13 +287,13 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_de_{}_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_de_{}_demo.mp3".format(
self.hass.config.api.base_url, opt_hash
)
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_{0}_demo.mp3".format(
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_{0}_demo.mp3".format(
opt_hash
),
)
@ -302,7 +312,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_LANGUAGE: "de",
tts.ATTR_OPTIONS: {"speed": 1},
},
@ -315,7 +326,7 @@ class TestTTS:
assert not os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_{0}_demo.mp3".format(
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_{0}_demo.mp3".format(
opt_hash
),
)
@ -333,7 +344,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -341,7 +355,7 @@ class TestTTS:
assert calls[0].data[ATTR_MEDIA_CONTENT_TYPE] == MEDIA_TYPE_MUSIC
assert (
calls[0].data[ATTR_MEDIA_CONTENT_ID] == "http://fnord"
"/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd"
"/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491"
"_en_-_demo.mp3"
)
@ -357,7 +371,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -365,7 +382,7 @@ class TestTTS:
assert os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
)
@ -375,7 +392,7 @@ class TestTTS:
assert not os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
)
@ -393,7 +410,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -401,7 +421,7 @@ class TestTTS:
req = requests.get(calls[0].data[ATTR_MEDIA_CONTENT_ID])
_, demo_data = self.demo_provider.get_tts_audio("bla", "en")
demo_data = tts.SpeechManager.write_tags(
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
demo_data,
self.demo_provider,
"AI person is in front of your door.",
@ -425,7 +445,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -433,10 +456,10 @@ class TestTTS:
req = requests.get(calls[0].data[ATTR_MEDIA_CONTENT_ID])
_, demo_data = self.demo_provider.get_tts_audio("bla", "de")
demo_data = tts.SpeechManager.write_tags(
"265944c108cbb00b2a621be5930513e03a0bb2cd_de_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_de_-_demo.mp3",
demo_data,
self.demo_provider,
"I person is on front of your door.",
"There is someone at the door.",
"de",
None,
)
@ -453,7 +476,7 @@ class TestTTS:
self.hass.start()
url = (
"{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"
"{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3"
).format(self.hass.config.api.base_url)
req = requests.get(url)
@ -487,7 +510,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -495,7 +521,7 @@ class TestTTS:
assert not os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
)
@ -512,7 +538,8 @@ class TestTTS:
tts.DOMAIN,
"demo_say",
{
tts.ATTR_MESSAGE: "I person is on front of your door.",
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
tts.ATTR_CACHE: False,
},
)
@ -522,7 +549,7 @@ class TestTTS:
assert not os.path.isfile(
os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
)
@ -533,7 +560,7 @@ class TestTTS:
_, demo_data = self.demo_provider.get_tts_audio("bla", "en")
cache_file = os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
os.mkdir(self.default_tts_cache)
@ -552,14 +579,17 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
assert len(calls) == 1
assert calls[0].data[
ATTR_MEDIA_CONTENT_ID
] == "{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3".format(
] == "{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3".format(
self.hass.config.api.base_url
)
@ -579,7 +609,10 @@ class TestTTS:
self.hass.services.call(
tts.DOMAIN,
"demo_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "There is someone at the door.",
},
)
self.hass.block_till_done()
@ -590,7 +623,7 @@ class TestTTS:
_, demo_data = self.demo_provider.get_tts_audio("bla", "en")
cache_file = os.path.join(
self.default_tts_cache,
"265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3",
"42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3",
)
os.mkdir(self.default_tts_cache)
@ -605,7 +638,7 @@ class TestTTS:
self.hass.start()
url = (
"{}/api/tts_proxy/265944c108cbb00b2a621be5930513e03a0bb2cd_en_-_demo.mp3"
"{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3"
).format(self.hass.config.api.base_url)
req = requests.get(url)
@ -622,14 +655,15 @@ async def test_setup_component_and_web_get_url(hass, hass_client):
client = await hass_client()
url = "/api/tts_get_url"
data = {"platform": "demo", "message": "I person is on front of your door."}
data = {"platform": "demo", "message": "There is someone at the door."}
req = await client.post(url, json=data)
assert req.status == 200
response = await req.json()
assert response.get("url") == (
"{}/api/tts_proxy/265944c108cbb00b2a62"
"1be5930513e03a0bb2cd_en_-_demo.mp3".format(hass.config.api.base_url)
"{}/api/tts_proxy/42f18378fd4393d18c8dd11d03fa9563c1e54491_en_-_demo.mp3".format(
hass.config.api.base_url
)
)
tts_cache = hass.config.path(tts.DEFAULT_CACHE_DIR)
@ -646,7 +680,7 @@ async def test_setup_component_and_web_get_url_bad_config(hass, hass_client):
client = await hass_client()
url = "/api/tts_get_url"
data = {"message": "I person is on front of your door."}
data = {"message": "There is someone at the door."}
req = await client.post(url, json=data)
assert req.status == 400

View file

@ -67,7 +67,10 @@ class TestTTSVoiceRSSPlatform:
self.hass.services.call(
tts.DOMAIN,
"voicerss_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -97,7 +100,10 @@ class TestTTSVoiceRSSPlatform:
self.hass.services.call(
tts.DOMAIN,
"voicerss_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -121,6 +127,7 @@ class TestTTSVoiceRSSPlatform:
tts.DOMAIN,
"voicerss_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
tts.ATTR_LANGUAGE: "de-de",
},
@ -145,7 +152,10 @@ class TestTTSVoiceRSSPlatform:
self.hass.services.call(
tts.DOMAIN,
"voicerss_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -167,7 +177,10 @@ class TestTTSVoiceRSSPlatform:
self.hass.services.call(
tts.DOMAIN,
"voicerss_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
},
)
self.hass.block_till_done()
@ -194,7 +207,10 @@ class TestTTSVoiceRSSPlatform:
self.hass.services.call(
tts.DOMAIN,
"voicerss_say",
{tts.ATTR_MESSAGE: "I person is on front of your door."},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "I person is on front of your door.",
},
)
self.hass.block_till_done()

View file

@ -67,7 +67,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -103,7 +105,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -135,7 +139,11 @@ class TestTTSYandexPlatform:
self.hass.services.call(
tts.DOMAIN,
"yandextts_say",
{tts.ATTR_MESSAGE: "HomeAssistant", tts.ATTR_LANGUAGE: "ru-RU"},
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "HomeAssistant",
tts.ATTR_LANGUAGE: "ru-RU",
},
)
self.hass.block_till_done()
@ -165,7 +173,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -195,7 +205,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -230,7 +242,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -266,7 +280,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -298,7 +314,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -330,7 +348,9 @@ class TestTTSYandexPlatform:
setup_component(self.hass, tts.DOMAIN, config)
self.hass.services.call(
tts.DOMAIN, "yandextts_say", {tts.ATTR_MESSAGE: "HomeAssistant"}
tts.DOMAIN,
"yandextts_say",
{"entity_id": "media_player.something", tts.ATTR_MESSAGE: "HomeAssistant"},
)
self.hass.block_till_done()
@ -362,6 +382,7 @@ class TestTTSYandexPlatform:
tts.DOMAIN,
"yandextts_say",
{
"entity_id": "media_player.something",
tts.ATTR_MESSAGE: "HomeAssistant",
"options": {"emotion": "evil", "speed": 2},
},

View file

@ -7,8 +7,9 @@ from unittest.mock import Mock, patch
import asynctest
import pytest
import voluptuous as vol
from homeassistant.const import ENTITY_MATCH_ALL
from homeassistant.const import ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
import homeassistant.core as ha
from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import discovery
@ -223,10 +224,21 @@ async def test_extract_from_service_fails_if_no_entity_id(hass):
[MockEntity(name="test_1"), MockEntity(name="test_2")]
)
call = ha.ServiceCall("test", "service")
assert [] == sorted(
ent.entity_id for ent in (await component.async_extract_from_service(call))
assert (
await component.async_extract_from_service(ha.ServiceCall("test", "service"))
== []
)
assert (
await component.async_extract_from_service(
ha.ServiceCall("test", "service", {"entity_id": ENTITY_MATCH_NONE})
)
== []
)
assert (
await component.async_extract_from_service(
ha.ServiceCall("test", "service", {"area_id": ENTITY_MATCH_NONE})
)
== []
)
@ -429,3 +441,53 @@ async def test_extract_all_use_match_all(hass, caplog):
assert (
"Not passing an entity ID to a service to target all entities is deprecated"
) not in caplog.text
async def test_register_entity_service(hass):
"""Test not expanding a group."""
entity = MockEntity(entity_id=f"{DOMAIN}.entity")
calls = []
@ha.callback
def appender(**kwargs):
calls.append(kwargs)
entity.async_called_by_service = appender
component = EntityComponent(_LOGGER, DOMAIN, hass)
await component.async_add_entities([entity])
component.async_register_entity_service(
"hello", {"some": str}, "async_called_by_service"
)
with pytest.raises(vol.Invalid):
await hass.services.async_call(
DOMAIN,
"hello",
{"entity_id": entity.entity_id, "invalid": "data"},
blocking=True,
)
assert len(calls) == 0
await hass.services.async_call(
DOMAIN, "hello", {"entity_id": entity.entity_id, "some": "data"}, blocking=True
)
assert len(calls) == 1
assert calls[0] == {"some": "data"}
await hass.services.async_call(
DOMAIN, "hello", {"entity_id": ENTITY_MATCH_ALL, "some": "data"}, blocking=True
)
assert len(calls) == 2
assert calls[1] == {"some": "data"}
await hass.services.async_call(
DOMAIN, "hello", {"entity_id": ENTITY_MATCH_NONE, "some": "data"}, blocking=True
)
assert len(calls) == 2
await hass.services.async_call(
DOMAIN, "hello", {"area_id": ENTITY_MATCH_NONE, "some": "data"}, blocking=True
)
assert len(calls) == 2

View file

@ -12,7 +12,13 @@ import voluptuous as vol
from homeassistant import core as ha, exceptions
from homeassistant.auth.permissions import PolicyPermissions
import homeassistant.components # noqa: F401
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, STATE_OFF, STATE_ON
from homeassistant.const import (
ATTR_ENTITY_ID,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
STATE_OFF,
STATE_ON,
)
from homeassistant.helpers import (
device_registry as dev_reg,
entity_registry as ent_reg,
@ -252,6 +258,14 @@ async def test_extract_entity_ids(hass):
hass, call, expand_group=False
)
assert (
await service.async_extract_entity_ids(
hass,
ha.ServiceCall("light", "turn_on", {ATTR_ENTITY_ID: ENTITY_MATCH_NONE}),
)
== set()
)
async def test_extract_entity_ids_from_area(hass, area_mock):
"""Test extract_entity_ids method with areas."""
@ -266,6 +280,13 @@ async def test_extract_entity_ids_from_area(hass, area_mock):
"light.diff_area",
} == await service.async_extract_entity_ids(hass, call)
assert (
await service.async_extract_entity_ids(
hass, ha.ServiceCall("light", "turn_on", {"area_id": ENTITY_MATCH_NONE})
)
== set()
)
@asyncio.coroutine
def test_async_get_all_descriptions(hass):
@ -742,6 +763,15 @@ async def test_extract_from_service_available_device(hass):
for ent in (await service.async_extract_entities(hass, entities, call_2))
]
assert (
await service.async_extract_entities(
hass,
entities,
ha.ServiceCall("test", "service", data={"entity_id": ENTITY_MATCH_NONE},),
)
== []
)
async def test_extract_from_service_empty_if_no_entity_id(hass):
"""Test the extraction from service without specifying entity."""