MQTT config entry (#16594)
* MQTT config entry * Solely rely on config entry * Improve wawrning * Lint * Lint2
This commit is contained in:
parent
05c0717167
commit
72e746d240
11 changed files with 435 additions and 133 deletions
|
@ -14,6 +14,7 @@
|
|||
"data": {
|
||||
"2fa": "2FA Pin"
|
||||
},
|
||||
"description": "",
|
||||
"title": "2-Factor-Authentication"
|
||||
},
|
||||
"user": {
|
||||
|
@ -21,6 +22,7 @@
|
|||
"email": "E-Mail Address",
|
||||
"password": "Password"
|
||||
},
|
||||
"description": "",
|
||||
"title": "Google Hangouts Login"
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Access point is already configured",
|
||||
"conection_aborted": "Could not connect to HMIP server",
|
||||
"connection_aborted": "Could not connect to HMIP server",
|
||||
"unknown": "Unknown error occurred."
|
||||
},
|
||||
|
|
23
homeassistant/components/mqtt/.translations/en.json
Normal file
23
homeassistant/components/mqtt/.translations/en.json
Normal 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"
|
||||
}
|
||||
}
|
|
@ -18,6 +18,7 @@ import attr
|
|||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType, \
|
||||
ServiceDataType
|
||||
from homeassistant.core import callback, Event, ServiceCall
|
||||
|
@ -30,8 +31,12 @@ from homeassistant.util.async_ import (
|
|||
run_coroutine_threadsafe, run_callback_threadsafe)
|
||||
from homeassistant.const import (
|
||||
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
|
||||
|
||||
REQUIREMENTS = ['paho-mqtt==1.3.1']
|
||||
|
@ -41,11 +46,12 @@ _LOGGER = logging.getLogger(__name__)
|
|||
DOMAIN = 'mqtt'
|
||||
|
||||
DATA_MQTT = 'mqtt'
|
||||
DATA_MQTT_CONFIG = 'mqtt_config'
|
||||
|
||||
SERVICE_PUBLISH = 'publish'
|
||||
|
||||
CONF_EMBEDDED = 'embedded'
|
||||
CONF_BROKER = 'broker'
|
||||
|
||||
CONF_CLIENT_ID = 'client_id'
|
||||
CONF_DISCOVERY = 'discovery'
|
||||
CONF_DISCOVERY_PREFIX = 'discovery_prefix'
|
||||
|
@ -311,6 +317,7 @@ async def _async_setup_server(hass: HomeAssistantType,
|
|||
|
||||
if not success:
|
||||
return None
|
||||
|
||||
return broker_config
|
||||
|
||||
|
||||
|
@ -340,19 +347,15 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
|||
conf = config.get(DOMAIN) # type: Optional[ConfigType]
|
||||
|
||||
if conf is None:
|
||||
conf = CONFIG_SCHEMA({DOMAIN: {}})[DOMAIN]
|
||||
conf = cast(ConfigType, conf)
|
||||
# If we have a config entry, setup is done by that config entry.
|
||||
# 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]
|
||||
keepalive = conf.get(CONF_KEEPALIVE) # type: int
|
||||
conf = dict(conf)
|
||||
|
||||
# Only setup if embedded config passed in or no broker specified
|
||||
if CONF_EMBEDDED not in conf and CONF_BROKER in conf:
|
||||
broker_config = None
|
||||
else:
|
||||
if CONF_EMBEDDED in conf or CONF_BROKER not in conf:
|
||||
if (conf.get(CONF_PASSWORD) is None and
|
||||
config.get('http') is not None and
|
||||
config['http'].get('api_password') is not None):
|
||||
config.get('http', {}).get('api_password') is not None):
|
||||
_LOGGER.error(
|
||||
"Starting from release 0.76, the embedded MQTT broker does not"
|
||||
" 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)
|
||||
|
||||
if CONF_BROKER in conf:
|
||||
broker = conf[CONF_BROKER] # type: str
|
||||
port = conf[CONF_PORT] # type: int
|
||||
username = conf.get(CONF_USERNAME) # type: Optional[str]
|
||||
password = conf.get(CONF_PASSWORD) # type: Optional[str]
|
||||
certificate = conf.get(CONF_CERTIFICATE) # type: Optional[str]
|
||||
client_key = conf.get(CONF_CLIENT_KEY) # type: Optional[str]
|
||||
client_cert = conf.get(CONF_CLIENT_CERT) # type: Optional[str]
|
||||
tls_insecure = conf.get(CONF_TLS_INSECURE) # type: Optional[bool]
|
||||
protocol = conf[CONF_PROTOCOL] # type: str
|
||||
elif broker_config is not None:
|
||||
# If no broker passed in, auto config to internal server
|
||||
broker, port, username, password, certificate, protocol = broker_config
|
||||
# Embedded broker doesn't have some ssl variables
|
||||
client_key, client_cert, tls_insecure = None, None, None
|
||||
# hbmqtt requires a client id to be set.
|
||||
if client_id is None:
|
||||
client_id = 'home-assistant'
|
||||
else:
|
||||
err = "Unable to start MQTT broker."
|
||||
if conf.get(CONF_EMBEDDED) is not None:
|
||||
# Explicit embedded config, requires explicit broker config
|
||||
err += " (Broker configuration required.)"
|
||||
_LOGGER.error(err)
|
||||
if broker_config is None:
|
||||
_LOGGER.error('Unable to start embedded MQTT broker')
|
||||
return False
|
||||
|
||||
conf.update({
|
||||
CONF_BROKER: broker_config[0],
|
||||
CONF_PORT: broker_config[1],
|
||||
CONF_USERNAME: broker_config[2],
|
||||
CONF_PASSWORD: broker_config[3],
|
||||
CONF_CERTIFICATE: broker_config[4],
|
||||
CONF_PROTOCOL: broker_config[5],
|
||||
CONF_CLIENT_KEY: None,
|
||||
CONF_CLIENT_CERT: None,
|
||||
CONF_TLS_INSECURE: None,
|
||||
})
|
||||
|
||||
hass.data[DATA_MQTT_CONFIG] = conf
|
||||
|
||||
# Only import if we haven't before.
|
||||
if not hass.config_entries.async_entries(DOMAIN):
|
||||
hass.async_create_task(hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
|
||||
data={}
|
||||
))
|
||||
|
||||
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
|
||||
|
||||
# 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
|
||||
if (certificate is None and 19999 < port < 30000 and
|
||||
broker.endswith('.cloudmqtt.com')):
|
||||
if (conf.get(CONF_CERTIFICATE) is None and
|
||||
19999 < conf[CONF_PORT] < 30000 and
|
||||
conf[CONF_BROKER].endswith('.cloudmqtt.com')):
|
||||
certificate = os.path.join(os.path.dirname(__file__),
|
||||
'addtrustexternalcaroot.crt')
|
||||
|
||||
# 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()
|
||||
|
||||
will_message = None # type: Optional[Message]
|
||||
if conf.get(CONF_WILL_MESSAGE) is not None:
|
||||
will_message = Message(**conf.get(CONF_WILL_MESSAGE))
|
||||
birth_message = None # type: Optional[Message]
|
||||
if conf.get(CONF_BIRTH_MESSAGE) is not None:
|
||||
birth_message = Message(**conf.get(CONF_BIRTH_MESSAGE))
|
||||
else:
|
||||
certificate = None
|
||||
|
||||
if CONF_WILL_MESSAGE in conf:
|
||||
will_message = Message(**conf[CONF_WILL_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
|
||||
conf_tls_version = conf.get(CONF_TLS_VERSION) # type: str
|
||||
|
@ -421,14 +474,27 @@ async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
|
|||
else:
|
||||
tls_version = ssl.PROTOCOL_TLSv1
|
||||
|
||||
try:
|
||||
hass.data[DATA_MQTT] = MQTT(
|
||||
hass, broker, port, client_id, keepalive, username, password,
|
||||
certificate, client_key, client_cert, tls_insecure, protocol,
|
||||
will_message, birth_message, tls_version)
|
||||
except socket.error:
|
||||
_LOGGER.exception("Can't connect to the broker. "
|
||||
"Please check your settings and the broker itself")
|
||||
hass.data[DATA_MQTT] = MQTT(
|
||||
hass,
|
||||
broker=broker,
|
||||
port=port,
|
||||
client_id=client_id,
|
||||
keepalive=keepalive,
|
||||
username=username,
|
||||
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
|
||||
|
||||
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)
|
||||
|
||||
success = await hass.data[DATA_MQTT].async_connect() # type: bool
|
||||
if not success:
|
||||
return False
|
||||
|
||||
async def async_publish_service(call: ServiceCall):
|
||||
"""Handle MQTT publish service calls."""
|
||||
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,
|
||||
schema=MQTT_PUBLISH_SCHEMA)
|
||||
|
||||
if conf.get(CONF_DISCOVERY):
|
||||
await _async_setup_discovery(hass, config)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
|
@ -501,7 +560,8 @@ class MQTT:
|
|||
certificate: Optional[str], client_key: Optional[str],
|
||||
client_cert: Optional[str], tls_insecure: Optional[bool],
|
||||
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."""
|
||||
import paho.mqtt.client as mqtt
|
||||
|
||||
|
|
98
homeassistant/components/mqtt/config_flow.py
Normal file
98
homeassistant/components/mqtt/config_flow.py
Normal 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()
|
2
homeassistant/components/mqtt/const.py
Normal file
2
homeassistant/components/mqtt/const.py
Normal file
|
@ -0,0 +1,2 @@
|
|||
"""Constants used by multiple MQTT modules."""
|
||||
CONF_BROKER = 'broker'
|
23
homeassistant/components/mqtt/strings.json
Normal file
23
homeassistant/components/mqtt/strings.json
Normal 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."
|
||||
}
|
||||
}
|
||||
}
|
|
@ -141,6 +141,7 @@ FLOWS = [
|
|||
'homematicip_cloud',
|
||||
'hue',
|
||||
'ios',
|
||||
'mqtt',
|
||||
'nest',
|
||||
'openuv',
|
||||
'sonos',
|
||||
|
@ -463,3 +464,19 @@ class ConfigEntries:
|
|||
async def _old_conf_migrator(old_config):
|
||||
"""Migrate the pre-0.73 config format to the latest version."""
|
||||
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]
|
||||
|
|
85
tests/components/mqtt/test_config_flow.py
Normal file
85
tests/components/mqtt/test_config_flow.py
Normal 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'
|
|
@ -2,21 +2,29 @@
|
|||
import asyncio
|
||||
import unittest
|
||||
from unittest import mock
|
||||
import socket
|
||||
import ssl
|
||||
|
||||
import pytest
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
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,
|
||||
EVENT_HOMEASSISTANT_STOP)
|
||||
|
||||
from tests.common import (get_test_home_assistant, mock_coro,
|
||||
mock_mqtt_component,
|
||||
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
|
||||
|
@ -533,64 +541,59 @@ def test_setup_embedded_with_embedded(hass):
|
|||
assert _start.call_count == 1
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_setup_fails_if_no_connect_broker(hass):
|
||||
async def test_setup_fails_if_no_connect_broker(hass):
|
||||
"""Test for setup failure if connection to broker is missing."""
|
||||
test_broker_cfg = {mqtt.DOMAIN: {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
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
|
||||
mqtt.CONF_BROKER: 'test-broker'
|
||||
})
|
||||
|
||||
with mock.patch('paho.mqtt.client.Client') as mock_client:
|
||||
mock_client().connect = lambda *args: 1
|
||||
result = yield from async_setup_component(hass, mqtt.DOMAIN,
|
||||
test_broker_cfg)
|
||||
assert not result
|
||||
assert not await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_setup_uses_certificate_on_certificate_set_to_auto(hass):
|
||||
async def test_setup_uses_certificate_on_certificate_set_to_auto(
|
||||
hass, mock_MQTT):
|
||||
"""Test setup uses bundled certs when certificate is set to auto."""
|
||||
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker',
|
||||
'certificate': 'auto'}}
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
|
||||
mqtt.CONF_BROKER: 'test-broker',
|
||||
'certificate': 'auto'
|
||||
})
|
||||
|
||||
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT:
|
||||
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
assert mock_MQTT.called
|
||||
|
||||
import requests.certs
|
||||
expectedCertificate = requests.certs.where()
|
||||
assert mock_MQTT.mock_calls[0][1][7] == expectedCertificate
|
||||
assert mock_MQTT.mock_calls[0][2]['certificate'] == expectedCertificate
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_setup_does_not_use_certificate_on_mqtts_port(hass):
|
||||
"""Test setup doesn't use bundled certs when certificate is not set."""
|
||||
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker',
|
||||
'port': 8883}}
|
||||
async def test_setup_does_not_use_certificate_on_mqtts_port(hass, mock_MQTT):
|
||||
"""Test setup doesn't use bundled certs when ssl set."""
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
|
||||
mqtt.CONF_BROKER: 'test-broker',
|
||||
'port': 8883
|
||||
})
|
||||
|
||||
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT:
|
||||
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
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
|
||||
mqttsCertificateBundle = requests.certs.where()
|
||||
assert mock_MQTT.mock_calls[0][1][7] != mqttsCertificateBundle
|
||||
assert mock_MQTT.mock_calls[0][2]['port'] != mqttsCertificateBundle
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_setup_without_tls_config_uses_tlsv1_under_python36(hass):
|
||||
async def test_setup_without_tls_config_uses_tlsv1_under_python36(
|
||||
hass, mock_MQTT):
|
||||
"""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:
|
||||
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
assert mock_MQTT.called
|
||||
|
||||
|
@ -600,34 +603,35 @@ def test_setup_without_tls_config_uses_tlsv1_under_python36(hass):
|
|||
else:
|
||||
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
|
||||
def test_setup_with_tls_config_uses_tls_version1_2(hass):
|
||||
async def test_setup_with_tls_config_uses_tls_version1_2(hass, mock_MQTT):
|
||||
"""Test setup uses specified TLS version."""
|
||||
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker',
|
||||
'tls_version': '1.2'}}
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
|
||||
mqtt.CONF_BROKER: 'test-broker',
|
||||
'tls_version': '1.2'
|
||||
})
|
||||
|
||||
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT:
|
||||
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
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
|
||||
def test_setup_with_tls_config_of_v1_under_python36_only_uses_v1(hass):
|
||||
async def test_setup_with_tls_config_of_v1_under_python36_only_uses_v1(
|
||||
hass, mock_MQTT):
|
||||
"""Test setup uses TLSv1.0 if explicitly chosen."""
|
||||
test_broker_cfg = {mqtt.DOMAIN: {mqtt.CONF_BROKER: 'test-broker',
|
||||
'tls_version': '1.0'}}
|
||||
entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
|
||||
mqtt.CONF_BROKER: 'test-broker',
|
||||
'tls_version': '1.0'
|
||||
})
|
||||
|
||||
with mock.patch('homeassistant.components.mqtt.MQTT') as mock_MQTT:
|
||||
yield from async_setup_component(hass, mqtt.DOMAIN, test_broker_cfg)
|
||||
assert await mqtt.async_setup_entry(hass, entry)
|
||||
|
||||
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
|
||||
|
@ -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}
|
||||
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, {})
|
||||
|
|
|
@ -57,8 +57,8 @@ class TestMQTT:
|
|||
assert mock_mqtt.called
|
||||
from pprint import pprint
|
||||
pprint(mock_mqtt.mock_calls)
|
||||
assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant'
|
||||
assert mock_mqtt.mock_calls[1][1][6] == password
|
||||
assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
|
||||
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()))
|
||||
|
@ -82,24 +82,8 @@ class TestMQTT:
|
|||
assert mock_mqtt.called
|
||||
from pprint import pprint
|
||||
pprint(mock_mqtt.mock_calls)
|
||||
assert mock_mqtt.mock_calls[1][1][5] == 'homeassistant'
|
||||
assert mock_mqtt.mock_calls[1][1][6] == 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
|
||||
assert mock_mqtt.mock_calls[1][2]['username'] == 'homeassistant'
|
||||
assert mock_mqtt.mock_calls[1][2]['password'] == password
|
||||
|
||||
@patch('tempfile.NamedTemporaryFile', Mock(return_value=MagicMock()))
|
||||
@patch('hbmqtt.broker.Broker.start', return_value=mock_coro())
|
||||
|
|
Loading…
Add table
Reference in a new issue