MQTT config entry (#16594)

* MQTT config entry

* Solely rely on config entry

* Improve wawrning

* Lint

* Lint2
This commit is contained in:
Paulus Schoutsen 2018-09-14 11:57:31 +02:00 committed by GitHub
parent 05c0717167
commit 72e746d240
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 435 additions and 133 deletions

View file

@ -14,6 +14,7 @@
"data": { "data": {
"2fa": "2FA Pin" "2fa": "2FA Pin"
}, },
"description": "",
"title": "2-Factor-Authentication" "title": "2-Factor-Authentication"
}, },
"user": { "user": {
@ -21,6 +22,7 @@
"email": "E-Mail Address", "email": "E-Mail Address",
"password": "Password" "password": "Password"
}, },
"description": "",
"title": "Google Hangouts Login" "title": "Google Hangouts Login"
} }
}, },

View file

@ -2,7 +2,6 @@
"config": { "config": {
"abort": { "abort": {
"already_configured": "Access point is already configured", "already_configured": "Access point is already configured",
"conection_aborted": "Could not connect to HMIP server",
"connection_aborted": "Could not connect to HMIP server", "connection_aborted": "Could not connect to HMIP server",
"unknown": "Unknown error occurred." "unknown": "Unknown error occurred."
}, },

View file

@ -0,0 +1,23 @@
{
"config": {
"abort": {
"single_instance_allowed": "Only a single configuration of MQTT is allowed."
},
"error": {
"cannot_connect": "Unable to connect to the broker."
},
"step": {
"broker": {
"data": {
"broker": "Broker",
"password": "Password",
"port": "Port",
"username": "Username"
},
"description": "Please enter the connection information of your MQTT broker.",
"title": "MQTT"
}
},
"title": "MQTT"
}
}

View file

@ -18,6 +18,7 @@ import attr
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries
from homeassistant.helpers.typing import HomeAssistantType, ConfigType, \ from homeassistant.helpers.typing import HomeAssistantType, ConfigType, \
ServiceDataType ServiceDataType
from homeassistant.core import callback, Event, ServiceCall from homeassistant.core import callback, Event, ServiceCall
@ -30,8 +31,12 @@ from homeassistant.util.async_ import (
run_coroutine_threadsafe, run_callback_threadsafe) run_coroutine_threadsafe, run_callback_threadsafe)
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_STOP, CONF_VALUE_TEMPLATE, CONF_USERNAME, EVENT_HOMEASSISTANT_STOP, CONF_VALUE_TEMPLATE, CONF_USERNAME,
CONF_PASSWORD, CONF_PORT, CONF_PROTOCOL, CONF_PAYLOAD) CONF_PASSWORD, CONF_PORT, CONF_PROTOCOL, CONF_PAYLOAD,
EVENT_HOMEASSISTANT_START)
# Loading the config flow file will register the flow
from . import config_flow # noqa # pylint: disable=unused-import
from .const import CONF_BROKER
from .server import HBMQTT_CONFIG_SCHEMA from .server import HBMQTT_CONFIG_SCHEMA
REQUIREMENTS = ['paho-mqtt==1.3.1'] REQUIREMENTS = ['paho-mqtt==1.3.1']
@ -41,11 +46,12 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'mqtt' DOMAIN = 'mqtt'
DATA_MQTT = 'mqtt' DATA_MQTT = 'mqtt'
DATA_MQTT_CONFIG = 'mqtt_config'
SERVICE_PUBLISH = 'publish' SERVICE_PUBLISH = 'publish'
CONF_EMBEDDED = 'embedded' CONF_EMBEDDED = 'embedded'
CONF_BROKER = 'broker'
CONF_CLIENT_ID = 'client_id' CONF_CLIENT_ID = 'client_id'
CONF_DISCOVERY = 'discovery' CONF_DISCOVERY = 'discovery'
CONF_DISCOVERY_PREFIX = 'discovery_prefix' CONF_DISCOVERY_PREFIX = 'discovery_prefix'
@ -311,6 +317,7 @@ async def _async_setup_server(hass: HomeAssistantType,
if not success: if not success:
return None return None
return broker_config return broker_config
@ -340,19 +347,15 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
conf = config.get(DOMAIN) # type: Optional[ConfigType] conf = config.get(DOMAIN) # type: Optional[ConfigType]
if conf is None: if conf is None:
conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN] # If we have a config entry, setup is done by that config entry.
conf = cast(ConfigType, conf) # If there is no config entry, this should fail.
return bool(hass.config_entries.async_entries(DOMAIN))
client_id = conf.get(CONF_CLIENT_ID) # type: Optional[str] conf = dict(conf)
keepalive = conf.get(CONF_KEEPALIVE) # type: int
# Only setup if embedded config passed in or no broker specified if CONF_EMBEDDED in conf or CONF_BROKER not in conf:
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
broker_config = None
else:
if (conf.get(CONF_PASSWORD) is None and if (conf.get(CONF_PASSWORD) is None and
config.get('http') is not None and config.get('http', {}).get('api_password') is not None):
config['http'].get('api_password') is not None):
_LOGGER.error( _LOGGER.error(
"Starting from release 0.76, the embedded MQTT broker does not" "Starting from release 0.76, the embedded MQTT broker does not"
" use api_password as default password anymore. Please set" " use api_password as default password anymore. Please set"
@ -362,48 +365,98 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
broker_config = await _async_setup_server(hass, config) broker_config = await _async_setup_server(hass, config)
if CONF_BROKER in conf: if broker_config is None:
broker = conf[CONF_BROKER] # type: str _LOGGER.error('Unable to start embedded MQTT broker')
port = conf[CONF_PORT] # type: int return False
username = conf.get(CONF_USERNAME) # type: Optional[str]
password = conf.get(CONF_PASSWORD) # type: Optional[str] conf.update({
certificate = conf.get(CONF_CERTIFICATE) # type: Optional[str] CONF_BROKER: broker_config[0],
client_key = conf.get(CONF_CLIENT_KEY) # type: Optional[str] CONF_PORT: broker_config[1],
client_cert = conf.get(CONF_CLIENT_CERT) # type: Optional[str] CONF_USERNAME: broker_config[2],
tls_insecure = conf.get(CONF_TLS_INSECURE) # type: Optional[bool] CONF_PASSWORD: broker_config[3],
protocol = conf[CONF_PROTOCOL] # type: str CONF_CERTIFICATE: broker_config[4],
elif broker_config is not None: CONF_PROTOCOL: broker_config[5],
# If no broker passed in, auto config to internal server CONF_CLIENT_KEY: None,
broker, port, username, password, certificate, protocol = broker_config CONF_CLIENT_CERT: None,
# Embedded broker doesn't have some ssl variables CONF_TLS_INSECURE: None,
client_key, client_cert, tls_insecure = None, None, None })
# hbmqtt requires a client id to be set.
if client_id is None: hass.data[DATA_MQTT_CONFIG] = conf
client_id = 'home-assistant'
else: # Only import if we haven't before.
err = "Unable to start MQTT broker." if not hass.config_entries.async_entries(DOMAIN):
if conf.get(CONF_EMBEDDED) is not None: hass.async_create_task(hass.config_entries.flow.async_init(
# Explicit embedded config, requires explicit broker config DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
err += " (Broker configuration required.)" data={}
_LOGGER.error(err) ))
if conf.get(CONF_DISCOVERY):
async def async_setup_discovery(event):
await _async_setup_discovery(hass, config)
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, async_setup_discovery)
return True
async def async_setup_entry(hass, entry):
"""Load a config entry."""
conf = hass.data.get(DATA_MQTT_CONFIG)
# Config entry was created because user had configuration.yaml entry
# They removed that, so remove entry.
if conf is None and entry.source == config_entries.SOURCE_IMPORT:
hass.async_create_task(
hass.config_entries.async_remove(entry.entry_id))
return False return False
# If user didn't have configuration.yaml config, generate defaults
if conf is None:
conf = CONFIG_SCHEMA({
DOMAIN: entry.data
})[DOMAIN]
elif any(key in conf for key in entry.data):
_LOGGER.warning(
'Data in your config entry is going to override your '
'configuration.yaml: %s', entry.data)
conf.update(entry.data)
broker = conf[CONF_BROKER]
port = conf[CONF_PORT]
client_id = conf.get(CONF_CLIENT_ID)
keepalive = conf[CONF_KEEPALIVE]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
client_key = conf.get(CONF_CLIENT_KEY)
client_cert = conf.get(CONF_CLIENT_CERT)
tls_insecure = conf.get(CONF_TLS_INSECURE)
protocol = conf[CONF_PROTOCOL]
# For cloudmqtt.com, secured connection, auto fill in certificate # For cloudmqtt.com, secured connection, auto fill in certificate
if (certificate is None and 19999 < port < 30000 and if (conf.get(CONF_CERTIFICATE) is None and
broker.endswith('.cloudmqtt.com')): 19999 < conf[CONF_PORT] < 30000 and
conf[CONF_BROKER].endswith('.cloudmqtt.com')):
certificate = os.path.join(os.path.dirname(__file__), certificate = os.path.join(os.path.dirname(__file__),
'addtrustexternalcaroot.crt') 'addtrustexternalcaroot.crt')
# When the certificate is set to auto, use bundled certs from requests # When the certificate is set to auto, use bundled certs from requests
if certificate == 'auto': elif conf.get(CONF_CERTIFICATE) == 'auto':
certificate = requests.certs.where() certificate = requests.certs.where()
will_message = None # type: Optional[Message] else:
if conf.get(CONF_WILL_MESSAGE) is not None: certificate = None
will_message = Message(**conf.get(CONF_WILL_MESSAGE))
birth_message = None # type: Optional[Message] if CONF_WILL_MESSAGE in conf:
if conf.get(CONF_BIRTH_MESSAGE) is not None: will_message = Message(**conf[CONF_WILL_MESSAGE])
birth_message = Message(**conf.get(CONF_BIRTH_MESSAGE)) else:
will_message = None
if CONF_BIRTH_MESSAGE in conf:
birth_message = Message(**conf[CONF_BIRTH_MESSAGE])
else:
birth_message = None
# Be able to override versions other than TLSv1.0 under Python3.6 # Be able to override versions other than TLSv1.0 under Python3.6
conf_tls_version = conf.get(CONF_TLS_VERSION) # type: str conf_tls_version = conf.get(CONF_TLS_VERSION) # type: str
@ -421,14 +474,27 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
else: else:
tls_version = ssl.PROTOCOL_TLSv1 tls_version = ssl.PROTOCOL_TLSv1
try: hass.data[DATA_MQTT] = MQTT(
hass.data[DATA_MQTT] = MQTT( hass,
hass, broker, port, client_id, keepalive, username, password, broker=broker,
certificate, client_key, client_cert, tls_insecure, protocol, port=port,
will_message, birth_message, tls_version) client_id=client_id,
except socket.error: keepalive=keepalive,
_LOGGER.exception("Can't connect to the broker. " username=username,
"Please check your settings and the broker itself") password=password,
certificate=certificate,
client_key=client_key,
client_cert=client_cert,
tls_insecure=tls_insecure,
protocol=protocol,
will_message=will_message,
birth_message=birth_message,
tls_version=tls_version,
)
success = await hass.data[DATA_MQTT].async_connect() # type: bool
if not success:
return False return False
async def async_stop_mqtt(event: Event): async def async_stop_mqtt(event: Event):
@ -437,10 +503,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_mqtt) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop_mqtt)
success = await hass.data[DATA_MQTT].async_connect() # type: bool
if not success:
return False
async def async_publish_service(call: ServiceCall): async def async_publish_service(call: ServiceCall):
"""Handle MQTT publish service calls.""" """Handle MQTT publish service calls."""
msg_topic = call.data[ATTR_TOPIC] # type: str msg_topic = call.data[ATTR_TOPIC] # type: str
@ -466,9 +528,6 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
DOMAIN, SERVICE_PUBLISH, async_publish_service, DOMAIN, SERVICE_PUBLISH, async_publish_service,
schema=MQTT_PUBLISH_SCHEMA) schema=MQTT_PUBLISH_SCHEMA)
if conf.get(CONF_DISCOVERY):
await _async_setup_discovery(hass, config)
return True return True
@ -501,7 +560,8 @@ class MQTT:
certificate: Optional[str], client_key: Optional[str], certificate: Optional[str], client_key: Optional[str],
client_cert: Optional[str], tls_insecure: Optional[bool], client_cert: Optional[str], tls_insecure: Optional[bool],
protocol: Optional[str], will_message: Optional[Message], protocol: Optional[str], will_message: Optional[Message],
birth_message: Optional[Message], tls_version) -> None: birth_message: Optional[Message],
tls_version: Optional[int]) -> None:
"""Initialize Home Assistant MQTT client.""" """Initialize Home Assistant MQTT client."""
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt

View file

@ -0,0 +1,98 @@
"""Config flow for MQTT."""
from collections import OrderedDict
import queue
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_PORT
from .const import CONF_BROKER
@config_entries.HANDLERS.register('mqtt')
class FlowHandler(config_entries.ConfigFlow):
"""Handle a config flow."""
VERSION = 1
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if self._async_current_entries():
return self.async_abort(
reason='single_instance_allowed'
)
return await self.async_step_broker()
async def async_step_broker(self, user_input=None):
"""Confirm setup."""
errors = {}
if user_input is not None:
can_connect = await self.hass.async_add_executor_job(
try_connection, user_input[CONF_BROKER], user_input[CONF_PORT],
user_input.get(CONF_USERNAME), user_input.get(CONF_PASSWORD))
if can_connect:
return self.async_create_entry(
title=user_input[CONF_BROKER],
data=user_input
)
errors['base'] = 'cannot_connect'
fields = OrderedDict()
fields[vol.Required(CONF_BROKER)] = str
fields[vol.Required(CONF_PORT, default=1883)] = vol.Coerce(int)
fields[vol.Optional(CONF_USERNAME)] = str
fields[vol.Optional(CONF_PASSWORD)] = str
return self.async_show_form(
step_id='broker',
data_schema=vol.Schema(fields),
errors=errors,
)
async def async_step_import(self, user_input):
"""Import a config entry.
Special type of import, we're not actually going to store any data.
Instead, we're going to rely on the values that are in config file.
"""
if self._async_current_entries():
return self.async_abort(
reason='single_instance_allowed'
)
return self.async_create_entry(
title='configuration.yaml',
data={}
)
def try_connection(broker, port, username, password):
"""Test if we can connect to an MQTT broker."""
import paho.mqtt.client as mqtt
client = mqtt.Client()
if username and password:
client.username_pw_set(username, password)
result = queue.Queue(maxsize=1)
def on_connect(client_, userdata, flags, result_code):
"""Handle connection result."""
result.put(result_code == mqtt.CONNACK_ACCEPTED)
client.on_connect = on_connect
client.connect_async(broker, port)
client.loop_start()
try:
return result.get(timeout=5)
except queue.Empty:
return False
finally:
client.disconnect()
client.loop_stop()

View file

@ -0,0 +1,2 @@
"""Constants used by multiple MQTT modules."""
CONF_BROKER = 'broker'

View file

@ -0,0 +1,23 @@
{
"config": {
"title": "MQTT",
"step": {
"broker": {
"title": "MQTT",
"description": "Please enter the connection information of your MQTT broker.",
"data": {
"broker": "Broker",
"port": "Port",
"username": "Username",
"password": "Password"
}
}
},
"abort": {
"single_instance_allowed": "Only a single configuration of MQTT is allowed."
},
"error": {
"cannot_connect": "Unable to connect to the broker."
}
}
}

View file

@ -141,6 +141,7 @@ FLOWS = [
'homematicip_cloud', 'homematicip_cloud',
'hue', 'hue',
'ios', 'ios',
'mqtt',
'nest', 'nest',
'openuv', 'openuv',
'sonos', 'sonos',
@ -463,3 +464,19 @@ class ConfigEntries:
async def _old_conf_migrator(old_config): async def _old_conf_migrator(old_config):
"""Migrate the pre-0.73 config format to the latest version.""" """Migrate the pre-0.73 config format to the latest version."""
return {'entries': old_config} return {'entries': old_config}
class ConfigFlow(data_entry_flow.FlowHandler):
"""Base class for config flows with some helpers."""
@callback
def _async_current_entries(self):
"""Return current entries."""
return self.hass.config_entries.async_entries(self.handler)
@callback
def _async_in_progress(self):
"""Return other in progress flows for current domain."""
return [flw for flw in self.hass.config_entries.flow.async_progress()
if flw['handler'] == self.handler and
flw['flow_id'] != self.flow_id]

View file

@ -0,0 +1,85 @@
"""Test config flow."""
from unittest.mock import patch
import pytest
from homeassistant.setup import async_setup_component
from tests.common import mock_coro
@pytest.fixture(autouse=True)
def mock_finish_setup():
"""Mock out the finish setup method."""
with patch('homeassistant.components.mqtt.MQTT.async_connect',
return_value=mock_coro(True)) as mock_finish:
yield mock_finish
@pytest.fixture
def mock_try_connection():
"""Mock the try connection method."""
with patch(
'homeassistant.components.mqtt.config_flow.try_connection'
) as mock_try:
yield mock_try
async def test_user_connection_works(hass, mock_try_connection,
mock_finish_setup):
"""Test we can finish a config flow."""
mock_try_connection.return_value = True
result = await hass.config_entries.flow.async_init(
'mqtt', context={'source': 'user'})
assert result['type'] == 'form'
result = await hass.config_entries.flow.async_configure(
result['flow_id'], {
'broker': '127.0.0.1',
}
)
assert result['type'] == 'create_entry'
# Check we tried the connection
assert len(mock_try_connection.mock_calls) == 1
# Check config entry got setup
assert len(mock_finish_setup.mock_calls) == 1
async def test_user_connection_fails(hass, mock_try_connection,
mock_finish_setup):
"""Test if connnection cannot be made."""
mock_try_connection.return_value = False
result = await hass.config_entries.flow.async_init(
'mqtt', context={'source': 'user'})
assert result['type'] == 'form'
result = await hass.config_entries.flow.async_configure(
result['flow_id'], {
'broker': '127.0.0.1',
}
)
assert result['type'] == 'form'
assert result['errors']['base'] == 'cannot_connect'
# Check we tried the connection
assert len(mock_try_connection.mock_calls) == 1
# Check config entry did not setup
assert len(mock_finish_setup.mock_calls) == 0
async def test_manual_config_set(hass, mock_try_connection,
mock_finish_setup):
"""Test we ignore entry if manual config available."""
assert await async_setup_component(
hass, 'mqtt', {'mqtt': {'broker': 'bla'}})
assert len(mock_finish_setup.mock_calls) == 1
mock_try_connection.return_value = True
result = await hass.config_entries.flow.async_init(
'mqtt', context={'source': 'user'})
assert result['type'] == 'abort'

View file

@ -2,21 +2,29 @@
import asyncio import asyncio
import unittest import unittest
from unittest import mock from unittest import mock
import socket
import ssl import ssl
import pytest
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
import homeassistant.components.mqtt as mqtt from homeassistant.components import mqtt
from homeassistant.const import (EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE, from homeassistant.const import (EVENT_CALL_SERVICE, ATTR_DOMAIN, ATTR_SERVICE,
EVENT_HOMEASSISTANT_STOP) EVENT_HOMEASSISTANT_STOP)
from tests.common import (get_test_home_assistant, mock_coro, from tests.common import (get_test_home_assistant, mock_coro,
mock_mqtt_component, mock_mqtt_component,
threadsafe_coroutine_factory, fire_mqtt_message, threadsafe_coroutine_factory, fire_mqtt_message,
async_fire_mqtt_message) async_fire_mqtt_message, MockConfigEntry)
@pytest.fixture
def mock_MQTT():
"""Make sure connection is established."""
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT:
mock_MQTT.return_value.async_connect.return_value = mock_coro(True)
yield mock_MQTT
@asyncio.coroutine @asyncio.coroutine
@ -533,64 +541,59 @@ def test_setup_embedded_with_embedded(hass):
assert _start.call_count == 1 assert _start.call_count == 1
@asyncio.coroutine async def test_setup_fails_if_no_connect_broker(hass):
def test_setup_fails_if_no_connect_broker(hass):
"""Test for setup failure if connection to broker is missing.""" """Test for setup failure if connection to broker is missing."""
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker'}} entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker'
with mock.patch('homeassistant.components.mqtt.MQTT', })
side_effect=socket.error()):
result = yield from async_setup_component(hass, mqtt.DOMAIN,
test_broker_cfg)
assert not result
with mock.patch('paho.mqtt.client.Client') as mock_client: with mock.patch('paho.mqtt.client.Client') as mock_client:
mock_client().connect = lambda *args: 1 mock_client().connect = lambda *args: 1
result = yield from async_setup_component(hass, mqtt.DOMAIN, assert not await mqtt.async_setup_entry(hass, entry)
test_broker_cfg)
assert not result
@asyncio.coroutine async def test_setup_uses_certificate_on_certificate_set_to_auto(
def test_setup_uses_certificate_on_certificate_set_to_auto(hass): hass, mock_MQTT):
"""Test setup uses bundled certs when certificate is set to auto.""" """Test setup uses bundled certs when certificate is set to auto."""
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker', entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
'certificate': 'auto'}} mqtt.CONF_BROKER: 'test-broker',
'certificate': 'auto'
})
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT: assert await mqtt.async_setup_entry(hass, entry)
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
assert mock_MQTT.called assert mock_MQTT.called
import requests.certs import requests.certs
expectedCertificate = requests.certs.where() expectedCertificate = requests.certs.where()
assert mock_MQTT.mock_calls[0][1][7] == expectedCertificate assert mock_MQTT.mock_calls[0][2]['certificate'] == expectedCertificate
@asyncio.coroutine async def test_setup_does_not_use_certificate_on_mqtts_port(hass, mock_MQTT):
def test_setup_does_not_use_certificate_on_mqtts_port(hass): """Test setup doesn't use bundled certs when ssl set."""
"""Test setup doesn't use bundled certs when certificate is not set.""" entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker', mqtt.CONF_BROKER: 'test-broker',
'port': 8883}} 'port': 8883
})
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT: assert await mqtt.async_setup_entry(hass, entry)
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
assert mock_MQTT.called assert mock_MQTT.called
assert mock_MQTT.mock_calls[0][1][2] == 8883 assert mock_MQTT.mock_calls[0][2]['port'] == 8883
import requests.certs import requests.certs
mqttsCertificateBundle = requests.certs.where() mqttsCertificateBundle = requests.certs.where()
assert mock_MQTT.mock_calls[0][1][7] != mqttsCertificateBundle assert mock_MQTT.mock_calls[0][2]['port'] != mqttsCertificateBundle
@asyncio.coroutine async def test_setup_without_tls_config_uses_tlsv1_under_python36(
def test_setup_without_tls_config_uses_tlsv1_under_python36(hass): hass, mock_MQTT):
"""Test setup defaults to TLSv1 under python3.6.""" """Test setup defaults to TLSv1 under python3.6."""
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker'}} entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
mqtt.CONF_BROKER: 'test-broker',
})
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT: assert await mqtt.async_setup_entry(hass, entry)
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
assert mock_MQTT.called assert mock_MQTT.called
@ -600,34 +603,35 @@ def test_setup_without_tls_config_uses_tlsv1_under_python36(hass):
else: else:
expectedTlsVersion = ssl.PROTOCOL_TLSv1 expectedTlsVersion = ssl.PROTOCOL_TLSv1
assert mock_MQTT.mock_calls[0][1][14] == expectedTlsVersion assert mock_MQTT.mock_calls[0][2]['tls_version'] == expectedTlsVersion
@asyncio.coroutine async def test_setup_with_tls_config_uses_tls_version1_2(hass, mock_MQTT):
def test_setup_with_tls_config_uses_tls_version1_2(hass):
"""Test setup uses specified TLS version.""" """Test setup uses specified TLS version."""
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker', entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
'tls_version': '1.2'}} mqtt.CONF_BROKER: 'test-broker',
'tls_version': '1.2'
})
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT: assert await mqtt.async_setup_entry(hass, entry)
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
assert mock_MQTT.called assert mock_MQTT.called
assert mock_MQTT.mock_calls[0][1][14] == ssl.PROTOCOL_TLSv1_2 assert mock_MQTT.mock_calls[0][2]['tls_version'] == ssl.PROTOCOL_TLSv1_2
@asyncio.coroutine async def test_setup_with_tls_config_of_v1_under_python36_only_uses_v1(
def test_setup_with_tls_config_of_v1_under_python36_only_uses_v1(hass): hass, mock_MQTT):
"""Test setup uses TLSv1.0 if explicitly chosen.""" """Test setup uses TLSv1.0 if explicitly chosen."""
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker', entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
'tls_version': '1.0'}} mqtt.CONF_BROKER: 'test-broker',
'tls_version': '1.0'
})
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT: assert await mqtt.async_setup_entry(hass, entry)
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
assert mock_MQTT.called assert mock_MQTT.called
assert mock_MQTT.mock_calls[0][1][14] == ssl.PROTOCOL_TLSv1 assert mock_MQTT.mock_calls[0][2]['tls_version'] == ssl.PROTOCOL_TLSv1
@asyncio.coroutine @asyncio.coroutine
@ -671,3 +675,8 @@ def test_mqtt_subscribes_topics_on_connect(hass):
} }
calls = {call[1][1]: call[1][2] for call in hass.add_job.mock_calls} calls = {call[1][1]: call[1][2] for call in hass.add_job.mock_calls}
assert calls == expected assert calls == expected
async def test_setup_fails_without_config(hass):
"""Test if the MQTT component fails to load with no config."""
assert not await async_setup_component(hass, mqtt.DOMAIN, {})

View file

@ -57,8 +57,8 @@ class TestMQTT:
assert mock_mqtt.called assert mock_mqtt.called
from pprint import pprint from pprint import pprint
pprint(mock_mqtt.mock_calls) pprint(mock_mqtt.mock_calls)
assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant' assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
assert mock_mqtt.mock_calls[1][1][6] == password assert mock_mqtt.mock_calls[1][2]['password'] == password
@patch('passlib.apps.custom_app_context', Mock(return_value='')) @patch('passlib.apps.custom_app_context', Mock(return_value=''))
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock())) @patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
@ -82,24 +82,8 @@ class TestMQTT:
assert mock_mqtt.called assert mock_mqtt.called
from pprint import pprint from pprint import pprint
pprint(mock_mqtt.mock_calls) pprint(mock_mqtt.mock_calls)
assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant' assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
assert mock_mqtt.mock_calls[1][1][6] == password assert mock_mqtt.mock_calls[1][2]['password'] == password
@patch('passlib.apps.custom_app_context', Mock(return_value=''))
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', Mock(return_value=mock_coro()))
@patch('homeassistant.components.mqtt.MQTT')
def test_creating_config_without_pass(self, mock_mqtt):
"""Test if the MQTT server gets started without password."""
mock_mqtt().async_connect.return_value = mock_coro(True)
self.hass.bus.listen_once = MagicMock()
self.hass.config.api = MagicMock(api_password=None)
assert setup_component(self.hass, mqtt.DOMAIN, {})
assert mock_mqtt.called
assert mock_mqtt.mock_calls[1][1][5] is None
assert mock_mqtt.mock_calls[1][1][6] is None
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock())) @patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
@patch('hbmqtt.broker.Broker.start', return_value=mock_coro()) @patch('hbmqtt.broker.Broker.start', return_value=mock_coro())