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_SENSORS,
CONF_USERNAME, CONF_USERNAME,
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
HTTP_BASIC_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
) )
from homeassistant.exceptions import Unauthorized, UnknownUser from homeassistant.exceptions import Unauthorized, UnknownUser
@ -236,6 +237,9 @@ def setup(hass, config):
if have_permission(user, entity_id) 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) call_ids = await async_extract_entity_ids(hass, call)
entity_ids = [] entity_ids = []
for entity_id in hass.data[DATA_AMCREST][CAMERAS]: for entity_id in hass.data[DATA_AMCREST][CAMERAS]:

View file

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

View file

@ -11,6 +11,7 @@ from homeassistant.const import (
CONF_PLATFORM, CONF_PLATFORM,
CONF_PORT, CONF_PORT,
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
) )
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -136,7 +137,9 @@ DEL_ALL_LINK_SCHEMA = vol.Schema(
LOAD_ALDB_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, vol.Optional(SRV_LOAD_DB_RELOAD, default=False): cv.boolean,
} }
) )

View file

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

View file

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

View file

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

View file

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

View file

@ -52,6 +52,7 @@ from homeassistant.const import (
CONF_UNIT_SYSTEM_METRIC, CONF_UNIT_SYSTEM_METRIC,
CONF_VALUE_TEMPLATE, CONF_VALUE_TEMPLATE,
ENTITY_MATCH_ALL, ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
SUN_EVENT_SUNRISE, SUN_EVENT_SUNRISE,
SUN_EVENT_SUNSET, SUN_EVENT_SUNSET,
TEMP_CELSIUS, 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] 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]: def entity_domain(domain: str) -> Callable[[Any], str]:
@ -736,7 +739,9 @@ def make_entity_service_schema(
{ {
**schema, **schema,
vol.Optional(ATTR_ENTITY_ID): comp_entity_ids, 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, extra=extra,
), ),

View file

@ -7,7 +7,12 @@ from typing import Callable
import voluptuous as vol import voluptuous as vol
from homeassistant.auth.permissions.const import CAT_ENTITIES, POLICY_CONTROL 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 import homeassistant.core as ha
from homeassistant.exceptions import ( from homeassistant.exceptions import (
HomeAssistantError, 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) entity_ids = await async_extract_entity_ids(hass, service_call, expand_group)
return [ found = []
entity
for entity in entities for entity in entities:
if entity.available and entity.entity_id in entity_ids 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 @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) entity_ids = service_call.data.get(ATTR_ENTITY_ID)
area_ids = service_call.data.get(ATTR_AREA_ID) area_ids = service_call.data.get(ATTR_AREA_ID)
if not entity_ids and not area_ids:
return []
extracted = set() 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 # Entity ID attr can be a list or a string
if isinstance(entity_ids, str): if isinstance(entity_ids, str):
entity_ids = [entity_ids] entity_ids = [entity_ids]
@ -152,7 +174,7 @@ async def async_extract_entity_ids(hass, service_call, expand_group=True):
extracted.update(entity_ids) extracted.update(entity_ids)
if area_ids: if area_ids and area_ids != ENTITY_MATCH_NONE:
if isinstance(area_ids, str): if isinstance(area_ids, str):
area_ids = [area_ids] 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) 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 = [ tasks = [
_handle_service_platform_call( _handle_service_platform_call(
hass, func, data, entities, call.context, required_features hass, func, data, entities, call.context, required_features

View file

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

View file

@ -66,7 +66,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn): with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call( 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() self.hass.block_till_done()
@ -93,7 +98,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn): with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call( 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() self.hass.block_till_done()
@ -123,7 +133,12 @@ class TestTTSMaryTTSPlatform:
with patch("http.client.HTTPConnection", return_value=conn): with patch("http.client.HTTPConnection", return_value=conn):
self.hass.services.call( 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() self.hass.block_till_done()

View file

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

View file

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

View file

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

View file

@ -7,8 +7,9 @@ from unittest.mock import Mock, patch
import asynctest import asynctest
import pytest 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 import homeassistant.core as ha
from homeassistant.exceptions import PlatformNotReady from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import discovery 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")] [MockEntity(name="test_1"), MockEntity(name="test_2")]
) )
call = ha.ServiceCall("test", "service") assert (
await component.async_extract_from_service(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", {"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 ( assert (
"Not passing an entity ID to a service to target all entities is deprecated" "Not passing an entity ID to a service to target all entities is deprecated"
) not in caplog.text ) 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 import core as ha, exceptions
from homeassistant.auth.permissions import PolicyPermissions from homeassistant.auth.permissions import PolicyPermissions
import homeassistant.components # noqa: F401 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 ( from homeassistant.helpers import (
device_registry as dev_reg, device_registry as dev_reg,
entity_registry as ent_reg, entity_registry as ent_reg,
@ -252,6 +258,14 @@ async def test_extract_entity_ids(hass):
hass, call, expand_group=False 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): async def test_extract_entity_ids_from_area(hass, area_mock):
"""Test extract_entity_ids method with areas.""" """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", "light.diff_area",
} == await service.async_extract_entity_ids(hass, call) } == 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 @asyncio.coroutine
def test_async_get_all_descriptions(hass): 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)) 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): async def test_extract_from_service_empty_if_no_entity_id(hass):
"""Test the extraction from service without specifying entity.""" """Test the extraction from service without specifying entity."""