Add discovery notify support and mysensors notify (#5219)

* Add mysensors notify platform

* Make add_devices optional in platform callback function.
* Use new argument structure for all existing mysensors platforms.
* Add notify platform.
* Update mysensors gateway.

* Refactor notify setup

* Enable discovery of notify platforms.
* Update and add tests for notify component and some platforms.
* Continue setup of notify platforms if a platform fails setup.
* Remove notify tests that check platform config. These tests are not
  needed when config validation is used.
* Add config validation to APNS notify platform.
* Use discovery to set up mysensors notify platform.

* Add discovery_info to get_service and update tests

* Add discovery_info as keyword argument to the get_service function
  signature and update all notify platforms.
* Update existing notify tests to check config validation using test
  helper.
* Add removed tests back in that checked config in apns, command_line
  and file platforms, but use config validation test helper to verify
  config.
* Add a test for notify file to increase coverage.
* Fix some PEP issues.

* Fix comments and use more constants

* Move apns notify service under notify domain
This commit is contained in:
Martin Hjelmare 2017-01-15 03:53:14 +01:00 committed by GitHub
parent 3b9fb6ccf5
commit 9db1aa7629
51 changed files with 395 additions and 255 deletions

View file

@ -47,7 +47,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsBinarySensor))
map_sv_types, devices, MySensorsBinarySensor, add_devices))
class MySensorsBinarySensor(

View file

@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsHVAC))
map_sv_types, devices, MySensorsHVAC, add_devices))
class MySensorsHVAC(mysensors.MySensorsDeviceEntity, ClimateDevice):

View file

@ -35,7 +35,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsCover))
map_sv_types, devices, MySensorsCover, add_devices))
class MySensorsCover(mysensors.MySensorsDeviceEntity, CoverDevice):

View file

@ -58,7 +58,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
})
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, device_class_map))
map_sv_types, devices, device_class_map, add_devices))
class MySensorsLight(mysensors.MySensorsDeviceEntity, Light):

View file

@ -9,10 +9,10 @@ import socket
import voluptuous as vol
from homeassistant.bootstrap import setup_component
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_OPTIMISTIC,
EVENT_HOMEASSISTANT_START,
from homeassistant.bootstrap import setup_component
from homeassistant.const import (ATTR_BATTERY_LEVEL, CONF_NAME,
CONF_OPTIMISTIC, EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP, STATE_OFF, STATE_ON)
from homeassistant.helpers import discovery
from homeassistant.loader import get_component
@ -169,10 +169,13 @@ def setup(hass, config):
'cover']:
discovery.load_platform(hass, component, DOMAIN, {}, config)
discovery.load_platform(
hass, 'notify', DOMAIN, {CONF_NAME: DOMAIN}, config)
return True
def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
def pf_callback_factory(map_sv_types, devices, entity_class, add_devices=None):
"""Return a new callback for the platform."""
def mysensors_callback(gateway, node_id):
"""Callback for mysensors platform."""
@ -187,7 +190,10 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
value_type not in map_sv_types[child.type]:
continue
if key in devices:
devices[key].update_ha_state(True)
if add_devices:
devices[key].schedule_update_ha_state(True)
else:
devices[key].update()
continue
name = '{} {} {}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id)
@ -197,11 +203,12 @@ def pf_callback_factory(map_sv_types, devices, add_devices, entity_class):
device_class = entity_class
devices[key] = device_class(
gateway, node_id, child.id, name, value_type, child.type)
_LOGGER.info('Adding new devices: %s', devices[key])
add_devices([devices[key]])
if key in devices:
devices[key].update_ha_state(True)
if add_devices:
_LOGGER.info('Adding new devices: %s', devices[key])
add_devices([devices[key]])
devices[key].schedule_update_ha_state(True)
else:
devices[key].update()
return mysensors_callback

View file

@ -14,7 +14,7 @@ import homeassistant.bootstrap as bootstrap
import homeassistant.helpers.config_validation as cv
from homeassistant.config import load_yaml_config_file
from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.helpers import config_per_platform
from homeassistant.helpers import config_per_platform, discovery
from homeassistant.util import slugify
_LOGGER = logging.getLogger(__name__)
@ -66,27 +66,32 @@ def send_message(hass, message, title=None, data=None):
def setup(hass, config):
"""Setup the notify services."""
success = False
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
targets = {}
for platform, p_config in config_per_platform(config, DOMAIN):
def setup_notify_platform(platform, p_config=None, discovery_info=None):
"""Set up a notify platform."""
if p_config is None:
p_config = {}
if discovery_info is None:
discovery_info = {}
notify_implementation = bootstrap.prepare_setup_platform(
hass, config, DOMAIN, platform)
if notify_implementation is None:
_LOGGER.error("Unknown notification service specified")
continue
return False
notify_service = notify_implementation.get_service(hass, p_config)
notify_service = notify_implementation.get_service(
hass, p_config, discovery_info)
if notify_service is None:
_LOGGER.error("Failed to initialize notification service %s",
platform)
continue
return False
def notify_message(notify_service, call):
"""Handle sending notification message service calls."""
@ -112,7 +117,9 @@ def setup(hass, config):
service_call_handler = partial(notify_message, notify_service)
if hasattr(notify_service, 'targets'):
platform_name = (p_config.get(CONF_NAME) or platform)
platform_name = (
p_config.get(CONF_NAME) or discovery_info.get(CONF_NAME) or
platform)
for name, target in notify_service.targets.items():
target_name = slugify('{}_{}'.format(platform_name, name))
targets[target_name] = target
@ -121,15 +128,29 @@ def setup(hass, config):
descriptions.get(SERVICE_NOTIFY),
schema=NOTIFY_SERVICE_SCHEMA)
platform_name = (p_config.get(CONF_NAME) or SERVICE_NOTIFY)
platform_name = (
p_config.get(CONF_NAME) or discovery_info.get(CONF_NAME) or
SERVICE_NOTIFY)
platform_name_slug = slugify(platform_name)
hass.services.register(
DOMAIN, platform_name_slug, service_call_handler,
descriptions.get(SERVICE_NOTIFY), schema=NOTIFY_SERVICE_SCHEMA)
success = True
return success
return True
for platform, p_config in config_per_platform(config, DOMAIN):
if not setup_notify_platform(platform, p_config):
_LOGGER.error("Failed to set up platform %s", platform)
continue
def platform_discovered(platform, info):
"""Callback to load a platform."""
setup_notify_platform(platform, discovery_info=info)
discovery.listen_platform(hass, DOMAIN, platform_discovered)
return True
class BaseNotificationService(object):

View file

@ -11,18 +11,29 @@ import voluptuous as vol
from homeassistant.helpers.event import track_state_change
from homeassistant.config import load_yaml_config_file
from homeassistant.components.notify import (
ATTR_TARGET, ATTR_DATA, BaseNotificationService)
ATTR_TARGET, ATTR_DATA, BaseNotificationService, DOMAIN)
from homeassistant.const import CONF_NAME, CONF_PLATFORM
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers import template as template_helper
DOMAIN = "apns"
APNS_DEVICES = "apns.yaml"
CONF_CERTFILE = "cert_file"
CONF_TOPIC = "topic"
CONF_SANDBOX = "sandbox"
DEVICE_TRACKER_DOMAIN = "device_tracker"
SERVICE_REGISTER = "apns_register"
ATTR_PUSH_ID = "push_id"
ATTR_NAME = "name"
PLATFORM_SCHEMA = vol.Schema({
vol.Required(CONF_PLATFORM): 'apns',
vol.Required(CONF_NAME): cv.string,
vol.Required(CONF_CERTFILE): cv.isfile,
vol.Required(CONF_TOPIC): cv.string,
vol.Optional(CONF_SANDBOX, default=False): cv.boolean,
})
REGISTER_SERVICE_SCHEMA = vol.Schema({
vol.Required(ATTR_PUSH_ID): cv.string,
vol.Optional(ATTR_NAME, default=None): cv.string,
@ -31,31 +42,19 @@ REGISTER_SERVICE_SCHEMA = vol.Schema({
REQUIREMENTS = ["apns2==0.1.1"]
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Return push service."""
descriptions = load_yaml_config_file(
os.path.join(os.path.dirname(__file__), 'services.yaml'))
name = config.get("name")
if name is None:
logging.error("Name must be specified.")
return None
cert_file = config.get('cert_file')
if cert_file is None:
logging.error("Certificate must be specified.")
return None
topic = config.get('topic')
if topic is None:
logging.error("Topic must be specified.")
return None
sandbox = bool(config.get('sandbox', False))
name = config.get(CONF_NAME)
cert_file = config.get(CONF_CERTFILE)
topic = config.get(CONF_TOPIC)
sandbox = config.get(CONF_SANDBOX)
service = ApnsNotificationService(hass, name, topic, sandbox, cert_file)
hass.services.register(DOMAIN,
name,
'apns_{}'.format(name),
service.register,
descriptions.get(SERVICE_REGISTER),
schema=REGISTER_SERVICE_SCHEMA)
@ -202,8 +201,6 @@ class ApnsNotificationService(BaseNotificationService):
def register(self, call):
"""Register a device to receive push messages."""
push_id = call.data.get(ATTR_PUSH_ID)
if push_id is None:
return False
device_name = call.data.get(ATTR_NAME)
current_device = self.devices.get(push_id)

View file

@ -35,7 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the AWS Lambda notification service."""
context_str = json.dumps({'hass': hass.config.as_dict(),
'custom': config[CONF_CONTEXT]})

View file

@ -33,7 +33,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the AWS SNS notification service."""
# pylint: disable=import-error
import boto3

View file

@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the AWS SQS notification service."""
# pylint: disable=import-error
import boto3

View file

@ -22,7 +22,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Command Line notification service."""
command = config[CONF_COMMAND]

View file

@ -9,7 +9,7 @@ from homeassistant.components.notify import BaseNotificationService
EVENT_NOTIFY = "notify"
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the demo notification service."""
return DemoNotificationService(hass)

View file

@ -24,7 +24,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Ecobee notification service."""
index = config.get(CONF_INDEX)
return EcobeeNotificationService(index)

View file

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the file notification service."""
filename = config[CONF_FILENAME]
timestamp = config[CONF_TIMESTAMP]

View file

@ -23,7 +23,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Free Mobile SMS notification service."""
return FreeSMSNotificationService(config[CONF_USERNAME],
config[CONF_ACCESS_TOKEN])

View file

@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the GNTP notification service."""
if config.get(CONF_APP_ICON) is None:
icon_file = os.path.join(os.path.dirname(__file__), "..", "frontend",

View file

@ -38,13 +38,13 @@ def update(input_dict, update_source):
return input_dict
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Group notification service."""
return GroupNotifyPlatform(hass, config.get(CONF_SERVICES))
class GroupNotifyPlatform(BaseNotificationService):
"""Implement the notification service for the group notify playform."""
"""Implement the notification service for the group notify platform."""
def __init__(self, hass, entities):
"""Initialize the service."""

View file

@ -97,7 +97,7 @@ HTML5_SHOWNOTIFICATION_PARAMETERS = ('actions', 'badge', 'body', 'dir',
'vibrate')
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the HTML5 push notification service."""
json_path = hass.config.path(REGISTRATIONS_FILE)

View file

@ -32,7 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Instapush notification service."""
headers = {'x-instapush-appid': config[CONF_API_KEY],
'x-instapush-appsecret': config[CONF_APP_SECRET]}

View file

@ -39,7 +39,7 @@ def log_rate_limits(target, resp, level=20):
str(resetsAtTime).split(".")[0])
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the iOS notification service."""
if "notify.ios" not in hass.config.components:
# Need this to enable requirements checking in the app.

View file

@ -27,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-variable
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Join notification service."""
device_id = config.get(CONF_DEVICE_ID)
api_key = config.get(CONF_API_KEY)

View file

@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
ATTR_DISPLAYTIME = 'displaytime'
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Return the notify service."""
url = '{}:{}'.format(config.get(CONF_HOST), config.get(CONF_PORT))

View file

@ -20,13 +20,13 @@ CONF_DEVICE = 'device'
_RESOURCE = 'https://llamalab.com/automate/cloud/message'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_TO): cv.string,
vol.Optional(CONF_DEVICE): cv.string,
vol.Required(CONF_API_KEY): cv.string,
vol.Required(CONF_TO): cv.string,
vol.Optional(CONF_DEVICE): cv.string,
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the LlamaLab Automate notification service."""
secret = config.get(CONF_API_KEY)
recipient = config.get(CONF_TO)

View file

@ -34,7 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Matrix notification service."""
if not AUTH_TOKENS:
load_token(hass.config.path(SESSION_FILE))

View file

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the MessageBird notification service."""
import messagebird

View file

@ -0,0 +1,65 @@
"""
MySensors notification service.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/notify.mysensors/
"""
from homeassistant.components import mysensors
from homeassistant.components.notify import (ATTR_TARGET,
BaseNotificationService)
def get_service(hass, config, discovery_info=None):
"""Get the MySensors notification service."""
if discovery_info is None:
return
platform_devices = []
gateways = hass.data.get(mysensors.MYSENSORS_GATEWAYS)
if not gateways:
return
for gateway in gateways:
pres = gateway.const.Presentation
set_req = gateway.const.SetReq
map_sv_types = {
pres.S_INFO: [set_req.V_TEXT],
}
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, MySensorsNotificationDevice))
platform_devices.append(devices)
return MySensorsNotificationService(platform_devices)
class MySensorsNotificationDevice(mysensors.MySensorsDeviceEntity):
"""Represent a MySensors Notification device."""
def send_msg(self, msg):
"""Send a message."""
for sub_msg in [msg[i:i + 25] for i in range(0, len(msg), 25)]:
# Max mysensors payload is 25 bytes.
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, sub_msg)
class MySensorsNotificationService(BaseNotificationService):
"""Implement MySensors notification service."""
# pylint: disable=too-few-public-methods
def __init__(self, platform_devices):
"""Initialize the service."""
self.platform_devices = platform_devices
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
target_devices = kwargs.get(ATTR_TARGET)
devices = []
for gw_devs in self.platform_devices:
for device in gw_devs.values():
if target_devices is None or device.name in target_devices:
devices.append(device)
for device in devices:
device.send_msg(message)

View file

@ -83,7 +83,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Notifications for Android TV notification service."""
remoteip = config.get(CONF_IP)
duration = config.get(CONF_DURATION)

View file

@ -24,7 +24,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the NMA notification service."""
parameters = {
'apikey': config[CONF_API_KEY],

View file

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the PushBullet notification service."""
from pushbullet import PushBullet
from pushbullet import InvalidKeyError

View file

@ -27,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Pushetta notification service."""
pushetta_service = PushettaNotificationService(config[CONF_API_KEY],
config[CONF_CHANNEL_NAME],

View file

@ -27,7 +27,7 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
# pylint: disable=unused-variable
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Pushover notification service."""
from pushover import InitError

View file

@ -39,7 +39,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the RESTful notification service."""
resource = config.get(CONF_RESOURCE)
method = config.get(CONF_METHOD)

View file

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the SendGrid notification service."""
api_key = config.get(CONF_API_KEY)
sender = config.get(CONF_SENDER)

View file

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Simplepush notification service."""
return SimplePushNotificationService(config.get(CONF_DEVICE_KEY))

View file

@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-variable
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Slack notification service."""
import slacker

View file

@ -47,7 +47,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the mail notification service."""
mail_service = MailNotificationService(
config.get(CONF_SERVER),

View file

@ -67,7 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
_LOGGER = logging.getLogger(__name__)
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the syslog notification service."""
import syslog

View file

@ -37,7 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Telegram notification service."""
import telegram

View file

@ -27,7 +27,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Telstra SMS API notification service."""
consumer_key = config.get(CONF_CONSUMER_KEY)
consumer_secret = config.get(CONF_CONSUMER_SECRET)

View file

@ -28,7 +28,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Twilio SMS notification service."""
# pylint: disable=import-error
from twilio.rest import TwilioRestClient

View file

@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Twitter notification service."""
return TwitterNotificationService(
config[CONF_CONSUMER_KEY], config[CONF_CONSUMER_SECRET],

View file

@ -24,7 +24,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Return the notify service."""
from pylgtv import WebOsClient
from pylgtv import PyLGTVPairException

View file

@ -30,7 +30,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
def get_service(hass, config):
def get_service(hass, config, discovery_info=None):
"""Get the Jabber (XMPP) notification service."""
return XmppNotificationService(
config.get('sender'),

View file

@ -83,7 +83,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, MySensorsSensor))
map_sv_types, devices, MySensorsSensor, add_devices))
class MySensorsSensor(mysensors.MySensorsDeviceEntity, Entity):

View file

@ -89,7 +89,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = {}
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
map_sv_types, devices, add_devices, device_class_map))
map_sv_types, devices, device_class_map, add_devices))
platform_devices.append(devices)
def send_ir_code_service(service):

View file

@ -399,7 +399,7 @@ def assert_setup_component(count, domain=None):
Use as a context manager aroung bootstrap.setup_component
with assert_setup_component(0) as result_config:
setup_component(hass, start_config, domain)
setup_component(hass, domain, start_config)
# using result_config is optional
"""
config = {}

View file

@ -1,14 +1,26 @@
"""The tests for the APNS component."""
import unittest
import os
import unittest
from unittest.mock import patch
from unittest.mock import Mock
from apns2.errors import Unregistered
import homeassistant.components.notify as notify
from homeassistant.core import State
from homeassistant.bootstrap import setup_component
from homeassistant.components.notify.apns import ApnsNotificationService
from tests.common import get_test_home_assistant
from homeassistant.config import load_yaml_config_file
from unittest.mock import patch
from apns2.errors import Unregistered
from homeassistant.core import State
from tests.common import assert_setup_component, get_test_home_assistant
CONFIG = {
notify.DOMAIN: {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
class TestApns(unittest.TestCase):
@ -22,6 +34,13 @@ class TestApns(unittest.TestCase):
"""Stop everything that was started."""
self.hass.stop()
@patch('os.path.isfile', Mock(return_value=True))
@patch('os.access', Mock(return_value=True))
def _setup_notify(self):
with assert_setup_component(1) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, CONFIG)
assert handle_config[notify.DOMAIN]
def test_apns_setup_full(self):
"""Test setup with all data."""
config = {
@ -41,53 +60,49 @@ class TestApns(unittest.TestCase):
config = {
'notify': {
'platform': 'apns',
'sandbox': 'True',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
'cert_file': 'test_app.pem',
}
}
self.assertFalse(notify.setup(self.hass, config))
with assert_setup_component(0) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, config)
assert not handle_config[notify.DOMAIN]
def test_apns_setup_missing_certificate(self):
"""Test setup with missing name."""
"""Test setup with missing certificate."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'name': 'test_app'
}
}
self.assertFalse(notify.setup(self.hass, config))
with assert_setup_component(0) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, config)
assert not handle_config[notify.DOMAIN]
def test_apns_setup_missing_topic(self):
"""Test setup with missing topic."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'cert_file': 'test_app.pem',
'name': 'test_app'
}
}
self.assertFalse(notify.setup(self.hass, config))
with assert_setup_component(0) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, config)
assert not handle_config[notify.DOMAIN]
def test_register_new_device(self):
"""Test registering a new device with a name."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('5678: {name: test device 2}\n')
notify.setup(self.hass, config)
self.assertTrue(self.hass.services.call('apns',
'test_app',
self._setup_notify()
self.assertTrue(self.hass.services.call(notify.DOMAIN,
'apns_test_app',
{'push_id': '1234',
'name': 'test device'},
blocking=True))
@ -107,21 +122,12 @@ class TestApns(unittest.TestCase):
def test_register_device_without_name(self):
"""Test registering a without a name."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('5678: {name: test device 2}\n')
notify.setup(self.hass, config)
self.assertTrue(self.hass.services.call('apns', 'test_app',
self._setup_notify()
self.assertTrue(self.hass.services.call(notify.DOMAIN, 'apns_test_app',
{'push_id': '1234'},
blocking=True))
@ -137,23 +143,14 @@ class TestApns(unittest.TestCase):
def test_update_existing_device(self):
"""Test updating an existing device."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('1234: {name: test device 1}\n')
out.write('5678: {name: test device 2}\n')
notify.setup(self.hass, config)
self.assertTrue(self.hass.services.call('apns',
'test_app',
self._setup_notify()
self.assertTrue(self.hass.services.call(notify.DOMAIN,
'apns_test_app',
{'push_id': '1234',
'name': 'updated device 1'},
blocking=True))
@ -173,15 +170,6 @@ class TestApns(unittest.TestCase):
def test_update_existing_device_with_tracking_id(self):
"""Test updating an existing device that has a tracking id."""
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('1234: {name: test device 1, '
@ -189,9 +177,9 @@ class TestApns(unittest.TestCase):
out.write('5678: {name: test device 2, '
'tracking_device_id: tracking456}\n')
notify.setup(self.hass, config)
self.assertTrue(self.hass.services.call('apns',
'test_app',
self._setup_notify()
self.assertTrue(self.hass.services.call(notify.DOMAIN,
'apns_test_app',
{'push_id': '1234',
'name': 'updated device 1'},
blocking=True))
@ -216,30 +204,20 @@ class TestApns(unittest.TestCase):
def test_send(self, mock_client):
"""Test updating an existing device."""
send = mock_client.return_value.send_notification
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('1234: {name: test device 1}\n')
notify.setup(self.hass, config)
self._setup_notify()
self.assertTrue(self.hass.services.call('notify', 'test_app',
{'message': 'Hello',
'data': {
'badge': 1,
'sound': 'test.mp3',
'category': 'testing'
}
},
blocking=True))
self.assertTrue(self.hass.services.call(
'notify', 'test_app',
{'message': 'Hello', 'data': {
'badge': 1,
'sound': 'test.mp3',
'category': 'testing'}},
blocking=True))
self.assertTrue(send.called)
self.assertEqual(1, len(send.mock_calls))
@ -257,30 +235,20 @@ class TestApns(unittest.TestCase):
def test_send_when_disabled(self, mock_client):
"""Test updating an existing device."""
send = mock_client.return_value.send_notification
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('1234: {name: test device 1, disabled: True}\n')
notify.setup(self.hass, config)
self._setup_notify()
self.assertTrue(self.hass.services.call('notify', 'test_app',
{'message': 'Hello',
'data': {
'badge': 1,
'sound': 'test.mp3',
'category': 'testing'
}
},
blocking=True))
self.assertTrue(self.hass.services.call(
'notify', 'test_app',
{'message': 'Hello', 'data': {
'badge': 1,
'sound': 'test.mp3',
'category': 'testing'}},
blocking=True))
self.assertFalse(send.called)
@ -328,20 +296,11 @@ class TestApns(unittest.TestCase):
send = mock_client.return_value.send_notification
send.side_effect = Unregistered()
config = {
'notify': {
'platform': 'apns',
'name': 'test_app',
'topic': 'testapp.appname',
'cert_file': 'test_app.pem'
}
}
devices_path = self.hass.config.path('test_app_apns.yaml')
with open(devices_path, 'w+') as out:
out.write('1234: {name: test device 1}\n')
notify.setup(self.hass, config)
self._setup_notify()
self.assertTrue(self.hass.services.call('notify', 'test_app',
{'message': 'Hello'},

View file

@ -6,7 +6,7 @@ from unittest.mock import patch
from homeassistant.bootstrap import setup_component
import homeassistant.components.notify as notify
from tests.common import get_test_home_assistant
from tests.common import assert_setup_component, get_test_home_assistant
class TestCommandLine(unittest.TestCase):
@ -22,34 +22,41 @@ class TestCommandLine(unittest.TestCase):
def test_setup(self):
"""Test setup."""
assert setup_component(self.hass, 'notify', {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat); exit 1',
}})
with assert_setup_component(1) as handle_config:
assert setup_component(self.hass, 'notify', {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat); exit 1', }
})
assert handle_config[notify.DOMAIN]
def test_bad_config(self):
"""Test set up the platform with bad/missing configuration."""
self.assertFalse(setup_component(self.hass, notify.DOMAIN, {
'notify': {
config = {
notify.DOMAIN: {
'name': 'test',
'platform': 'bad_platform',
'platform': 'command_line',
}
}))
}
with assert_setup_component(0) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, config)
assert not handle_config[notify.DOMAIN]
def test_command_line_output(self):
"""Test the command line output."""
with tempfile.TemporaryDirectory() as tempdirname:
filename = os.path.join(tempdirname, 'message.txt')
message = 'one, two, testing, testing'
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat) > {}'.format(filename)
}
}))
with assert_setup_component(1) as handle_config:
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat) > {}'.format(filename)
}
}))
assert handle_config[notify.DOMAIN]
self.assertTrue(
self.hass.services.call('notify', 'test', {'message': message},
@ -63,13 +70,15 @@ class TestCommandLine(unittest.TestCase):
@patch('homeassistant.components.notify.command_line._LOGGER.error')
def test_error_for_none_zero_exit_code(self, mock_error):
"""Test if an error is logged for non zero exit codes."""
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat); exit 1'
}
}))
with assert_setup_component(1) as handle_config:
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'command_line',
'command': 'echo $(cat); exit 1'
}
}))
assert handle_config[notify.DOMAIN]
self.assertTrue(
self.hass.services.call('notify', 'test', {'message': 'error'},

View file

@ -1,13 +1,19 @@
"""The tests for the notify demo platform."""
import unittest
from unittest.mock import patch
from homeassistant.core import callback
from homeassistant.bootstrap import setup_component
import homeassistant.components.notify as notify
from homeassistant.bootstrap import setup_component
from homeassistant.components.notify import demo
from homeassistant.helpers import script
from homeassistant.core import callback
from homeassistant.helpers import discovery, script
from tests.common import assert_setup_component, get_test_home_assistant
from tests.common import get_test_home_assistant
CONFIG = {
notify.DOMAIN: {
'platform': 'demo'
}
}
class TestNotifyDemo(unittest.TestCase):
@ -16,11 +22,6 @@ class TestNotifyDemo(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
"""Setup things to be run when tests are started."""
self.hass = get_test_home_assistant()
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'platform': 'demo'
}
}))
self.events = []
self.calls = []
@ -35,6 +36,59 @@ class TestNotifyDemo(unittest.TestCase):
""""Stop down everything that was started."""
self.hass.stop()
def _setup_notify(self):
with assert_setup_component(1) as config:
assert setup_component(self.hass, notify.DOMAIN, CONFIG)
assert config[notify.DOMAIN]
def test_setup(self):
"""Test setup."""
self._setup_notify()
@patch('homeassistant.bootstrap.prepare_setup_platform')
def test_no_prepare_setup_platform(self, mock_prep_setup_platform):
"""Test missing notify platform."""
mock_prep_setup_platform.return_value = None
with self.assertLogs('homeassistant.components.notify',
level='ERROR') as log_handle:
self._setup_notify()
self.hass.block_till_done()
assert mock_prep_setup_platform.called
self.assertEqual(
log_handle.output,
['ERROR:homeassistant.components.notify:'
'Unknown notification service specified',
'ERROR:homeassistant.components.notify:'
'Failed to set up platform demo'])
@patch('homeassistant.components.notify.demo.get_service')
def test_no_notify_service(self, mock_demo_get_service):
"""Test missing platform notify service instance."""
mock_demo_get_service.return_value = None
with self.assertLogs('homeassistant.components.notify',
level='ERROR') as log_handle:
self._setup_notify()
self.hass.block_till_done()
assert mock_demo_get_service.called
self.assertEqual(
log_handle.output,
['ERROR:homeassistant.components.notify:'
'Failed to initialize notification service demo',
'ERROR:homeassistant.components.notify:'
'Failed to set up platform demo'])
@patch('homeassistant.components.notify.demo.get_service')
def test_discover_notify(self, mock_demo_get_service):
"""Test discovery of notify demo platform."""
assert notify.DOMAIN not in self.hass.config.components
discovery.load_platform(
self.hass, 'notify', 'demo', {'test_key': 'test_val'}, {})
self.hass.block_till_done()
assert notify.DOMAIN in self.hass.config.components
assert mock_demo_get_service.called
assert mock_demo_get_service.call_args[0] == (
self.hass, {}, {'test_key': 'test_val'})
@callback
def record_calls(self, *args):
"""Helper for recording calls."""
@ -42,12 +96,14 @@ class TestNotifyDemo(unittest.TestCase):
def test_sending_none_message(self):
"""Test send with None as message."""
self._setup_notify()
notify.send_message(self.hass, None)
self.hass.block_till_done()
self.assertTrue(len(self.events) == 0)
def test_sending_templated_message(self):
"""Send a templated message."""
self._setup_notify()
self.hass.states.set('sensor.temperature', 10)
notify.send_message(self.hass, '{{ states.sensor.temperature.state }}',
'{{ states.sensor.temperature.name }}')
@ -58,6 +114,7 @@ class TestNotifyDemo(unittest.TestCase):
def test_method_forwards_correct_data(self):
"""Test that all data from the service gets forwarded to service."""
self._setup_notify()
notify.send_message(self.hass, 'my message', 'my title',
{'hello': 'world'})
self.hass.block_till_done()
@ -71,6 +128,7 @@ class TestNotifyDemo(unittest.TestCase):
def test_calling_notify_from_script_loaded_from_yaml_without_title(self):
"""Test if we can call a notify from a script."""
self._setup_notify()
conf = {
'service': 'notify.notify',
'data': {
@ -97,6 +155,7 @@ class TestNotifyDemo(unittest.TestCase):
def test_calling_notify_from_script_loaded_from_yaml_with_title(self):
"""Test if we can call a notify from a script."""
self._setup_notify()
conf = {
'service': 'notify.notify',
'data': {
@ -127,12 +186,14 @@ class TestNotifyDemo(unittest.TestCase):
def test_targets_are_services(self):
"""Test that all targets are exposed as individual services."""
self._setup_notify()
self.assertIsNotNone(self.hass.services.has_service("notify", "demo"))
service = "demo_test_target_name"
self.assertIsNotNone(self.hass.services.has_service("notify", service))
def test_messages_to_targets_route(self):
"""Test message routing to specific target services."""
self._setup_notify()
self.hass.bus.listen_once("notify", self.record_calls)
self.hass.services.call("notify", "demo_test_target_name",

View file

@ -9,7 +9,7 @@ from homeassistant.components.notify import (
ATTR_TITLE_DEFAULT)
import homeassistant.util.dt as dt_util
from tests.common import get_test_home_assistant, assert_setup_component
from tests.common import assert_setup_component, get_test_home_assistant
class TestNotifyFile(unittest.TestCase):
@ -25,36 +25,38 @@ class TestNotifyFile(unittest.TestCase):
def test_bad_config(self):
"""Test set up the platform with bad/missing config."""
with assert_setup_component(0):
assert not setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'file',
},
})
config = {
notify.DOMAIN: {
'name': 'test',
'platform': 'file',
},
}
with assert_setup_component(0) as handle_config:
assert setup_component(self.hass, notify.DOMAIN, config)
assert not handle_config[notify.DOMAIN]
@patch('homeassistant.components.notify.file.os.stat')
@patch('homeassistant.util.dt.utcnow')
def test_notify_file(self, mock_utcnow, mock_stat):
def _test_notify_file(self, timestamp, mock_utcnow, mock_stat):
"""Test the notify file output."""
mock_utcnow.return_value = dt_util.as_utc(dt_util.now())
mock_stat.return_value.st_size = 0
m_open = mock_open()
with patch(
'homeassistant.components.notify.file.open',
m_open, create=True
'homeassistant.components.notify.file.open',
m_open, create=True
):
filename = 'mock_file'
message = 'one, two, testing, testing'
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'file',
'filename': filename,
'timestamp': False,
}
}))
with assert_setup_component(1) as handle_config:
self.assertTrue(setup_component(self.hass, notify.DOMAIN, {
'notify': {
'name': 'test',
'platform': 'file',
'filename': filename,
'timestamp': timestamp,
}
}))
assert handle_config[notify.DOMAIN]
title = '{} notifications (Log started: {})\n{}\n'.format(
ATTR_TITLE_DEFAULT,
dt_util.utcnow().isoformat(),
@ -68,7 +70,26 @@ class TestNotifyFile(unittest.TestCase):
self.assertEqual(m_open.call_args, call(full_filename, 'a'))
self.assertEqual(m_open.return_value.write.call_count, 2)
self.assertEqual(
m_open.return_value.write.call_args_list,
[call(title), call(message + '\n')]
)
if not timestamp:
self.assertEqual(
m_open.return_value.write.call_args_list,
[call(title), call('{}\n'.format(message))]
)
else:
self.assertEqual(
m_open.return_value.write.call_args_list,
[call(title), call('{} {}\n'.format(
dt_util.utcnow().isoformat(), message))]
)
@patch('homeassistant.components.notify.file.os.stat')
@patch('homeassistant.util.dt.utcnow')
def test_notify_file(self, mock_utcnow, mock_stat):
"""Test the notify file output without timestamp."""
self._test_notify_file(False, mock_utcnow, mock_stat)
@patch('homeassistant.components.notify.file.os.stat')
@patch('homeassistant.util.dt.utcnow')
def test_notify_file_timestamp(self, mock_utcnow, mock_stat):
"""Test the notify file output with timestamp."""
self._test_notify_file(True, mock_utcnow, mock_stat)

View file

@ -19,7 +19,7 @@ class TestNotifyGroup(unittest.TestCase):
self.service1 = MagicMock()
self.service2 = MagicMock()
def mock_get_service(hass, config):
def mock_get_service(hass, config, discovery_info=None):
if config['name'] == 'demo1':
return self.service1
else: