From 72e746d240c42ed552cb3c10f5c44d9d210c58ad Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Fri, 14 Sep 2018 11:57:31 +0200 Subject: [PATCH] MQTT config entry (#16594) * MQTT config entry * Solely rely on config entry * Improve wawrning * Lint * Lint2 --- .../components/hangouts/.translations/en.json | 2 + .../homematicip_cloud/.translations/en.json | 1 - .../components/mqtt/.translations/en.json | 23 +++ homeassistant/components/mqtt/__init__.py | 182 ++++++++++++------ homeassistant/components/mqtt/config_flow.py | 98 ++++++++++ homeassistant/components/mqtt/const.py | 2 + homeassistant/components/mqtt/strings.json | 23 +++ homeassistant/config_entries.py | 17 ++ tests/components/mqtt/test_config_flow.py | 85 ++++++++ tests/components/mqtt/test_init.py | 111 ++++++----- tests/components/mqtt/test_server.py | 24 +-- 11 files changed, 435 insertions(+), 133 deletions(-) create mode 100644 homeassistant/components/mqtt/.translations/en.json create mode 100644 homeassistant/components/mqtt/config_flow.py create mode 100644 homeassistant/components/mqtt/const.py create mode 100644 homeassistant/components/mqtt/strings.json create mode 100644 tests/components/mqtt/test_config_flow.py diff --git a/homeassistant/components/hangouts/.translations/en.json b/homeassistant/components/hangouts/.translations/en.json index f526bec4f34..08d491e7009 100644 --- a/homeassistant/components/hangouts/.translations/en.json +++ b/homeassistant/components/hangouts/.translations/en.json @@ -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" } }, diff --git a/homeassistant/components/homematicip_cloud/.translations/en.json b/homeassistant/components/homematicip_cloud/.translations/en.json index 6fcfcddd75d..605bb0d250b 100644 --- a/homeassistant/components/homematicip_cloud/.translations/en.json +++ b/homeassistant/components/homematicip_cloud/.translations/en.json @@ -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." }, diff --git a/homeassistant/components/mqtt/.translations/en.json b/homeassistant/components/mqtt/.translations/en.json new file mode 100644 index 00000000000..1f0ed341bb6 --- /dev/null +++ b/homeassistant/components/mqtt/.translations/en.json @@ -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" + } +} \ No newline at end of file diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py index 6bb08d7e8e5..ac29671667f 100644 --- a/homeassistant/components/mqtt/__init__.py +++ b/homeassistant/components/mqtt/__init__.py @@ -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 diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py new file mode 100644 index 00000000000..42bc324f8fc --- /dev/null +++ b/homeassistant/components/mqtt/config_flow.py @@ -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() diff --git a/homeassistant/components/mqtt/const.py b/homeassistant/components/mqtt/const.py new file mode 100644 index 00000000000..8f9d938cf88 --- /dev/null +++ b/homeassistant/components/mqtt/const.py @@ -0,0 +1,2 @@ +"""Constants used by multiple MQTT modules.""" +CONF_BROKER = 'broker' diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json new file mode 100644 index 00000000000..a38983125ae --- /dev/null +++ b/homeassistant/components/mqtt/strings.json @@ -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." + } + } +} diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py index b06d6e7df55..b1e98d31048 100644 --- a/homeassistant/config_entries.py +++ b/homeassistant/config_entries.py @@ -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] diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py new file mode 100644 index 00000000000..4a4d783940f --- /dev/null +++ b/tests/components/mqtt/test_config_flow.py @@ -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' diff --git a/tests/components/mqtt/test_init.py b/tests/components/mqtt/test_init.py index 51bd75f66e3..831bcaa1d24 100644 --- a/tests/components/mqtt/test_init.py +++ b/tests/components/mqtt/test_init.py @@ -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, {}) diff --git a/tests/components/mqtt/test_server.py b/tests/components/mqtt/test_server.py index 976fdd3d15c..9f80f753690 100644 --- a/tests/components/mqtt/test_server.py +++ b/tests/components/mqtt/test_server.py @@ -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())