From f62f64311d4661e9e910ad15da22f53c525609b2 Mon Sep 17 00:00:00 2001 From: cdce8p <30130371+cdce8p@users.noreply.github.com> Date: Sun, 22 Jul 2018 09:51:42 +0200 Subject: [PATCH 1/5] Bugfix HomeKit name and serial_number (#15600) * Bugfix HomeKit name and serial_number * Revert serial_number changes --- homeassistant/components/homekit/__init__.py | 19 ++++++++----- .../components/homekit/accessories.py | 4 +-- tests/components/homekit/test_accessories.py | 2 +- tests/components/homekit/test_homekit.py | 27 ++++++++++--------- 4 files changed, 31 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py index cb9387fb2c0ce5..611043c1b160b7 100644 --- a/homeassistant/components/homekit/__init__.py +++ b/homeassistant/components/homekit/__init__.py @@ -21,9 +21,10 @@ from homeassistant.util import get_local_ip from homeassistant.util.decorator import Registry from .const import ( - CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, CONF_FILTER, - DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, - DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH) + BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST, + CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO2, + DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START, + TYPE_OUTLET, TYPE_SWITCH) from .util import ( show_setup_message, validate_entity_config, validate_media_player_features) @@ -43,6 +44,8 @@ CONFIG_SCHEMA = vol.Schema({ DOMAIN: vol.All({ + vol.Optional(CONF_NAME, default=BRIDGE_NAME): + vol.All(cv.string, vol.Length(min=3, max=25)), vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_IP_ADDRESS): vol.All(ipaddress.ip_address, cv.string), @@ -58,13 +61,15 @@ async def async_setup(hass, config): _LOGGER.debug('Begin setup HomeKit') conf = config[DOMAIN] + name = conf[CONF_NAME] port = conf[CONF_PORT] ip_address = conf.get(CONF_IP_ADDRESS) auto_start = conf[CONF_AUTO_START] entity_filter = conf[CONF_FILTER] entity_config = conf[CONF_ENTITY_CONFIG] - homekit = HomeKit(hass, port, ip_address, entity_filter, entity_config) + homekit = HomeKit(hass, name, port, ip_address, entity_filter, + entity_config) await hass.async_add_job(homekit.setup) if auto_start: @@ -176,9 +181,11 @@ def generate_aid(entity_id): class HomeKit(): """Class to handle all actions between HomeKit and Home Assistant.""" - def __init__(self, hass, port, ip_address, entity_filter, entity_config): + def __init__(self, hass, name, port, ip_address, entity_filter, + entity_config): """Initialize a HomeKit object.""" self.hass = hass + self._name = name self._port = port self._ip_address = ip_address self._filter = entity_filter @@ -199,7 +206,7 @@ def setup(self): path = self.hass.config.path(HOMEKIT_FILE) self.driver = HomeDriver(self.hass, address=ip_addr, port=self._port, persist_file=path) - self.bridge = HomeBridge(self.hass, self.driver) + self.bridge = HomeBridge(self.hass, self.driver, self._name) def add_bridge_accessory(self, state): """Try adding accessory to bridge if configured beforehand.""" diff --git a/homeassistant/components/homekit/accessories.py b/homeassistant/components/homekit/accessories.py index d4e6d48c29f2ef..a7e895f49e2a2a 100644 --- a/homeassistant/components/homekit/accessories.py +++ b/homeassistant/components/homekit/accessories.py @@ -17,7 +17,7 @@ from homeassistant.util import dt as dt_util from .const import ( - BRIDGE_MODEL, BRIDGE_NAME, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL, + BRIDGE_MODEL, BRIDGE_SERIAL_NUMBER, CHAR_BATTERY_LEVEL, CHAR_CHARGING_STATE, CHAR_STATUS_LOW_BATTERY, DEBOUNCE_TIMEOUT, MANUFACTURER, SERV_BATTERY_SERVICE) from .util import ( @@ -141,7 +141,7 @@ def update_state(self, new_state): class HomeBridge(Bridge): """Adapter class for Bridge.""" - def __init__(self, hass, driver, name=BRIDGE_NAME): + def __init__(self, hass, driver, name): """Initialize a Bridge object.""" super().__init__(driver, name) self.set_info_service( diff --git a/tests/components/homekit/test_accessories.py b/tests/components/homekit/test_accessories.py index 59da90cc75bbc7..23706f02e75499 100644 --- a/tests/components/homekit/test_accessories.py +++ b/tests/components/homekit/test_accessories.py @@ -146,7 +146,7 @@ async def test_battery_service(hass, hk_driver): def test_home_bridge(hk_driver): """Test HomeBridge class.""" - bridge = HomeBridge('hass', hk_driver) + bridge = HomeBridge('hass', hk_driver, BRIDGE_NAME) assert bridge.hass == 'hass' assert bridge.display_name == BRIDGE_NAME assert bridge.category == 2 # Category.BRIDGE diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py index cc0370f01b186d..f8afb4a49ab419 100644 --- a/tests/components/homekit/test_homekit.py +++ b/tests/components/homekit/test_homekit.py @@ -9,9 +9,10 @@ STATUS_STOPPED, STATUS_WAIT) from homeassistant.components.homekit.accessories import HomeBridge from homeassistant.components.homekit.const import ( - CONF_AUTO_START, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE, SERVICE_HOMEKIT_START) + CONF_AUTO_START, BRIDGE_NAME, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE, + SERVICE_HOMEKIT_START) from homeassistant.const import ( - CONF_IP_ADDRESS, CONF_PORT, + CONF_NAME, CONF_IP_ADDRESS, CONF_PORT, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) from homeassistant.core import State from homeassistant.helpers.entityfilter import generate_filter @@ -47,7 +48,8 @@ async def test_setup_min(hass): assert await setup.async_setup_component( hass, DOMAIN, {DOMAIN: {}}) - mock_homekit.assert_any_call(hass, DEFAULT_PORT, None, ANY, {}) + mock_homekit.assert_any_call(hass, BRIDGE_NAME, DEFAULT_PORT, None, ANY, + {}) assert mock_homekit().setup.called is True # Test auto start enabled @@ -60,15 +62,16 @@ async def test_setup_min(hass): async def test_setup_auto_start_disabled(hass): """Test async_setup with auto start disabled and test service calls.""" - config = {DOMAIN: {CONF_AUTO_START: False, CONF_PORT: 11111, - CONF_IP_ADDRESS: '172.0.0.0'}} + config = {DOMAIN: {CONF_AUTO_START: False, CONF_NAME: 'Test Name', + CONF_PORT: 11111, CONF_IP_ADDRESS: '172.0.0.0'}} with patch(PATH_HOMEKIT + '.HomeKit') as mock_homekit: mock_homekit.return_value = homekit = Mock() assert await setup.async_setup_component( hass, DOMAIN, config) - mock_homekit.assert_any_call(hass, 11111, '172.0.0.0', ANY, {}) + mock_homekit.assert_any_call(hass, 'Test Name', 11111, '172.0.0.0', ANY, + {}) assert mock_homekit().setup.called is True # Test auto_start disabled @@ -96,7 +99,7 @@ async def test_setup_auto_start_disabled(hass): async def test_homekit_setup(hass, hk_driver): """Test setup of bridge and driver.""" - homekit = HomeKit(hass, DEFAULT_PORT, None, {}, {}) + homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, None, {}, {}) with patch(PATH_HOMEKIT + '.accessories.HomeDriver', return_value=hk_driver) as mock_driver, \ @@ -115,7 +118,7 @@ async def test_homekit_setup(hass, hk_driver): async def test_homekit_setup_ip_address(hass, hk_driver): """Test setup with given IP address.""" - homekit = HomeKit(hass, DEFAULT_PORT, '172.0.0.0', {}, {}) + homekit = HomeKit(hass, BRIDGE_NAME, DEFAULT_PORT, '172.0.0.0', {}, {}) with patch(PATH_HOMEKIT + '.accessories.HomeDriver', return_value=hk_driver) as mock_driver: @@ -126,7 +129,7 @@ async def test_homekit_setup_ip_address(hass, hk_driver): async def test_homekit_add_accessory(): """Add accessory if config exists and get_acc returns an accessory.""" - homekit = HomeKit('hass', None, None, lambda entity_id: True, {}) + homekit = HomeKit('hass', None, None, None, lambda entity_id: True, {}) homekit.driver = 'driver' homekit.bridge = mock_bridge = Mock() @@ -149,7 +152,7 @@ async def test_homekit_add_accessory(): async def test_homekit_entity_filter(hass): """Test the entity filter.""" entity_filter = generate_filter(['cover'], ['demo.test'], [], []) - homekit = HomeKit(hass, None, None, entity_filter, {}) + homekit = HomeKit(hass, None, None, None, entity_filter, {}) with patch(PATH_HOMEKIT + '.get_accessory') as mock_get_acc: mock_get_acc.return_value = None @@ -169,7 +172,7 @@ async def test_homekit_entity_filter(hass): async def test_homekit_start(hass, hk_driver, debounce_patcher): """Test HomeKit start method.""" pin = b'123-45-678' - homekit = HomeKit(hass, None, None, {}, {'cover.demo': {}}) + homekit = HomeKit(hass, None, None, None, {}, {'cover.demo': {}}) homekit.bridge = 'bridge' homekit.driver = hk_driver @@ -199,7 +202,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher): async def test_homekit_stop(hass): """Test HomeKit stop method.""" - homekit = HomeKit(hass, None, None, None, None) + homekit = HomeKit(hass, None, None, None, None, None) homekit.driver = Mock() assert homekit.status == STATUS_READY From 58f287f55115f2be42bad0e1bd0451e7d26d2442 Mon Sep 17 00:00:00 2001 From: Anders Melchiorsen Date: Mon, 23 Jul 2018 12:29:37 +0200 Subject: [PATCH 2/5] Use case insensitive comparison for Sonos model check (#15604) --- homeassistant/components/media_player/sonos.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py index da0ad24b135591..8a92d89ce675b0 100644 --- a/homeassistant/components/media_player/sonos.py +++ b/homeassistant/components/media_player/sonos.py @@ -865,9 +865,10 @@ def source_list(self): """List of available input sources.""" sources = [fav.title for fav in self._favorites] - if 'PLAY:5' in self._model or 'CONNECT' in self._model: + model = self._model.upper() + if 'PLAY:5' in model or 'CONNECT' in model: sources += [SOURCE_LINEIN] - elif 'PLAYBAR' in self._model: + elif 'PLAYBAR' in model: sources += [SOURCE_LINEIN, SOURCE_TV] return sources From 3eda6db227a25a98906b280b8a5b033bbb78fe16 Mon Sep 17 00:00:00 2001 From: Jason Hu Date: Sun, 22 Jul 2018 00:49:58 -0700 Subject: [PATCH 3/5] Frontend component should auto load auth coomponent (#15606) --- homeassistant/components/frontend/__init__.py | 3 ++- tests/components/frontend/test_init.py | 12 ++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py index 68e88406ad69d3..fb59d6254b046e 100644 --- a/homeassistant/components/frontend/__init__.py +++ b/homeassistant/components/frontend/__init__.py @@ -29,7 +29,8 @@ REQUIREMENTS = ['home-assistant-frontend==20180720.0'] DOMAIN = 'frontend' -DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', 'onboarding'] +DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log', + 'auth', 'onboarding'] CONF_THEMES = 'themes' CONF_EXTRA_HTML_URL = 'extra_html_url' diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py index 2125668facb8a9..4a950910809dd4 100644 --- a/tests/components/frontend/test_init.py +++ b/tests/components/frontend/test_init.py @@ -336,3 +336,15 @@ async def test_lovelace_ui_load_err(hass, hass_ws_client): assert msg['type'] == wapi.TYPE_RESULT assert msg['success'] is False assert msg['error']['code'] == 'load_error' + + +async def test_auth_load(mock_http_client): + """Test auth component loaded by default.""" + resp = await mock_http_client.get('/auth/providers') + assert resp.status == 200 + + +async def test_onboarding_load(mock_http_client): + """Test onboarding component loaded by default.""" + resp = await mock_http_client.get('/api/onboarding') + assert resp.status == 200 From 45a5ae1f23d61581244f8bedb51e3ebeb0ca7cd5 Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Mon, 23 Jul 2018 15:08:03 +0200 Subject: [PATCH 4/5] Cast/Sonos: create config entry if manually configured (#15630) * Cast/Sonos: create config entry if manually configured * Add test for helper --- homeassistant/components/cast/__init__.py | 10 ++++++- homeassistant/components/sonos/__init__.py | 10 ++++++- homeassistant/helpers/config_entry_flow.py | 12 +++++++++ tests/components/cast/test_init.py | 31 ++++++++++++++++++++++ tests/components/sonos/test_init.py | 27 +++++++++++++++++++ tests/helpers/test_config_entry_flow.py | 21 +++++++++++++++ 6 files changed, 109 insertions(+), 2 deletions(-) diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index a4ee25f0915c91..86c6152b6e2098 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -1,4 +1,5 @@ """Component to embed Google Cast.""" +from homeassistant import data_entry_flow from homeassistant.helpers import config_entry_flow @@ -8,7 +9,14 @@ async def async_setup(hass, config): """Set up the Cast component.""" - hass.data[DOMAIN] = config.get(DOMAIN, {}) + conf = config.get(DOMAIN) + + hass.data[DOMAIN] = conf or {} + + if conf is not None: + hass.async_create_task(hass.config_entries.flow.async_init( + DOMAIN, source=data_entry_flow.SOURCE_IMPORT)) + return True diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py index 7c3de210768152..a5a45417de2997 100644 --- a/homeassistant/components/sonos/__init__.py +++ b/homeassistant/components/sonos/__init__.py @@ -1,4 +1,5 @@ """Component to embed Sonos.""" +from homeassistant import data_entry_flow from homeassistant.helpers import config_entry_flow @@ -8,7 +9,14 @@ async def async_setup(hass, config): """Set up the Sonos component.""" - hass.data[DOMAIN] = config.get(DOMAIN, {}) + conf = config.get(DOMAIN) + + hass.data[DOMAIN] = conf or {} + + if conf is not None: + hass.async_create_task(hass.config_entries.flow.async_init( + DOMAIN, source=data_entry_flow.SOURCE_IMPORT)) + return True diff --git a/homeassistant/helpers/config_entry_flow.py b/homeassistant/helpers/config_entry_flow.py index 2a4ec2966df967..6f51d9aca2c49c 100644 --- a/homeassistant/helpers/config_entry_flow.py +++ b/homeassistant/helpers/config_entry_flow.py @@ -72,6 +72,18 @@ async def async_step_discovery(self, discovery_info): return await self.async_step_confirm() + async def async_step_import(self, _): + """Handle a flow initialized by import.""" + if self._async_in_progress() or self._async_current_entries(): + return self.async_abort( + reason='single_instance_allowed' + ) + + return self.async_create_entry( + title=self._title, + data={}, + ) + @callback def _async_current_entries(self): """Return current entries.""" diff --git a/tests/components/cast/test_init.py b/tests/components/cast/test_init.py index 260856c6742e2c..3ed9ea7b88e11b 100644 --- a/tests/components/cast/test_init.py +++ b/tests/components/cast/test_init.py @@ -2,6 +2,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow +from homeassistant.setup import async_setup_component from homeassistant.components import cast from tests.common import MockDependency, mock_coro @@ -20,3 +21,33 @@ async def test_creating_entry_sets_up_media_player(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + + +async def test_configuring_cast_creates_entry(hass): + """Test that specifying config will create an entry.""" + with patch('homeassistant.components.cast.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + MockDependency('pychromecast', 'discovery'), \ + patch('pychromecast.discovery.discover_chromecasts', + return_value=True): + await async_setup_component(hass, cast.DOMAIN, { + 'cast': { + 'some_config': 'to_trigger_import' + } + }) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + + +async def test_not_configuring_cast_not_creates_entry(hass): + """Test that no config will not create an entry.""" + with patch('homeassistant.components.cast.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + MockDependency('pychromecast', 'discovery'), \ + patch('pychromecast.discovery.discover_chromecasts', + return_value=True): + await async_setup_component(hass, cast.DOMAIN, {}) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 0 diff --git a/tests/components/sonos/test_init.py b/tests/components/sonos/test_init.py index 2cbc2360fd44d9..9fe22fc7e79ee5 100644 --- a/tests/components/sonos/test_init.py +++ b/tests/components/sonos/test_init.py @@ -2,6 +2,7 @@ from unittest.mock import patch from homeassistant import data_entry_flow +from homeassistant.setup import async_setup_component from homeassistant.components import sonos from tests.common import mock_coro @@ -18,3 +19,29 @@ async def test_creating_entry_sets_up_media_player(hass): await hass.async_block_till_done() assert len(mock_setup.mock_calls) == 1 + + +async def test_configuring_sonos_creates_entry(hass): + """Test that specifying config will create an entry.""" + with patch('homeassistant.components.sonos.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + patch('soco.discover', return_value=True): + await async_setup_component(hass, sonos.DOMAIN, { + 'sonos': { + 'some_config': 'to_trigger_import' + } + }) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 1 + + +async def test_not_configuring_sonos_not_creates_entry(hass): + """Test that no config will not create an entry.""" + with patch('homeassistant.components.sonos.async_setup_entry', + return_value=mock_coro(True)) as mock_setup, \ + patch('soco.discover', return_value=True): + await async_setup_component(hass, sonos.DOMAIN, {}) + await hass.async_block_till_done() + + assert len(mock_setup.mock_calls) == 0 diff --git a/tests/helpers/test_config_entry_flow.py b/tests/helpers/test_config_entry_flow.py index d3f13ac43021bc..19185e165bccd7 100644 --- a/tests/helpers/test_config_entry_flow.py +++ b/tests/helpers/test_config_entry_flow.py @@ -114,3 +114,24 @@ async def test_user_init_trumps_discovery(hass, flow_conf): # Discovery flow has been aborted assert len(hass.config_entries.flow.async_progress()) == 0 + + +async def test_import_no_confirmation(hass, flow_conf): + """Test import requires no confirmation to setup.""" + flow = config_entries.HANDLERS['test']() + flow.hass = hass + flow_conf['discovered'] = True + + result = await flow.async_step_import(None) + assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY + + +async def test_import_single_instance(hass, flow_conf): + """Test import doesn't create second instance.""" + flow = config_entries.HANDLERS['test']() + flow.hass = hass + flow_conf['discovered'] = True + MockConfigEntry(domain='test').add_to_hass(hass) + + result = await flow.async_step_import(None) + assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT From d65bd7b7eaf6a00d97001e4fc2d83253b1db7d0c Mon Sep 17 00:00:00 2001 From: Paulus Schoutsen Date: Tue, 24 Jul 2018 11:20:13 +0200 Subject: [PATCH 5/5] Bumped version to 0.74.1 --- homeassistant/const.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homeassistant/const.py b/homeassistant/const.py index 1627910f9bb5cd..df7ca73bd71608 100644 --- a/homeassistant/const.py +++ b/homeassistant/const.py @@ -2,7 +2,7 @@ """Constants used by Home Assistant components.""" MAJOR_VERSION = 0 MINOR_VERSION = 74 -PATCH_VERSION = '0' +PATCH_VERSION = '1' __short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION) __version__ = '{}.{}'.format(__short_version__, PATCH_VERSION) REQUIRED_PYTHON_VER = (3, 5, 3)