RFC: Call services directly (#18720)

* Call services directly

* Simplify

* Type

* Lint

* Update name

* Fix tests

* Catch exceptions in HTTP view

* Lint

* Handle ServiceNotFound in API endpoints that call services

* Type

* Don't crash recorder on non-JSON serializable objects
This commit is contained in:
Paulus Schoutsen 2018-11-30 21:28:35 +01:00 committed by Pascal Vizeli
parent 53cbb28926
commit df21dd21f2
30 changed files with 312 additions and 186 deletions

View file

@ -11,6 +11,7 @@ import voluptuous as vol
from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE from homeassistant.const import CONF_EXCLUDE, CONF_INCLUDE
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ServiceNotFound
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \
@ -314,8 +315,11 @@ class NotifySetupFlow(SetupFlow):
_generate_otp, self._secret, self._count) _generate_otp, self._secret, self._count)
assert self._notify_service assert self._notify_service
await self._auth_module.async_notify( try:
code, self._notify_service, self._target) await self._auth_module.async_notify(
code, self._notify_service, self._target)
except ServiceNotFound:
return self.async_abort(reason='notify_service_not_exist')
return self.async_show_form( return self.async_show_form(
step_id='setup', step_id='setup',

View file

@ -226,7 +226,11 @@ class LoginFlow(data_entry_flow.FlowHandler):
if user_input is None and hasattr(auth_module, if user_input is None and hasattr(auth_module,
'async_initialize_login_mfa_step'): 'async_initialize_login_mfa_step'):
await auth_module.async_initialize_login_mfa_step(self.user.id) try:
await auth_module.async_initialize_login_mfa_step(self.user.id)
except HomeAssistantError:
_LOGGER.exception('Error initializing MFA step')
return self.async_abort(reason='unknown_error')
if user_input is not None: if user_input is not None:
expires = self.created_at + MFA_SESSION_EXPIRATION expires = self.created_at + MFA_SESSION_EXPIRATION

View file

@ -9,7 +9,9 @@ import json
import logging import logging
from aiohttp import web from aiohttp import web
from aiohttp.web_exceptions import HTTPBadRequest
import async_timeout import async_timeout
import voluptuous as vol
from homeassistant.bootstrap import DATA_LOGGING from homeassistant.bootstrap import DATA_LOGGING
from homeassistant.components.http import HomeAssistantView from homeassistant.components.http import HomeAssistantView
@ -21,7 +23,8 @@ from homeassistant.const import (
URL_API_TEMPLATE, __version__) URL_API_TEMPLATE, __version__)
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.auth.permissions.const import POLICY_READ from homeassistant.auth.permissions.const import POLICY_READ
from homeassistant.exceptions import TemplateError, Unauthorized from homeassistant.exceptions import (
TemplateError, Unauthorized, ServiceNotFound)
from homeassistant.helpers import template from homeassistant.helpers import template
from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.service import async_get_all_descriptions
from homeassistant.helpers.state import AsyncTrackStates from homeassistant.helpers.state import AsyncTrackStates
@ -339,8 +342,11 @@ class APIDomainServicesView(HomeAssistantView):
"Data should be valid JSON.", HTTP_BAD_REQUEST) "Data should be valid JSON.", HTTP_BAD_REQUEST)
with AsyncTrackStates(hass) as changed_states: with AsyncTrackStates(hass) as changed_states:
await hass.services.async_call( try:
domain, service, data, True, self.context(request)) await hass.services.async_call(
domain, service, data, True, self.context(request))
except (vol.Invalid, ServiceNotFound):
raise HTTPBadRequest()
return self.json(changed_states) return self.json(changed_states)

View file

@ -9,7 +9,9 @@ import json
import logging import logging
from aiohttp import web from aiohttp import web
from aiohttp.web_exceptions import HTTPUnauthorized, HTTPInternalServerError from aiohttp.web_exceptions import (
HTTPUnauthorized, HTTPInternalServerError, HTTPBadRequest)
import voluptuous as vol
from homeassistant.components.http.ban import process_success_login from homeassistant.components.http.ban import process_success_login
from homeassistant.core import Context, is_callback from homeassistant.core import Context, is_callback
@ -114,6 +116,10 @@ def request_handler_factory(view, handler):
if asyncio.iscoroutine(result): if asyncio.iscoroutine(result):
result = await result result = await result
except vol.Invalid:
raise HTTPBadRequest()
except exceptions.ServiceNotFound:
raise HTTPInternalServerError()
except exceptions.Unauthorized: except exceptions.Unauthorized:
raise HTTPUnauthorized() raise HTTPUnauthorized()

View file

@ -13,7 +13,7 @@ from homeassistant.core import callback
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
valid_publish_topic, valid_subscribe_topic) valid_publish_topic, valid_subscribe_topic)
from homeassistant.const import ( from homeassistant.const import (
ATTR_SERVICE_DATA, EVENT_CALL_SERVICE, EVENT_SERVICE_EXECUTED, ATTR_SERVICE_DATA, EVENT_CALL_SERVICE,
EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL) EVENT_STATE_CHANGED, EVENT_TIME_CHANGED, MATCH_ALL)
from homeassistant.core import EventOrigin, State from homeassistant.core import EventOrigin, State
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -69,16 +69,6 @@ def async_setup(hass, config):
): ):
return return
# Filter out all the "event service executed" events because they
# are only used internally by core as callbacks for blocking
# during the interval while a service is being executed.
# They will serve no purpose to the external system,
# and thus are unnecessary traffic.
# And at any rate it would cause an infinite loop to publish them
# because publishing to an MQTT topic itself triggers one.
if event.event_type == EVENT_SERVICE_EXECUTED:
return
event_info = {'event_type': event.event_type, 'event_data': event.data} event_info = {'event_type': event.event_type, 'event_data': event.data}
msg = json.dumps(event_info, cls=JSONEncoder) msg = json.dumps(event_info, cls=JSONEncoder)
mqtt.async_publish(pub_topic, msg) mqtt.async_publish(pub_topic, msg)

View file

@ -300,14 +300,24 @@ class Recorder(threading.Thread):
time.sleep(CONNECT_RETRY_WAIT) time.sleep(CONNECT_RETRY_WAIT)
try: try:
with session_scope(session=self.get_session()) as session: with session_scope(session=self.get_session()) as session:
dbevent = Events.from_event(event) try:
session.add(dbevent) dbevent = Events.from_event(event)
session.flush() session.add(dbevent)
session.flush()
except (TypeError, ValueError):
_LOGGER.warning(
"Event is not JSON serializable: %s", event)
if event.event_type == EVENT_STATE_CHANGED: if event.event_type == EVENT_STATE_CHANGED:
dbstate = States.from_event(event) try:
dbstate.event_id = dbevent.event_id dbstate = States.from_event(event)
session.add(dbstate) dbstate.event_id = dbevent.event_id
session.add(dbstate)
except (TypeError, ValueError):
_LOGGER.warning(
"State is not JSON serializable: %s",
event.data.get('new_state'))
updated = True updated = True
except exc.OperationalError as err: except exc.OperationalError as err:

View file

@ -3,7 +3,7 @@ import voluptuous as vol
from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED
from homeassistant.core import callback, DOMAIN as HASS_DOMAIN from homeassistant.core import callback, DOMAIN as HASS_DOMAIN
from homeassistant.exceptions import Unauthorized from homeassistant.exceptions import Unauthorized, ServiceNotFound
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.service import async_get_all_descriptions from homeassistant.helpers.service import async_get_all_descriptions
@ -141,10 +141,15 @@ async def handle_call_service(hass, connection, msg):
if (msg['domain'] == HASS_DOMAIN and if (msg['domain'] == HASS_DOMAIN and
msg['service'] in ['restart', 'stop']): msg['service'] in ['restart', 'stop']):
blocking = False blocking = False
await hass.services.async_call(
msg['domain'], msg['service'], msg.get('service_data'), blocking, try:
connection.context(msg)) await hass.services.async_call(
connection.send_message(messages.result_message(msg['id'])) msg['domain'], msg['service'], msg.get('service_data'), blocking,
connection.context(msg))
connection.send_message(messages.result_message(msg['id']))
except ServiceNotFound:
connection.send_message(messages.error_message(
msg['id'], const.ERR_NOT_FOUND, 'Service not found.'))
@callback @callback

View file

@ -163,7 +163,6 @@ EVENT_HOMEASSISTANT_CLOSE = 'homeassistant_close'
EVENT_STATE_CHANGED = 'state_changed' EVENT_STATE_CHANGED = 'state_changed'
EVENT_TIME_CHANGED = 'time_changed' EVENT_TIME_CHANGED = 'time_changed'
EVENT_CALL_SERVICE = 'call_service' EVENT_CALL_SERVICE = 'call_service'
EVENT_SERVICE_EXECUTED = 'service_executed'
EVENT_PLATFORM_DISCOVERED = 'platform_discovered' EVENT_PLATFORM_DISCOVERED = 'platform_discovered'
EVENT_COMPONENT_LOADED = 'component_loaded' EVENT_COMPONENT_LOADED = 'component_loaded'
EVENT_SERVICE_REGISTERED = 'service_registered' EVENT_SERVICE_REGISTERED = 'service_registered'
@ -233,9 +232,6 @@ ATTR_ID = 'id'
# Name # Name
ATTR_NAME = 'name' ATTR_NAME = 'name'
# Data for a SERVICE_EXECUTED event
ATTR_SERVICE_CALL_ID = 'service_call_id'
# Contains one string or a list of strings, each being an entity id # Contains one string or a list of strings, each being an entity id
ATTR_ENTITY_ID = 'entity_id' ATTR_ENTITY_ID = 'entity_id'

View file

@ -25,18 +25,18 @@ from typing import ( # noqa: F401 pylint: disable=unused-import
from async_timeout import timeout from async_timeout import timeout
import attr import attr
import voluptuous as vol import voluptuous as vol
from voluptuous.humanize import humanize_error
from homeassistant.const import ( from homeassistant.const import (
ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE, ATTR_DOMAIN, ATTR_FRIENDLY_NAME, ATTR_NOW, ATTR_SERVICE,
ATTR_SERVICE_CALL_ID, ATTR_SERVICE_DATA, ATTR_SECONDS, EVENT_CALL_SERVICE, ATTR_SERVICE_DATA, ATTR_SECONDS, EVENT_CALL_SERVICE,
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED, EVENT_HOMEASSISTANT_CLOSE, EVENT_SERVICE_REMOVED,
EVENT_SERVICE_EXECUTED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED, EVENT_SERVICE_REGISTERED, EVENT_STATE_CHANGED,
EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__) EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, MATCH_ALL, __version__)
from homeassistant import loader from homeassistant import loader
from homeassistant.exceptions import ( from homeassistant.exceptions import (
HomeAssistantError, InvalidEntityFormatError, InvalidStateError) HomeAssistantError, InvalidEntityFormatError, InvalidStateError,
Unauthorized, ServiceNotFound)
from homeassistant.util.async_ import ( from homeassistant.util.async_ import (
run_coroutine_threadsafe, run_callback_threadsafe, run_coroutine_threadsafe, run_callback_threadsafe,
fire_coroutine_threadsafe) fire_coroutine_threadsafe)
@ -954,7 +954,6 @@ class ServiceRegistry:
"""Initialize a service registry.""" """Initialize a service registry."""
self._services = {} # type: Dict[str, Dict[str, Service]] self._services = {} # type: Dict[str, Dict[str, Service]]
self._hass = hass self._hass = hass
self._async_unsub_call_event = None # type: Optional[CALLBACK_TYPE]
@property @property
def services(self) -> Dict[str, Dict[str, Service]]: def services(self) -> Dict[str, Dict[str, Service]]:
@ -1010,10 +1009,6 @@ class ServiceRegistry:
else: else:
self._services[domain] = {service: service_obj} self._services[domain] = {service: service_obj}
if self._async_unsub_call_event is None:
self._async_unsub_call_event = self._hass.bus.async_listen(
EVENT_CALL_SERVICE, self._event_to_service_call)
self._hass.bus.async_fire( self._hass.bus.async_fire(
EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REGISTERED,
{ATTR_DOMAIN: domain, ATTR_SERVICE: service} {ATTR_DOMAIN: domain, ATTR_SERVICE: service}
@ -1092,100 +1087,61 @@ class ServiceRegistry:
This method is a coroutine. This method is a coroutine.
""" """
domain = domain.lower()
service = service.lower()
context = context or Context() context = context or Context()
call_id = uuid.uuid4().hex service_data = service_data or {}
event_data = {
try:
handler = self._services[domain][service]
except KeyError:
raise ServiceNotFound(domain, service) from None
if handler.schema:
service_data = handler.schema(service_data)
service_call = ServiceCall(domain, service, service_data, context)
self._hass.bus.async_fire(EVENT_CALL_SERVICE, {
ATTR_DOMAIN: domain.lower(), ATTR_DOMAIN: domain.lower(),
ATTR_SERVICE: service.lower(), ATTR_SERVICE: service.lower(),
ATTR_SERVICE_DATA: service_data, ATTR_SERVICE_DATA: service_data,
ATTR_SERVICE_CALL_ID: call_id, })
}
if not blocking: if not blocking:
self._hass.bus.async_fire( self._hass.async_create_task(
EVENT_CALL_SERVICE, event_data, EventOrigin.local, context) self._safe_execute(handler, service_call))
return None return None
fut = asyncio.Future() # type: asyncio.Future
@callback
def service_executed(event: Event) -> None:
"""Handle an executed service."""
if event.data[ATTR_SERVICE_CALL_ID] == call_id:
fut.set_result(True)
unsub()
unsub = self._hass.bus.async_listen(
EVENT_SERVICE_EXECUTED, service_executed)
self._hass.bus.async_fire(EVENT_CALL_SERVICE, event_data,
EventOrigin.local, context)
done, _ = await asyncio.wait([fut], timeout=SERVICE_CALL_LIMIT)
success = bool(done)
if not success:
unsub()
return success
async def _event_to_service_call(self, event: Event) -> None:
"""Handle the SERVICE_CALLED events from the EventBus."""
service_data = event.data.get(ATTR_SERVICE_DATA) or {}
domain = event.data.get(ATTR_DOMAIN).lower() # type: ignore
service = event.data.get(ATTR_SERVICE).lower() # type: ignore
call_id = event.data.get(ATTR_SERVICE_CALL_ID)
if not self.has_service(domain, service):
if event.origin == EventOrigin.local:
_LOGGER.warning("Unable to find service %s/%s",
domain, service)
return
service_handler = self._services[domain][service]
def fire_service_executed() -> None:
"""Fire service executed event."""
if not call_id:
return
data = {ATTR_SERVICE_CALL_ID: call_id}
if (service_handler.is_coroutinefunction or
service_handler.is_callback):
self._hass.bus.async_fire(EVENT_SERVICE_EXECUTED, data,
EventOrigin.local, event.context)
else:
self._hass.bus.fire(EVENT_SERVICE_EXECUTED, data,
EventOrigin.local, event.context)
try: try:
if service_handler.schema: with timeout(SERVICE_CALL_LIMIT):
service_data = service_handler.schema(service_data) await asyncio.shield(
except vol.Invalid as ex: self._execute_service(handler, service_call))
_LOGGER.error("Invalid service data for %s.%s: %s", return True
domain, service, humanize_error(service_data, ex)) except asyncio.TimeoutError:
fire_service_executed() return False
return
service_call = ServiceCall(
domain, service, service_data, event.context)
async def _safe_execute(self, handler: Service,
service_call: ServiceCall) -> None:
"""Execute a service and catch exceptions."""
try: try:
if service_handler.is_callback: await self._execute_service(handler, service_call)
service_handler.func(service_call) except Unauthorized:
fire_service_executed() _LOGGER.warning('Unauthorized service called %s/%s',
elif service_handler.is_coroutinefunction: service_call.domain, service_call.service)
await service_handler.func(service_call)
fire_service_executed()
else:
def execute_service() -> None:
"""Execute a service and fires a SERVICE_EXECUTED event."""
service_handler.func(service_call)
fire_service_executed()
await self._hass.async_add_executor_job(execute_service)
except Exception: # pylint: disable=broad-except except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error executing service %s', service_call) _LOGGER.exception('Error executing service %s', service_call)
async def _execute_service(self, handler: Service,
service_call: ServiceCall) -> None:
"""Execute a service."""
if handler.is_callback:
handler.func(service_call)
elif handler.is_coroutinefunction:
await handler.func(service_call)
else:
await self._hass.async_add_executor_job(handler.func, service_call)
class Config: class Config:
"""Configuration settings for Home Assistant.""" """Configuration settings for Home Assistant."""

View file

@ -58,3 +58,14 @@ class Unauthorized(HomeAssistantError):
class UnknownUser(Unauthorized): class UnknownUser(Unauthorized):
"""When call is made with user ID that doesn't exist.""" """When call is made with user ID that doesn't exist."""
class ServiceNotFound(HomeAssistantError):
"""Raised when a service is not found."""
def __init__(self, domain: str, service: str) -> None:
"""Initialize error."""
super().__init__(
self, "Service {}.{} not found".format(domain, service))
self.domain = domain
self.service = service

View file

@ -61,6 +61,7 @@ async def test_validating_mfa_counter(hass):
'counter': 0, 'counter': 0,
'notify_service': 'dummy', 'notify_service': 'dummy',
}) })
async_mock_service(hass, 'notify', 'dummy')
assert notify_auth_module._user_settings assert notify_auth_module._user_settings
notify_setting = list(notify_auth_module._user_settings.values())[0] notify_setting = list(notify_auth_module._user_settings.values())[0]
@ -389,9 +390,8 @@ async def test_not_raise_exception_when_service_not_exist(hass):
'username': 'test-user', 'username': 'test-user',
'password': 'test-pass', 'password': 'test-pass',
}) })
assert result['type'] == data_entry_flow.RESULT_TYPE_FORM assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
assert result['step_id'] == 'mfa' assert result['reason'] == 'unknown_error'
assert result['data_schema'].schema.get('code') == str
# wait service call finished # wait service call finished
await hass.async_block_till_done() await hass.async_block_till_done()

View file

@ -1,6 +1,9 @@
"""The tests for the demo climate component.""" """The tests for the demo climate component."""
import unittest import unittest
import pytest
import voluptuous as vol
from homeassistant.util.unit_system import ( from homeassistant.util.unit_system import (
METRIC_SYSTEM METRIC_SYSTEM
) )
@ -57,7 +60,8 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the target temperature without required attribute.""" """Test setting the target temperature without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert 21 == state.attributes.get('temperature') assert 21 == state.attributes.get('temperature')
common.set_temperature(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_temperature(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
assert 21 == state.attributes.get('temperature') assert 21 == state.attributes.get('temperature')
@ -99,9 +103,11 @@ class TestDemoClimate(unittest.TestCase):
assert state.attributes.get('temperature') is None assert state.attributes.get('temperature') is None
assert 21.0 == state.attributes.get('target_temp_low') assert 21.0 == state.attributes.get('target_temp_low')
assert 24.0 == state.attributes.get('target_temp_high') assert 24.0 == state.attributes.get('target_temp_high')
common.set_temperature(self.hass, temperature=None, with pytest.raises(vol.Invalid):
entity_id=ENTITY_ECOBEE, target_temp_low=None, common.set_temperature(self.hass, temperature=None,
target_temp_high=None) entity_id=ENTITY_ECOBEE,
target_temp_low=None,
target_temp_high=None)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE) state = self.hass.states.get(ENTITY_ECOBEE)
assert state.attributes.get('temperature') is None assert state.attributes.get('temperature') is None
@ -112,7 +118,8 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the target humidity without required attribute.""" """Test setting the target humidity without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get('humidity') assert 67 == state.attributes.get('humidity')
common.set_humidity(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_humidity(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert 67 == state.attributes.get('humidity') assert 67 == state.attributes.get('humidity')
@ -130,7 +137,8 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting fan mode without required attribute.""" """Test setting fan mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get('fan_mode') assert "On High" == state.attributes.get('fan_mode')
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "On High" == state.attributes.get('fan_mode') assert "On High" == state.attributes.get('fan_mode')
@ -148,7 +156,8 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting swing mode without required attribute.""" """Test setting swing mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get('swing_mode') assert "Off" == state.attributes.get('swing_mode')
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "Off" == state.attributes.get('swing_mode') assert "Off" == state.attributes.get('swing_mode')
@ -170,7 +179,8 @@ class TestDemoClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "cool" == state.attributes.get('operation_mode') assert "cool" == state.attributes.get('operation_mode')
assert "cool" == state.state assert "cool" == state.state
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "cool" == state.attributes.get('operation_mode') assert "cool" == state.attributes.get('operation_mode')

View file

@ -1,6 +1,9 @@
"""The tests for the climate component.""" """The tests for the climate component."""
import asyncio import asyncio
import pytest
import voluptuous as vol
from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA from homeassistant.components.climate import SET_TEMPERATURE_SCHEMA
from tests.common import async_mock_service from tests.common import async_mock_service
@ -14,12 +17,11 @@ def test_set_temp_schema_no_req(hass, caplog):
calls = async_mock_service(hass, domain, service, schema) calls = async_mock_service(hass, domain, service, schema)
data = {'operation_mode': 'test', 'entity_id': ['climate.test_id']} data = {'operation_mode': 'test', 'entity_id': ['climate.test_id']}
yield from hass.services.async_call(domain, service, data) with pytest.raises(vol.Invalid):
yield from hass.services.async_call(domain, service, data)
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
assert len(calls) == 0 assert len(calls) == 0
assert 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text
@asyncio.coroutine @asyncio.coroutine

View file

@ -2,6 +2,9 @@
import unittest import unittest
import copy import copy
import pytest
import voluptuous as vol
from homeassistant.util.unit_system import ( from homeassistant.util.unit_system import (
METRIC_SYSTEM METRIC_SYSTEM
) )
@ -91,7 +94,8 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "off" == state.attributes.get('operation_mode') assert "off" == state.attributes.get('operation_mode')
assert "off" == state.state assert "off" == state.state
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "off" == state.attributes.get('operation_mode') assert "off" == state.attributes.get('operation_mode')
@ -177,7 +181,8 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "low" == state.attributes.get('fan_mode') assert "low" == state.attributes.get('fan_mode')
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "low" == state.attributes.get('fan_mode') assert "low" == state.attributes.get('fan_mode')
@ -225,7 +230,8 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "off" == state.attributes.get('swing_mode') assert "off" == state.attributes.get('swing_mode')
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE) with pytest.raises(vol.Invalid):
common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE) state = self.hass.states.get(ENTITY_CLIMATE)
assert "off" == state.attributes.get('swing_mode') assert "off" == state.attributes.get('swing_mode')

View file

@ -1,6 +1,9 @@
"""Test deCONZ component setup process.""" """Test deCONZ component setup process."""
from unittest.mock import Mock, patch from unittest.mock import Mock, patch
import pytest
import voluptuous as vol
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components import deconz from homeassistant.components import deconz
@ -163,11 +166,13 @@ async def test_service_configure(hass):
await hass.async_block_till_done() await hass.async_block_till_done()
# field does not start with / # field does not start with /
with patch('pydeconz.DeconzSession.async_put_state', with pytest.raises(vol.Invalid):
return_value=mock_coro(True)): with patch('pydeconz.DeconzSession.async_put_state',
await hass.services.async_call('deconz', 'configure', service_data={ return_value=mock_coro(True)):
'entity': 'light.test', 'field': 'state', 'data': data}) await hass.services.async_call(
await hass.async_block_till_done() 'deconz', 'configure', service_data={
'entity': 'light.test', 'field': 'state', 'data': data})
await hass.async_block_till_done()
async def test_service_refresh_devices(hass): async def test_service_refresh_devices(hass):

View file

@ -1,8 +1,25 @@
"""Tests for Home Assistant View.""" """Tests for Home Assistant View."""
from aiohttp.web_exceptions import HTTPInternalServerError from unittest.mock import Mock
import pytest
from homeassistant.components.http.view import HomeAssistantView from aiohttp.web_exceptions import (
HTTPInternalServerError, HTTPBadRequest, HTTPUnauthorized)
import pytest
import voluptuous as vol
from homeassistant.components.http.view import (
HomeAssistantView, request_handler_factory)
from homeassistant.exceptions import ServiceNotFound, Unauthorized
from tests.common import mock_coro_func
@pytest.fixture
def mock_request():
"""Mock a request."""
return Mock(
app={'hass': Mock(is_running=True)},
match_info={},
)
async def test_invalid_json(caplog): async def test_invalid_json(caplog):
@ -13,3 +30,30 @@ async def test_invalid_json(caplog):
view.json(float("NaN")) view.json(float("NaN"))
assert str(float("NaN")) in caplog.text assert str(float("NaN")) in caplog.text
async def test_handling_unauthorized(mock_request):
"""Test handling unauth exceptions."""
with pytest.raises(HTTPUnauthorized):
await request_handler_factory(
Mock(requires_auth=False),
mock_coro_func(exception=Unauthorized)
)(mock_request)
async def test_handling_invalid_data(mock_request):
"""Test handling unauth exceptions."""
with pytest.raises(HTTPBadRequest):
await request_handler_factory(
Mock(requires_auth=False),
mock_coro_func(exception=vol.Invalid('yo'))
)(mock_request)
async def test_handling_service_not_found(mock_request):
"""Test handling unauth exceptions."""
with pytest.raises(HTTPInternalServerError):
await request_handler_factory(
Mock(requires_auth=False),
mock_coro_func(exception=ServiceNotFound('test', 'test'))
)(mock_request)

View file

@ -3,6 +3,9 @@ import unittest
from unittest.mock import patch from unittest.mock import patch
import asyncio import asyncio
import pytest
import voluptuous as vol
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
from homeassistant.const import HTTP_HEADER_HA_AUTH from homeassistant.const import HTTP_HEADER_HA_AUTH
import homeassistant.components.media_player as mp import homeassistant.components.media_player as mp
@ -43,7 +46,8 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source') assert 'dvd' == state.attributes.get('source')
common.select_source(self.hass, None, entity_id) with pytest.raises(vol.Invalid):
common.select_source(self.hass, None, entity_id)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source') assert 'dvd' == state.attributes.get('source')
@ -72,7 +76,8 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level') assert 1.0 == state.attributes.get('volume_level')
common.set_volume_level(self.hass, None, entity_id) with pytest.raises(vol.Invalid):
common.set_volume_level(self.hass, None, entity_id)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(entity_id) state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level') assert 1.0 == state.attributes.get('volume_level')
@ -201,7 +206,8 @@ class TestDemoMediaPlayer(unittest.TestCase):
state.attributes.get('supported_features')) state.attributes.get('supported_features'))
assert state.attributes.get('media_content_id') is not None assert state.attributes.get('media_content_id') is not None
common.play_media(self.hass, None, 'some_id', ent_id) with pytest.raises(vol.Invalid):
common.play_media(self.hass, None, 'some_id', ent_id)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ent_id) state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA & assert 0 < (mp.SUPPORT_PLAY_MEDIA &
@ -216,7 +222,8 @@ class TestDemoMediaPlayer(unittest.TestCase):
assert 'some_id' == state.attributes.get('media_content_id') assert 'some_id' == state.attributes.get('media_content_id')
assert not mock_seek.called assert not mock_seek.called
common.media_seek(self.hass, None, ent_id) with pytest.raises(vol.Invalid):
common.media_seek(self.hass, None, ent_id)
self.hass.block_till_done() self.hass.block_till_done()
assert not mock_seek.called assert not mock_seek.called
common.media_seek(self.hass, 100, ent_id) common.media_seek(self.hass, 100, ent_id)

View file

@ -223,7 +223,7 @@ class TestMonopriceMediaPlayer(unittest.TestCase):
# Restoring wrong media player to its previous state # Restoring wrong media player to its previous state
# Nothing should be done # Nothing should be done
self.hass.services.call(DOMAIN, SERVICE_RESTORE, self.hass.services.call(DOMAIN, SERVICE_RESTORE,
{'entity_id': 'not_existing'}, {'entity_id': 'media.not_existing'},
blocking=True) blocking=True)
# self.hass.block_till_done() # self.hass.block_till_done()

View file

@ -113,11 +113,12 @@ class TestMQTTComponent(unittest.TestCase):
""" """
payload = "not a template" payload = "not a template"
payload_template = "a template" payload_template = "a template"
self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, { with pytest.raises(vol.Invalid):
mqtt.ATTR_TOPIC: "test/topic", self.hass.services.call(mqtt.DOMAIN, mqtt.SERVICE_PUBLISH, {
mqtt.ATTR_PAYLOAD: payload, mqtt.ATTR_TOPIC: "test/topic",
mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template mqtt.ATTR_PAYLOAD: payload,
}, blocking=True) mqtt.ATTR_PAYLOAD_TEMPLATE: payload_template
}, blocking=True)
assert not self.hass.data['mqtt'].async_publish.called assert not self.hass.data['mqtt'].async_publish.called
def test_service_call_with_ascii_qos_retain_flags(self): def test_service_call_with_ascii_qos_retain_flags(self):

View file

@ -2,6 +2,9 @@
import unittest import unittest
from unittest.mock import patch from unittest.mock import patch
import pytest
import voluptuous as vol
import homeassistant.components.notify as notify import homeassistant.components.notify as notify
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
from homeassistant.components.notify import demo from homeassistant.components.notify import demo
@ -81,7 +84,8 @@ class TestNotifyDemo(unittest.TestCase):
def test_sending_none_message(self): def test_sending_none_message(self):
"""Test send with None as message.""" """Test send with None as message."""
self._setup_notify() self._setup_notify()
common.send_message(self.hass, None) with pytest.raises(vol.Invalid):
common.send_message(self.hass, None)
self.hass.block_till_done() self.hass.block_till_done()
assert len(self.events) == 0 assert len(self.events) == 0

View file

@ -99,6 +99,7 @@ class TestAlert(unittest.TestCase):
def setUp(self): def setUp(self):
"""Set up things to be run when tests are started.""" """Set up things to be run when tests are started."""
self.hass = get_test_home_assistant() self.hass = get_test_home_assistant()
self._setup_notify()
def tearDown(self): def tearDown(self):
"""Stop everything that was started.""" """Stop everything that was started."""

View file

@ -6,6 +6,7 @@ from unittest.mock import patch
from aiohttp import web from aiohttp import web
import pytest import pytest
import voluptuous as vol
from homeassistant import const from homeassistant import const
from homeassistant.bootstrap import DATA_LOGGING from homeassistant.bootstrap import DATA_LOGGING
@ -578,3 +579,29 @@ async def test_rendering_template_legacy_user(
json={"template": '{{ states.sensor.temperature.state }}'} json={"template": '{{ states.sensor.temperature.state }}'}
) )
assert resp.status == 401 assert resp.status == 401
async def test_api_call_service_not_found(hass, mock_api_client):
"""Test if the API failes 400 if unknown service."""
resp = await mock_api_client.post(
const.URL_API_SERVICES_SERVICE.format(
"test_domain", "test_service"))
assert resp.status == 400
async def test_api_call_service_bad_data(hass, mock_api_client):
"""Test if the API failes 400 if unknown service."""
test_value = []
@ha.callback
def listener(service_call):
"""Record that our service got called."""
test_value.append(1)
hass.services.async_register("test_domain", "test_service", listener,
schema=vol.Schema({'hello': str}))
resp = await mock_api_client.post(
const.URL_API_SERVICES_SERVICE.format(
"test_domain", "test_service"), json={'hello': 5})
assert resp.status == 400

View file

@ -3,6 +3,9 @@
import asyncio import asyncio
import datetime import datetime
import pytest
import voluptuous as vol
from homeassistant.core import CoreState, State, Context from homeassistant.core import CoreState, State, Context
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components.input_datetime import ( from homeassistant.components.input_datetime import (
@ -109,10 +112,11 @@ def test_set_invalid(hass):
dt_obj = datetime.datetime(2017, 9, 7, 19, 46) dt_obj = datetime.datetime(2017, 9, 7, 19, 46)
time_portion = dt_obj.time() time_portion = dt_obj.time()
yield from hass.services.async_call('input_datetime', 'set_datetime', { with pytest.raises(vol.Invalid):
'entity_id': 'test_date', yield from hass.services.async_call('input_datetime', 'set_datetime', {
'time': time_portion 'entity_id': 'test_date',
}) 'time': time_portion
})
yield from hass.async_block_till_done() yield from hass.async_block_till_done()
state = hass.states.get(entity_id) state = hass.states.get(entity_id)

View file

@ -4,6 +4,9 @@ import logging
from datetime import (timedelta, datetime) from datetime import (timedelta, datetime)
import unittest import unittest
import pytest
import voluptuous as vol
from homeassistant.components import sun from homeassistant.components import sun
import homeassistant.core as ha import homeassistant.core as ha
from homeassistant.const import ( from homeassistant.const import (
@ -89,7 +92,9 @@ class TestComponentLogbook(unittest.TestCase):
calls.append(event) calls.append(event)
self.hass.bus.listen(logbook.EVENT_LOGBOOK_ENTRY, event_listener) self.hass.bus.listen(logbook.EVENT_LOGBOOK_ENTRY, event_listener)
self.hass.services.call(logbook.DOMAIN, 'log', {}, True)
with pytest.raises(vol.Invalid):
self.hass.services.call(logbook.DOMAIN, 'log', {}, True)
# Logbook entry service call results in firing an event. # Logbook entry service call results in firing an event.
# Our service call will unblock when the event listeners have been # Our service call will unblock when the event listeners have been

View file

@ -2,6 +2,9 @@
import json import json
import logging import logging
import pytest
import voluptuous as vol
from homeassistant.bootstrap import async_setup_component from homeassistant.bootstrap import async_setup_component
from homeassistant.components.mqtt import MQTT_PUBLISH_SCHEMA from homeassistant.components.mqtt import MQTT_PUBLISH_SCHEMA
import homeassistant.components.snips as snips import homeassistant.components.snips as snips
@ -452,12 +455,11 @@ async def test_snips_say_invalid_config(hass, caplog):
snips.SERVICE_SCHEMA_SAY) snips.SERVICE_SCHEMA_SAY)
data = {'text': 'Hello', 'badKey': 'boo'} data = {'text': 'Hello', 'badKey': 'boo'}
await hass.services.async_call('snips', 'say', data) with pytest.raises(vol.Invalid):
await hass.services.async_call('snips', 'say', data)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls) == 0 assert len(calls) == 0
assert 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text
async def test_snips_say_action_invalid(hass, caplog): async def test_snips_say_action_invalid(hass, caplog):
@ -466,12 +468,12 @@ async def test_snips_say_action_invalid(hass, caplog):
snips.SERVICE_SCHEMA_SAY_ACTION) snips.SERVICE_SCHEMA_SAY_ACTION)
data = {'text': 'Hello', 'can_be_enqueued': 'notabool'} data = {'text': 'Hello', 'can_be_enqueued': 'notabool'}
await hass.services.async_call('snips', 'say_action', data)
with pytest.raises(vol.Invalid):
await hass.services.async_call('snips', 'say_action', data)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls) == 0 assert len(calls) == 0
assert 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text
async def test_snips_feedback_on(hass, caplog): async def test_snips_feedback_on(hass, caplog):
@ -510,7 +512,8 @@ async def test_snips_feedback_config(hass, caplog):
snips.SERVICE_SCHEMA_FEEDBACK) snips.SERVICE_SCHEMA_FEEDBACK)
data = {'site_id': 'remote', 'test': 'test'} data = {'site_id': 'remote', 'test': 'test'}
await hass.services.async_call('snips', 'feedback_on', data) with pytest.raises(vol.Invalid):
await hass.services.async_call('snips', 'feedback_on', data)
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls) == 0 assert len(calls) == 0

View file

@ -3,6 +3,7 @@ import asyncio
from unittest import mock from unittest import mock
import pytest import pytest
import voluptuous as vol
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
from homeassistant.components.wake_on_lan import ( from homeassistant.components.wake_on_lan import (
@ -34,10 +35,10 @@ def test_send_magic_packet(hass, caplog, mock_wakeonlan):
assert mock_wakeonlan.mock_calls[-1][1][0] == mac assert mock_wakeonlan.mock_calls[-1][1][0] == mac
assert mock_wakeonlan.mock_calls[-1][2]['ip_address'] == bc_ip assert mock_wakeonlan.mock_calls[-1][2]['ip_address'] == bc_ip
yield from hass.services.async_call( with pytest.raises(vol.Invalid):
DOMAIN, SERVICE_SEND_MAGIC_PACKET, yield from hass.services.async_call(
{"broadcast_address": bc_ip}, blocking=True) DOMAIN, SERVICE_SEND_MAGIC_PACKET,
assert 'ERROR' in caplog.text {"broadcast_address": bc_ip}, blocking=True)
assert len(mock_wakeonlan.mock_calls) == 1 assert len(mock_wakeonlan.mock_calls) == 1
yield from hass.services.async_call( yield from hass.services.async_call(

View file

@ -1,6 +1,9 @@
"""The tests for the demo water_heater component.""" """The tests for the demo water_heater component."""
import unittest import unittest
import pytest
import voluptuous as vol
from homeassistant.util.unit_system import ( from homeassistant.util.unit_system import (
IMPERIAL_SYSTEM IMPERIAL_SYSTEM
) )
@ -48,7 +51,8 @@ class TestDemowater_heater(unittest.TestCase):
"""Test setting the target temperature without required attribute.""" """Test setting the target temperature without required attribute."""
state = self.hass.states.get(ENTITY_WATER_HEATER) state = self.hass.states.get(ENTITY_WATER_HEATER)
assert 119 == state.attributes.get('temperature') assert 119 == state.attributes.get('temperature')
common.set_temperature(self.hass, None, ENTITY_WATER_HEATER) with pytest.raises(vol.Invalid):
common.set_temperature(self.hass, None, ENTITY_WATER_HEATER)
self.hass.block_till_done() self.hass.block_till_done()
assert 119 == state.attributes.get('temperature') assert 119 == state.attributes.get('temperature')
@ -69,7 +73,8 @@ class TestDemowater_heater(unittest.TestCase):
state = self.hass.states.get(ENTITY_WATER_HEATER) state = self.hass.states.get(ENTITY_WATER_HEATER)
assert "eco" == state.attributes.get('operation_mode') assert "eco" == state.attributes.get('operation_mode')
assert "eco" == state.state assert "eco" == state.state
common.set_operation_mode(self.hass, None, ENTITY_WATER_HEATER) with pytest.raises(vol.Invalid):
common.set_operation_mode(self.hass, None, ENTITY_WATER_HEATER)
self.hass.block_till_done() self.hass.block_till_done()
state = self.hass.states.get(ENTITY_WATER_HEATER) state = self.hass.states.get(ENTITY_WATER_HEATER)
assert "eco" == state.attributes.get('operation_mode') assert "eco" == state.attributes.get('operation_mode')

View file

@ -49,6 +49,25 @@ async def test_call_service(hass, websocket_client):
assert call.data == {'hello': 'world'} assert call.data == {'hello': 'world'}
async def test_call_service_not_found(hass, websocket_client):
"""Test call service command."""
await websocket_client.send_json({
'id': 5,
'type': commands.TYPE_CALL_SERVICE,
'domain': 'domain_test',
'service': 'test_service',
'service_data': {
'hello': 'world'
}
})
msg = await websocket_client.receive_json()
assert msg['id'] == 5
assert msg['type'] == const.TYPE_RESULT
assert not msg['success']
assert msg['error']['code'] == const.ERR_NOT_FOUND
async def test_subscribe_unsubscribe_events(hass, websocket_client): async def test_subscribe_unsubscribe_events(hass, websocket_client):
"""Test subscribe/unsubscribe events command.""" """Test subscribe/unsubscribe events command."""
init_count = sum(hass.bus.async_listeners().values()) init_count = sum(hass.bus.async_listeners().values())

View file

@ -947,7 +947,7 @@ class TestZWaveServices(unittest.TestCase):
assert self.zwave_network.stop.called assert self.zwave_network.stop.called
assert len(self.zwave_network.stop.mock_calls) == 1 assert len(self.zwave_network.stop.mock_calls) == 1
assert mock_fire.called assert mock_fire.called
assert len(mock_fire.mock_calls) == 2 assert len(mock_fire.mock_calls) == 1
assert mock_fire.mock_calls[0][1][0] == const.EVENT_NETWORK_STOP assert mock_fire.mock_calls[0][1][0] == const.EVENT_NETWORK_STOP
def test_rename_node(self): def test_rename_node(self):

View file

@ -21,7 +21,7 @@ from homeassistant.const import (
__version__, EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, CONF_UNIT_SYSTEM, __version__, EVENT_STATE_CHANGED, ATTR_FRIENDLY_NAME, CONF_UNIT_SYSTEM,
ATTR_NOW, EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, ATTR_SECONDS, ATTR_NOW, EVENT_TIME_CHANGED, EVENT_TIMER_OUT_OF_SYNC, ATTR_SECONDS,
EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_CLOSE, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_CLOSE,
EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED, EVENT_SERVICE_EXECUTED) EVENT_SERVICE_REGISTERED, EVENT_SERVICE_REMOVED)
from tests.common import get_test_home_assistant, async_mock_service from tests.common import get_test_home_assistant, async_mock_service
@ -673,13 +673,8 @@ class TestServiceRegistry(unittest.TestCase):
def test_call_non_existing_with_blocking(self): def test_call_non_existing_with_blocking(self):
"""Test non-existing with blocking.""" """Test non-existing with blocking."""
prior = ha.SERVICE_CALL_LIMIT with pytest.raises(ha.ServiceNotFound):
try: self.services.call('test_domain', 'i_do_not_exist', blocking=True)
ha.SERVICE_CALL_LIMIT = 0.01
assert not self.services.call('test_domain', 'i_do_not_exist',
blocking=True)
finally:
ha.SERVICE_CALL_LIMIT = prior
def test_async_service(self): def test_async_service(self):
"""Test registering and calling an async service.""" """Test registering and calling an async service."""
@ -1005,4 +1000,3 @@ async def test_service_executed_with_subservices(hass):
assert len(calls) == 4 assert len(calls) == 4
assert [call.service for call in calls] == [ assert [call.service for call in calls] == [
'outer', 'inner', 'inner', 'outer'] 'outer', 'inner', 'inner', 'outer']
assert len(hass.bus.async_listeners().get(EVENT_SERVICE_EXECUTED, [])) == 0