Snips sounds (#13746)

* Added feedback sound configuration

* Added feedback sound configuration

* Cleaned up feedback off

* Cleaned up whitespace

* Moved feedback pus to helper funx

* Async

* Used async_mock_service for tests

* Lint
This commit is contained in:
Tod Schmidt 2018-04-09 11:46:27 -04:00 committed by Paulus Schoutsen
parent c61611d2b4
commit e593117ab6
2 changed files with 256 additions and 81 deletions

View File

@ -4,13 +4,13 @@ Support for Snips on-device ASR and NLU.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/snips/ https://home-assistant.io/components/snips/
""" """
import asyncio
import json import json
import logging import logging
from datetime import timedelta from datetime import timedelta
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers import intent, config_validation as cv from homeassistant.helpers import intent, config_validation as cv
import homeassistant.components.mqtt as mqtt import homeassistant.components.mqtt as mqtt
@ -19,11 +19,18 @@ DEPENDENCIES = ['mqtt']
CONF_INTENTS = 'intents' CONF_INTENTS = 'intents'
CONF_ACTION = 'action' CONF_ACTION = 'action'
CONF_FEEDBACK = 'feedback_sounds'
CONF_PROBABILITY = 'probability_threshold'
CONF_SITE_IDS = 'site_ids'
SERVICE_SAY = 'say' SERVICE_SAY = 'say'
SERVICE_SAY_ACTION = 'say_action' SERVICE_SAY_ACTION = 'say_action'
SERVICE_FEEDBACK_ON = 'feedback_on'
SERVICE_FEEDBACK_OFF = 'feedback_off'
INTENT_TOPIC = 'hermes/intent/#' INTENT_TOPIC = 'hermes/intent/#'
FEEDBACK_ON_TOPIC = 'hermes/feedback/sound/toggleOn'
FEEDBACK_OFF_TOPIC = 'hermes/feedback/sound/toggleOff'
ATTR_TEXT = 'text' ATTR_TEXT = 'text'
ATTR_SITE_ID = 'site_id' ATTR_SITE_ID = 'site_id'
@ -34,7 +41,12 @@ ATTR_INTENT_FILTER = 'intent_filter'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: {} DOMAIN: vol.Schema({
vol.Optional(CONF_FEEDBACK): cv.boolean,
vol.Optional(CONF_PROBABILITY, default=0): vol.Coerce(float),
vol.Optional(CONF_SITE_IDS, default=['default']):
vol.All(cv.ensure_list, [cv.string]),
}),
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
INTENT_SCHEMA = vol.Schema({ INTENT_SCHEMA = vol.Schema({
@ -57,7 +69,6 @@ SERVICE_SCHEMA_SAY = vol.Schema({
vol.Optional(ATTR_SITE_ID, default='default'): str, vol.Optional(ATTR_SITE_ID, default='default'): str,
vol.Optional(ATTR_CUSTOM_DATA, default=''): str vol.Optional(ATTR_CUSTOM_DATA, default=''): str
}) })
SERVICE_SCHEMA_SAY_ACTION = vol.Schema({ SERVICE_SCHEMA_SAY_ACTION = vol.Schema({
vol.Required(ATTR_TEXT): str, vol.Required(ATTR_TEXT): str,
vol.Optional(ATTR_SITE_ID, default='default'): str, vol.Optional(ATTR_SITE_ID, default='default'): str,
@ -65,13 +76,31 @@ SERVICE_SCHEMA_SAY_ACTION = vol.Schema({
vol.Optional(ATTR_CAN_BE_ENQUEUED, default=True): cv.boolean, vol.Optional(ATTR_CAN_BE_ENQUEUED, default=True): cv.boolean,
vol.Optional(ATTR_INTENT_FILTER): vol.All(cv.ensure_list), vol.Optional(ATTR_INTENT_FILTER): vol.All(cv.ensure_list),
}) })
SERVICE_SCHEMA_FEEDBACK = vol.Schema({
vol.Optional(ATTR_SITE_ID, default='default'): str
})
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Activate Snips component.""" """Activate Snips component."""
@asyncio.coroutine @callback
def message_received(topic, payload, qos): def async_set_feedback(site_ids, state):
"""Set Feedback sound state."""
site_ids = (site_ids if site_ids
else config[DOMAIN].get(CONF_SITE_IDS))
topic = (FEEDBACK_ON_TOPIC if state
else FEEDBACK_OFF_TOPIC)
for site_id in site_ids:
payload = json.dumps({'siteId': site_id})
hass.components.mqtt.async_publish(
FEEDBACK_ON_TOPIC, None, qos=0, retain=False)
hass.components.mqtt.async_publish(
topic, payload, qos=int(state), retain=state)
if CONF_FEEDBACK in config[DOMAIN]:
async_set_feedback(None, config[DOMAIN][CONF_FEEDBACK])
async def message_received(topic, payload, qos):
"""Handle new messages on MQTT.""" """Handle new messages on MQTT."""
_LOGGER.debug("New intent: %s", payload) _LOGGER.debug("New intent: %s", payload)
@ -81,6 +110,13 @@ def async_setup(hass, config):
_LOGGER.error('Received invalid JSON: %s', payload) _LOGGER.error('Received invalid JSON: %s', payload)
return return
if (request['intent']['probability']
< config[DOMAIN].get(CONF_PROBABILITY)):
_LOGGER.warning("Intent below probaility threshold %s < %s",
request['intent']['probability'],
config[DOMAIN].get(CONF_PROBABILITY))
return
try: try:
request = INTENT_SCHEMA(request) request = INTENT_SCHEMA(request)
except vol.Invalid as err: except vol.Invalid as err:
@ -97,7 +133,7 @@ def async_setup(hass, config):
slots[slot['slotName']] = {'value': resolve_slot_values(slot)} slots[slot['slotName']] = {'value': resolve_slot_values(slot)}
try: try:
intent_response = yield from intent.async_handle( intent_response = await intent.async_handle(
hass, DOMAIN, intent_type, slots, request['input']) hass, DOMAIN, intent_type, slots, request['input'])
if 'plain' in intent_response.speech: if 'plain' in intent_response.speech:
snips_response = intent_response.speech['plain']['speech'] snips_response = intent_response.speech['plain']['speech']
@ -115,11 +151,10 @@ def async_setup(hass, config):
mqtt.async_publish(hass, 'hermes/dialogueManager/endSession', mqtt.async_publish(hass, 'hermes/dialogueManager/endSession',
json.dumps(notification)) json.dumps(notification))
yield from hass.components.mqtt.async_subscribe( await hass.components.mqtt.async_subscribe(
INTENT_TOPIC, message_received) INTENT_TOPIC, message_received)
@asyncio.coroutine async def snips_say(call):
def snips_say(call):
"""Send a Snips notification message.""" """Send a Snips notification message."""
notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'), notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'),
'customData': call.data.get(ATTR_CUSTOM_DATA, ''), 'customData': call.data.get(ATTR_CUSTOM_DATA, ''),
@ -129,8 +164,7 @@ def async_setup(hass, config):
json.dumps(notification)) json.dumps(notification))
return return
@asyncio.coroutine async def snips_say_action(call):
def snips_say_action(call):
"""Send a Snips action message.""" """Send a Snips action message."""
notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'), notification = {'siteId': call.data.get(ATTR_SITE_ID, 'default'),
'customData': call.data.get(ATTR_CUSTOM_DATA, ''), 'customData': call.data.get(ATTR_CUSTOM_DATA, ''),
@ -144,12 +178,26 @@ def async_setup(hass, config):
json.dumps(notification)) json.dumps(notification))
return return
async def feedback_on(call):
"""Turn feedback sounds on."""
async_set_feedback(call.data.get(ATTR_SITE_ID), True)
async def feedback_off(call):
"""Turn feedback sounds off."""
async_set_feedback(call.data.get(ATTR_SITE_ID), False)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SAY, snips_say, DOMAIN, SERVICE_SAY, snips_say,
schema=SERVICE_SCHEMA_SAY) schema=SERVICE_SCHEMA_SAY)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SAY_ACTION, snips_say_action, DOMAIN, SERVICE_SAY_ACTION, snips_say_action,
schema=SERVICE_SCHEMA_SAY_ACTION) schema=SERVICE_SCHEMA_SAY_ACTION)
hass.services.async_register(
DOMAIN, SERVICE_FEEDBACK_ON, feedback_on,
schema=SERVICE_SCHEMA_FEEDBACK)
hass.services.async_register(
DOMAIN, SERVICE_FEEDBACK_OFF, feedback_off,
schema=SERVICE_SCHEMA_FEEDBACK)
return True return True

View File

@ -1,20 +1,92 @@
"""Test the Snips component.""" """Test the Snips component."""
import asyncio
import json import json
import logging import logging
from homeassistant.core import callback
from homeassistant.bootstrap import async_setup_component from homeassistant.bootstrap import async_setup_component
from homeassistant.components.mqtt import MQTT_PUBLISH_SCHEMA
import homeassistant.components.snips as snips
from tests.common import (async_fire_mqtt_message, async_mock_intent, from tests.common import (async_fire_mqtt_message, async_mock_intent,
async_mock_service) async_mock_service)
from homeassistant.components.snips import (SERVICE_SCHEMA_SAY,
SERVICE_SCHEMA_SAY_ACTION)
@asyncio.coroutine async def test_snips_config(hass, mqtt_mock):
def test_snips_intent(hass, mqtt_mock): """Test Snips Config."""
result = await async_setup_component(hass, "snips", {
"snips": {
"feedback_sounds": True,
"probability_threshold": .5,
"site_ids": ["default", "remote"]
},
})
assert result
async def test_snips_bad_config(hass, mqtt_mock):
"""Test Snips bad config."""
result = await async_setup_component(hass, "snips", {
"snips": {
"feedback_sounds": "on",
"probability": "none",
"site_ids": "default"
},
})
assert not result
async def test_snips_config_feedback_on(hass, mqtt_mock):
"""Test Snips Config."""
calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA)
result = await async_setup_component(hass, "snips", {
"snips": {
"feedback_sounds": True
},
})
assert result
await hass.async_block_till_done()
assert len(calls) == 2
topic = calls[0].data['topic']
assert topic == 'hermes/feedback/sound/toggleOn'
topic = calls[1].data['topic']
assert topic == 'hermes/feedback/sound/toggleOn'
assert calls[1].data['qos'] == 1
assert calls[1].data['retain']
async def test_snips_config_feedback_off(hass, mqtt_mock):
"""Test Snips Config."""
calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA)
result = await async_setup_component(hass, "snips", {
"snips": {
"feedback_sounds": False
},
})
assert result
await hass.async_block_till_done()
assert len(calls) == 2
topic = calls[0].data['topic']
assert topic == 'hermes/feedback/sound/toggleOn'
topic = calls[1].data['topic']
assert topic == 'hermes/feedback/sound/toggleOff'
assert calls[1].data['qos'] == 0
assert not calls[1].data['retain']
async def test_snips_config_no_feedback(hass, mqtt_mock):
"""Test Snips Config."""
calls = async_mock_service(hass, 'snips', 'say')
result = await async_setup_component(hass, "snips", {
"snips": {},
})
assert result
await hass.async_block_till_done()
assert len(calls) == 0
async def test_snips_intent(hass, mqtt_mock):
"""Test intent via Snips.""" """Test intent via Snips."""
result = yield from async_setup_component(hass, "snips", { result = await async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
@ -41,7 +113,7 @@ def test_snips_intent(hass, mqtt_mock):
async_fire_mqtt_message(hass, 'hermes/intent/Lights', async_fire_mqtt_message(hass, 'hermes/intent/Lights',
payload) payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(intents) == 1 assert len(intents) == 1
intent = intents[0] intent = intents[0]
assert intent.platform == 'snips' assert intent.platform == 'snips'
@ -50,10 +122,9 @@ def test_snips_intent(hass, mqtt_mock):
assert intent.text_input == 'turn the lights green' assert intent.text_input == 'turn the lights green'
@asyncio.coroutine async def test_snips_intent_with_duration(hass, mqtt_mock):
def test_snips_intent_with_duration(hass, mqtt_mock):
"""Test intent with Snips duration.""" """Test intent with Snips duration."""
result = yield from async_setup_component(hass, "snips", { result = await async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
@ -61,7 +132,8 @@ def test_snips_intent_with_duration(hass, mqtt_mock):
{ {
"input": "set a timer of five minutes", "input": "set a timer of five minutes",
"intent": { "intent": {
"intentName": "SetTimer" "intentName": "SetTimer",
"probability": 1
}, },
"slots": [ "slots": [
{ {
@ -92,7 +164,7 @@ def test_snips_intent_with_duration(hass, mqtt_mock):
async_fire_mqtt_message(hass, 'hermes/intent/SetTimer', async_fire_mqtt_message(hass, 'hermes/intent/SetTimer',
payload) payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(intents) == 1 assert len(intents) == 1
intent = intents[0] intent = intents[0]
assert intent.platform == 'snips' assert intent.platform == 'snips'
@ -100,22 +172,14 @@ def test_snips_intent_with_duration(hass, mqtt_mock):
assert intent.slots == {'timer_duration': {'value': 300}} assert intent.slots == {'timer_duration': {'value': 300}}
@asyncio.coroutine async def test_intent_speech_response(hass, mqtt_mock):
def test_intent_speech_response(hass, mqtt_mock):
"""Test intent speech response via Snips.""" """Test intent speech response via Snips."""
event = 'call_service' calls = async_mock_service(hass, 'mqtt', 'publish', MQTT_PUBLISH_SCHEMA)
events = [] result = await async_setup_component(hass, "snips", {
@callback
def record_event(event):
"""Add recorded event to set."""
events.append(event)
result = yield from async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
result = yield from async_setup_component(hass, "intent_script", { result = await async_setup_component(hass, "intent_script", {
"intent_script": { "intent_script": {
"spokenIntent": { "spokenIntent": {
"speech": { "speech": {
@ -131,31 +195,28 @@ def test_intent_speech_response(hass, mqtt_mock):
"input": "speak to me", "input": "speak to me",
"sessionId": "abcdef0123456789", "sessionId": "abcdef0123456789",
"intent": { "intent": {
"intentName": "spokenIntent" "intentName": "spokenIntent",
"probability": 1
}, },
"slots": [] "slots": []
} }
""" """
hass.bus.async_listen(event, record_event)
async_fire_mqtt_message(hass, 'hermes/intent/spokenIntent', async_fire_mqtt_message(hass, 'hermes/intent/spokenIntent',
payload) payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(events) == 1 assert len(calls) == 1
assert events[0].data['domain'] == 'mqtt' payload = json.loads(calls[0].data['payload'])
assert events[0].data['service'] == 'publish' topic = calls[0].data['topic']
payload = json.loads(events[0].data['service_data']['payload'])
topic = events[0].data['service_data']['topic']
assert payload['sessionId'] == 'abcdef0123456789' assert payload['sessionId'] == 'abcdef0123456789'
assert payload['text'] == 'I am speaking to you' assert payload['text'] == 'I am speaking to you'
assert topic == 'hermes/dialogueManager/endSession' assert topic == 'hermes/dialogueManager/endSession'
@asyncio.coroutine async def test_unknown_intent(hass, mqtt_mock, caplog):
def test_unknown_intent(hass, mqtt_mock, caplog):
"""Test unknown intent.""" """Test unknown intent."""
caplog.set_level(logging.WARNING) caplog.set_level(logging.WARNING)
result = yield from async_setup_component(hass, "snips", { result = await async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
@ -164,21 +225,21 @@ def test_unknown_intent(hass, mqtt_mock, caplog):
"input": "I don't know what I am supposed to do", "input": "I don't know what I am supposed to do",
"sessionId": "abcdef1234567890", "sessionId": "abcdef1234567890",
"intent": { "intent": {
"intentName": "unknownIntent" "intentName": "unknownIntent",
"probability": 1
}, },
"slots": [] "slots": []
} }
""" """
async_fire_mqtt_message(hass, async_fire_mqtt_message(hass,
'hermes/intent/unknownIntent', payload) 'hermes/intent/unknownIntent', payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert 'Received unknown intent unknownIntent' in caplog.text assert 'Received unknown intent unknownIntent' in caplog.text
@asyncio.coroutine async def test_snips_intent_user(hass, mqtt_mock):
def test_snips_intent_user(hass, mqtt_mock):
"""Test intentName format user_XXX__intentName.""" """Test intentName format user_XXX__intentName."""
result = yield from async_setup_component(hass, "snips", { result = await async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
@ -186,7 +247,8 @@ def test_snips_intent_user(hass, mqtt_mock):
{ {
"input": "what to do", "input": "what to do",
"intent": { "intent": {
"intentName": "user_ABCDEF123__Lights" "intentName": "user_ABCDEF123__Lights",
"probability": 1
}, },
"slots": [] "slots": []
} }
@ -194,7 +256,7 @@ def test_snips_intent_user(hass, mqtt_mock):
intents = async_mock_intent(hass, 'Lights') intents = async_mock_intent(hass, 'Lights')
async_fire_mqtt_message(hass, 'hermes/intent/user_ABCDEF123__Lights', async_fire_mqtt_message(hass, 'hermes/intent/user_ABCDEF123__Lights',
payload) payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(intents) == 1 assert len(intents) == 1
intent = intents[0] intent = intents[0]
@ -202,10 +264,9 @@ def test_snips_intent_user(hass, mqtt_mock):
assert intent.intent_type == 'Lights' assert intent.intent_type == 'Lights'
@asyncio.coroutine async def test_snips_intent_username(hass, mqtt_mock):
def test_snips_intent_username(hass, mqtt_mock):
"""Test intentName format username:intentName.""" """Test intentName format username:intentName."""
result = yield from async_setup_component(hass, "snips", { result = await async_setup_component(hass, "snips", {
"snips": {}, "snips": {},
}) })
assert result assert result
@ -213,7 +274,8 @@ def test_snips_intent_username(hass, mqtt_mock):
{ {
"input": "what to do", "input": "what to do",
"intent": { "intent": {
"intentName": "username:Lights" "intentName": "username:Lights",
"probability": 1
}, },
"slots": [] "slots": []
} }
@ -221,7 +283,7 @@ def test_snips_intent_username(hass, mqtt_mock):
intents = async_mock_intent(hass, 'Lights') intents = async_mock_intent(hass, 'Lights')
async_fire_mqtt_message(hass, 'hermes/intent/username:Lights', async_fire_mqtt_message(hass, 'hermes/intent/username:Lights',
payload) payload)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(intents) == 1 assert len(intents) == 1
intent = intents[0] intent = intents[0]
@ -229,15 +291,41 @@ def test_snips_intent_username(hass, mqtt_mock):
assert intent.intent_type == 'Lights' assert intent.intent_type == 'Lights'
@asyncio.coroutine async def test_snips_low_probability(hass, mqtt_mock, caplog):
def test_snips_say(hass, caplog): """Test intent via Snips."""
caplog.set_level(logging.WARNING)
result = await async_setup_component(hass, "snips", {
"snips": {
"probability_threshold": 0.5
},
})
assert result
payload = """
{
"input": "I am not sure what to say",
"intent": {
"intentName": "LightsMaybe",
"probability": 0.49
},
"slots": []
}
"""
async_mock_intent(hass, 'LightsMaybe')
async_fire_mqtt_message(hass, 'hermes/intent/LightsMaybe',
payload)
await hass.async_block_till_done()
assert 'Intent below probaility threshold 0.49 < 0.5' in caplog.text
async def test_snips_say(hass, caplog):
"""Test snips say with invalid config.""" """Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'say', calls = async_mock_service(hass, 'snips', 'say',
SERVICE_SCHEMA_SAY) snips.SERVICE_SCHEMA_SAY)
data = {'text': 'Hello'} data = {'text': 'Hello'}
yield from hass.services.async_call('snips', 'say', data) await hass.services.async_call('snips', 'say', data)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls) == 1 assert len(calls) == 1
assert calls[0].domain == 'snips' assert calls[0].domain == 'snips'
@ -245,15 +333,14 @@ def test_snips_say(hass, caplog):
assert calls[0].data['text'] == 'Hello' assert calls[0].data['text'] == 'Hello'
@asyncio.coroutine async def test_snips_say_action(hass, caplog):
def test_snips_say_action(hass, caplog):
"""Test snips say_action with invalid config.""" """Test snips say_action with invalid config."""
calls = async_mock_service(hass, 'snips', 'say_action', calls = async_mock_service(hass, 'snips', 'say_action',
SERVICE_SCHEMA_SAY_ACTION) snips.SERVICE_SCHEMA_SAY_ACTION)
data = {'text': 'Hello', 'intent_filter': ['myIntent']} data = {'text': 'Hello', 'intent_filter': ['myIntent']}
yield from hass.services.async_call('snips', 'say_action', data) await hass.services.async_call('snips', 'say_action', data)
yield from hass.async_block_till_done() await hass.async_block_till_done()
assert len(calls) == 1 assert len(calls) == 1
assert calls[0].domain == 'snips' assert calls[0].domain == 'snips'
@ -262,31 +349,71 @@ def test_snips_say_action(hass, caplog):
assert calls[0].data['intent_filter'] == ['myIntent'] assert calls[0].data['intent_filter'] == ['myIntent']
@asyncio.coroutine async def test_snips_say_invalid_config(hass, caplog):
def test_snips_say_invalid_config(hass, caplog):
"""Test snips say with invalid config.""" """Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'say', calls = async_mock_service(hass, 'snips', 'say',
SERVICE_SCHEMA_SAY) snips.SERVICE_SCHEMA_SAY)
data = {'text': 'Hello', 'badKey': 'boo'} data = {'text': 'Hello', 'badKey': 'boo'}
yield from hass.services.async_call('snips', 'say', data) await hass.services.async_call('snips', 'say', data)
yield from 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 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text assert 'Invalid service data' in caplog.text
@asyncio.coroutine async def test_snips_say_action_invalid(hass, caplog):
def test_snips_say_action_invalid_config(hass, caplog):
"""Test snips say_action with invalid config.""" """Test snips say_action with invalid config."""
calls = async_mock_service(hass, 'snips', 'say_action', calls = async_mock_service(hass, 'snips', 'say_action',
SERVICE_SCHEMA_SAY_ACTION) snips.SERVICE_SCHEMA_SAY_ACTION)
data = {'text': 'Hello', 'can_be_enqueued': 'notabool'} data = {'text': 'Hello', 'can_be_enqueued': 'notabool'}
yield from hass.services.async_call('snips', 'say_action', data) await hass.services.async_call('snips', 'say_action', data)
yield from 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 'ERROR' in caplog.text
assert 'Invalid service data' in caplog.text assert 'Invalid service data' in caplog.text
async def test_snips_feedback_on(hass, caplog):
"""Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'feedback_on',
snips.SERVICE_SCHEMA_FEEDBACK)
data = {'site_id': 'remote'}
await hass.services.async_call('snips', 'feedback_on', data)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].domain == 'snips'
assert calls[0].service == 'feedback_on'
assert calls[0].data['site_id'] == 'remote'
async def test_snips_feedback_off(hass, caplog):
"""Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'feedback_off',
snips.SERVICE_SCHEMA_FEEDBACK)
data = {'site_id': 'remote'}
await hass.services.async_call('snips', 'feedback_off', data)
await hass.async_block_till_done()
assert len(calls) == 1
assert calls[0].domain == 'snips'
assert calls[0].service == 'feedback_off'
assert calls[0].data['site_id'] == 'remote'
async def test_snips_feedback_config(hass, caplog):
"""Test snips say with invalid config."""
calls = async_mock_service(hass, 'snips', 'feedback_on',
snips.SERVICE_SCHEMA_FEEDBACK)
data = {'site_id': 'remote', 'test': 'test'}
await hass.services.async_call('snips', 'feedback_on', data)
await hass.async_block_till_done()
assert len(calls) == 0