From 53f8778cf728baa3713a125ef4a56250dabf7a39 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Mon, 21 Sep 2020 17:36:49 +0200 Subject: [PATCH 1/7] Abort config flow if firmware version is too low --- .../components/shelly/config_flow.py | 21 +++ homeassistant/components/shelly/manifest.json | 2 +- homeassistant/components/shelly/strings.json | 3 +- requirements_all.txt | 3 + requirements_test_all.txt | 3 + tests/components/shelly/test_config_flow.py | 121 ++++++++++++++++-- 6 files changed, 142 insertions(+), 11 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 0ebf70d2f00397..f900074cd1305e 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -1,10 +1,12 @@ """Config flow for Shelly integration.""" import asyncio import logging +import re import aiohttp import aioshelly import async_timeout +import semantic_version import voluptuous as vol from homeassistant import config_entries, core @@ -24,6 +26,8 @@ HTTP_CONNECT_ERRORS = (asyncio.TimeoutError, aiohttp.ClientError) +MIN_FIRMWARE_VERSION = "1.8.0" + async def validate_input(hass: core.HomeAssistant, host, data): """Validate the user input allows us to connect. @@ -45,6 +49,18 @@ async def validate_input(hass: core.HomeAssistant, host, data): return {"title": device.settings["name"], "mac": device.settings["device"]["mac"]} +def supported_firmware(ver_str): + """Return True if firmware version is supported.""" + ver_pattern = re.compile(r"(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)") + try: + ver = ver_pattern.search(ver_str)[0] + except TypeError: + return False + return semantic_version.Version.coerce(ver) > semantic_version.Version.coerce( + MIN_FIRMWARE_VERSION + ) + + class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Shelly.""" @@ -66,6 +82,8 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: + if not supported_firmware(info["fw"]): + return self.async_abort(reason="unsupported_firmware") await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host @@ -134,6 +152,9 @@ async def async_step_zeroconf(self, zeroconf_info): except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") + if not supported_firmware(info["fw"]): + return self.async_abort(reason="unsupported_firmware") + await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index eebf53dd69c1fd..2b84aabeb7cb0c 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.3.2"], + "requirements": ["aioshelly==0.3.2", "semantic_version==2.8.5"], "zeroconf": [{"type": "_http._tcp.local.", "name":"shelly*"}], "codeowners": ["@balloob", "@bieniu"] } diff --git a/homeassistant/components/shelly/strings.json b/homeassistant/components/shelly/strings.json index 16dc331e4523f8..1a7c8c7818918b 100644 --- a/homeassistant/components/shelly/strings.json +++ b/homeassistant/components/shelly/strings.json @@ -24,7 +24,8 @@ "unknown": "[%key:common::config_flow::error::unknown%]" }, "abort": { - "already_configured": "[%key:common::config_flow::abort::already_configured_device%]" + "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", + "unsupported_firmware": "The device is using an unsupported firmware version." } } } diff --git a/requirements_all.txt b/requirements_all.txt index c3f431ff8fbf5e..077a2347debe77 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1959,6 +1959,9 @@ schiene==0.23 # homeassistant.components.scsgate scsgate==0.1.0 +# homeassistant.components.shelly +semantic_version==2.8.5 + # homeassistant.components.sendgrid sendgrid==6.4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0c04b2f707ceb0..ab37825f9c877f 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -913,6 +913,9 @@ samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv samsungtvws==1.4.0 +# homeassistant.components.shelly +semantic_version==2.8.5 + # homeassistant.components.emulated_kasa # homeassistant.components.sense sense_energy==0.8.0 diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 366b52ca4e772d..165cd97e52243a 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -22,7 +22,12 @@ async def test_form(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ), patch( "aioshelly.Device.create", new=AsyncMock( @@ -62,7 +67,12 @@ async def test_form_auth(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": True, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -137,7 +147,12 @@ async def test_form_errors_test_connection(hass, error): ) with patch( - "aioshelly.get_info", return_value={"mac": "test-mac", "auth": False} + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ), patch( "aioshelly.Device.create", new=AsyncMock(side_effect=exc), @@ -165,7 +180,12 @@ async def test_form_already_configured(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -179,6 +199,36 @@ async def test_form_already_configured(hass): assert entry.data["host"] == "1.1.1.1" +async def test_form_firmware_unsupported(hass): + """Test we abort if device firmware is unsupported.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0"} + ) + entry.add_to_hass(hass) + + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/test-build@4a8bc427", + }, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1"}, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "unsupported_firmware" + + @pytest.mark.parametrize( "error", [ @@ -195,7 +245,14 @@ async def test_form_auth_errors_test_connection(hass, error): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch("aioshelly.get_info", return_value={"mac": "test-mac", "auth": True}): + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "auth": True, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, + ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -219,7 +276,12 @@ async def test_zeroconf(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -268,7 +330,12 @@ async def test_zeroconf_confirm_error(hass, error): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -301,7 +368,12 @@ async def test_zeroconf_already_configured(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -315,6 +387,32 @@ async def test_zeroconf_already_configured(hass): assert entry.data["host"] == "1.1.1.1" +async def test_zeroconf_firmware_unsupported(hass): + """Test we abort if device firmware is unsupported.""" + await setup.async_setup_component(hass, "persistent_notification", {}) + entry = MockConfigEntry( + domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0"} + ) + entry.add_to_hass(hass) + + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": False, + "fw": "20200827-070306/v1.7.0@4a8bc427", + }, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + assert result["type"] == "abort" + assert result["reason"] == "unsupported_firmware" + + async def test_zeroconf_cannot_connect(hass): """Test we get the form.""" with patch( @@ -336,7 +434,12 @@ async def test_zeroconf_require_auth(hass): with patch( "aioshelly.get_info", - return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True}, + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": True, + "fw": "20200827-070306/v1.8.4@4a8bc427", + }, ): result = await hass.config_entries.flow.async_init( DOMAIN, From db0464fdc11575a0441d33774de340c7739299fe Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Tue, 22 Sep 2020 17:38:28 +0200 Subject: [PATCH 2/7] Compare firmware date, not version number --- homeassistant/components/shelly/config_flow.py | 11 ++++------- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 3 --- requirements_test_all.txt | 3 --- tests/components/shelly/test_config_flow.py | 4 ++-- 5 files changed, 7 insertions(+), 16 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index f900074cd1305e..38f0e905599656 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -6,7 +6,6 @@ import aiohttp import aioshelly import async_timeout -import semantic_version import voluptuous as vol from homeassistant import config_entries, core @@ -26,7 +25,7 @@ HTTP_CONNECT_ERRORS = (asyncio.TimeoutError, aiohttp.ClientError) -MIN_FIRMWARE_VERSION = "1.8.0" +MIN_FIRMWARE_DATE = 20200812 async def validate_input(hass: core.HomeAssistant, host, data): @@ -51,14 +50,12 @@ async def validate_input(hass: core.HomeAssistant, host, data): def supported_firmware(ver_str): """Return True if firmware version is supported.""" - ver_pattern = re.compile(r"(?:(\d+)\.)?(?:(\d+)\.)?(?:(\d+)\.\d+)") + data_pattern = re.compile(r"^(\d{8})") try: - ver = ver_pattern.search(ver_str)[0] + data = int(data_pattern.search(ver_str)[0]) except TypeError: return False - return semantic_version.Version.coerce(ver) > semantic_version.Version.coerce( - MIN_FIRMWARE_VERSION - ) + return data > MIN_FIRMWARE_DATE class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index 2b84aabeb7cb0c..eebf53dd69c1fd 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.3.2", "semantic_version==2.8.5"], + "requirements": ["aioshelly==0.3.2"], "zeroconf": [{"type": "_http._tcp.local.", "name":"shelly*"}], "codeowners": ["@balloob", "@bieniu"] } diff --git a/requirements_all.txt b/requirements_all.txt index 077a2347debe77..c3f431ff8fbf5e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1959,9 +1959,6 @@ schiene==0.23 # homeassistant.components.scsgate scsgate==0.1.0 -# homeassistant.components.shelly -semantic_version==2.8.5 - # homeassistant.components.sendgrid sendgrid==6.4.6 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index ab37825f9c877f..0c04b2f707ceb0 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -913,9 +913,6 @@ samsungctl[websocket]==0.7.1 # homeassistant.components.samsungtv samsungtvws==1.4.0 -# homeassistant.components.shelly -semantic_version==2.8.5 - # homeassistant.components.emulated_kasa # homeassistant.components.sense sense_energy==0.8.0 diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 165cd97e52243a..b5835113f3b104 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -217,7 +217,7 @@ async def test_form_firmware_unsupported(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/test-build@4a8bc427", + "fw": "070306/test-build@4a8bc427", }, ): result2 = await hass.config_entries.flow.async_configure( @@ -401,7 +401,7 @@ async def test_zeroconf_firmware_unsupported(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.7.0@4a8bc427", + "fw": "20200421-070306/v1.7.0@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( From dc239091809b326a69b996d46b71b1dfecd4ee8a Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 23 Sep 2020 17:51:45 +0200 Subject: [PATCH 3/7] Use firmware version checking ftom aioshelly --- .../components/shelly/config_flow.py | 24 ++----- tests/components/shelly/test_config_flow.py | 64 ++++++++++++++----- 2 files changed, 55 insertions(+), 33 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 38f0e905599656..8ec087f1178a97 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -1,7 +1,6 @@ """Config flow for Shelly integration.""" import asyncio import logging -import re import aiohttp import aioshelly @@ -25,8 +24,6 @@ HTTP_CONNECT_ERRORS = (asyncio.TimeoutError, aiohttp.ClientError) -MIN_FIRMWARE_DATE = 20200812 - async def validate_input(hass: core.HomeAssistant, host, data): """Validate the user input allows us to connect. @@ -48,16 +45,6 @@ async def validate_input(hass: core.HomeAssistant, host, data): return {"title": device.settings["name"], "mac": device.settings["device"]["mac"]} -def supported_firmware(ver_str): - """Return True if firmware version is supported.""" - data_pattern = re.compile(r"^(\d{8})") - try: - data = int(data_pattern.search(ver_str)[0]) - except TypeError: - return False - return data > MIN_FIRMWARE_DATE - - class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle a config flow for Shelly.""" @@ -79,8 +66,6 @@ async def async_step_user(self, user_input=None): _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" else: - if not supported_firmware(info["fw"]): - return self.async_abort(reason="unsupported_firmware") await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: host}) self.host = host @@ -91,6 +76,8 @@ async def async_step_user(self, user_input=None): device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -117,6 +104,8 @@ async def async_step_credentials(self, user_input=None): errors["base"] = "cannot_connect" except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -149,9 +138,6 @@ async def async_step_zeroconf(self, zeroconf_info): except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") - if not supported_firmware(info["fw"]): - return self.async_abort(reason="unsupported_firmware") - await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) self.host = zeroconf_info["host"] @@ -170,6 +156,8 @@ async def async_step_confirm_discovery(self, user_input=None): device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index b5835113f3b104..1ef495c5f3b5d4 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -201,12 +201,6 @@ async def test_form_already_configured(hass): async def test_form_firmware_unsupported(hass): """Test we abort if device firmware is unsupported.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - entry = MockConfigEntry( - domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0"} - ) - entry.add_to_hass(hass) - result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_USER} ) @@ -389,19 +383,13 @@ async def test_zeroconf_already_configured(hass): async def test_zeroconf_firmware_unsupported(hass): """Test we abort if device firmware is unsupported.""" - await setup.async_setup_component(hass, "persistent_notification", {}) - entry = MockConfigEntry( - domain="shelly", unique_id="test-mac", data={"host": "0.0.0.0"} - ) - entry.add_to_hass(hass) - with patch( "aioshelly.get_info", return_value={ "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200421-070306/v1.7.0@4a8bc427", + "fw": "070306/test-build@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( @@ -409,8 +397,17 @@ async def test_zeroconf_firmware_unsupported(hass): data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, context={"source": config_entries.SOURCE_ZEROCONF}, ) - assert result["type"] == "abort" - assert result["reason"] == "unsupported_firmware" + + assert result["type"] == "form" + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1"}, + ) + + assert result2["type"] == "abort" + assert result2["reason"] == "unsupported_firmware" async def test_zeroconf_cannot_connect(hass): @@ -487,6 +484,43 @@ async def test_zeroconf_require_auth(hass): assert len(mock_setup_entry.mock_calls) == 1 +async def test_zeroconf_require_auth_firmware_unsupported(hass): + """Test we abort if device firmware is unsupported.""" + with patch( + "aioshelly.get_info", + return_value={ + "mac": "test-mac", + "type": "SHSW-1", + "auth": True, + "fw": "070306/test-build@4a8bc427", + }, + ): + result = await hass.config_entries.flow.async_init( + DOMAIN, + data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, + context={"source": config_entries.SOURCE_ZEROCONF}, + ) + + assert result["type"] == "form" + assert result["errors"] == {} + + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {"host": "1.1.1.1"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {} + + result3 = await hass.config_entries.flow.async_configure( + result2["flow_id"], + {"username": "test username", "password": "test password"}, + ) + + assert result3["type"] == "abort" + assert result3["reason"] == "unsupported_firmware" + + async def test_zeroconf_not_shelly(hass): """Test we filter out non-shelly devices.""" result = await hass.config_entries.flow.async_init( From 2bbeafa902ae456d479df82c912a1ddfcfaafcb1 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Wed, 23 Sep 2020 19:52:00 +0200 Subject: [PATCH 4/7] Catch FirmwareUnsupported when get_info() is called --- .../components/shelly/config_flow.py | 10 ++- tests/components/shelly/test_config_flow.py | 64 ++----------------- 2 files changed, 9 insertions(+), 65 deletions(-) diff --git a/homeassistant/components/shelly/config_flow.py b/homeassistant/components/shelly/config_flow.py index 8ec087f1178a97..b13c4090a1095a 100644 --- a/homeassistant/components/shelly/config_flow.py +++ b/homeassistant/components/shelly/config_flow.py @@ -62,6 +62,8 @@ async def async_step_user(self, user_input=None): info = await self._async_get_info(host) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -76,8 +78,6 @@ async def async_step_user(self, user_input=None): device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" - except aioshelly.FirmwareUnsupported: - return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -104,8 +104,6 @@ async def async_step_credentials(self, user_input=None): errors["base"] = "cannot_connect" except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" - except aioshelly.FirmwareUnsupported: - return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -137,6 +135,8 @@ async def async_step_zeroconf(self, zeroconf_info): self.info = info = await self._async_get_info(zeroconf_info["host"]) except HTTP_CONNECT_ERRORS: return self.async_abort(reason="cannot_connect") + except aioshelly.FirmwareUnsupported: + return self.async_abort(reason="unsupported_firmware") await self.async_set_unique_id(info["mac"]) self._abort_if_unique_id_configured({CONF_HOST: zeroconf_info["host"]}) @@ -156,8 +156,6 @@ async def async_step_confirm_discovery(self, user_input=None): device_info = await validate_input(self.hass, self.host, {}) except HTTP_CONNECT_ERRORS: errors["base"] = "cannot_connect" - except aioshelly.FirmwareUnsupported: - return self.async_abort(reason="unsupported_firmware") except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 1ef495c5f3b5d4..09088bfc776758 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -2,6 +2,7 @@ import asyncio import aiohttp +import aioshelly import pytest from homeassistant import config_entries, setup @@ -207,12 +208,7 @@ async def test_form_firmware_unsupported(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - "fw": "070306/test-build@4a8bc427", - }, + side_effect=aioshelly.FirmwareUnsupported, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -385,12 +381,7 @@ async def test_zeroconf_firmware_unsupported(hass): """Test we abort if device firmware is unsupported.""" with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - "fw": "070306/test-build@4a8bc427", - }, + side_effect=aioshelly.FirmwareUnsupported, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -398,16 +389,8 @@ async def test_zeroconf_firmware_unsupported(hass): context={"source": config_entries.SOURCE_ZEROCONF}, ) - assert result["type"] == "form" - assert result["errors"] == {} - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "1.1.1.1"}, - ) - - assert result2["type"] == "abort" - assert result2["reason"] == "unsupported_firmware" + assert result["type"] == "abort" + assert result["reason"] == "unsupported_firmware" async def test_zeroconf_cannot_connect(hass): @@ -484,43 +467,6 @@ async def test_zeroconf_require_auth(hass): assert len(mock_setup_entry.mock_calls) == 1 -async def test_zeroconf_require_auth_firmware_unsupported(hass): - """Test we abort if device firmware is unsupported.""" - with patch( - "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": True, - "fw": "070306/test-build@4a8bc427", - }, - ): - result = await hass.config_entries.flow.async_init( - DOMAIN, - data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, - context={"source": config_entries.SOURCE_ZEROCONF}, - ) - - assert result["type"] == "form" - assert result["errors"] == {} - - result2 = await hass.config_entries.flow.async_configure( - result["flow_id"], - {"host": "1.1.1.1"}, - ) - - assert result2["type"] == "form" - assert result2["errors"] == {} - - result3 = await hass.config_entries.flow.async_configure( - result2["flow_id"], - {"username": "test username", "password": "test password"}, - ) - - assert result3["type"] == "abort" - assert result3["reason"] == "unsupported_firmware" - - async def test_zeroconf_not_shelly(hass): """Test we filter out non-shelly devices.""" result = await hass.config_entries.flow.async_init( From 656ae9de3cb0aad1b87f7b7959442f9af01a5c5c Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 24 Sep 2020 09:35:23 +0200 Subject: [PATCH 5/7] Remove unnecessary code --- tests/components/shelly/test_config_flow.py | 9 --------- 1 file changed, 9 deletions(-) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index 09088bfc776758..eabbe965061fef 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -27,7 +27,6 @@ async def test_form(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ), patch( "aioshelly.Device.create", @@ -72,7 +71,6 @@ async def test_form_auth(hass): "mac": "test-mac", "type": "SHSW-1", "auth": True, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result2 = await hass.config_entries.flow.async_configure( @@ -152,7 +150,6 @@ async def test_form_errors_test_connection(hass, error): return_value={ "mac": "test-mac", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ), patch( "aioshelly.Device.create", @@ -185,7 +182,6 @@ async def test_form_already_configured(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result2 = await hass.config_entries.flow.async_configure( @@ -240,7 +236,6 @@ async def test_form_auth_errors_test_connection(hass, error): return_value={ "mac": "test-mac", "auth": True, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result2 = await hass.config_entries.flow.async_configure( @@ -270,7 +265,6 @@ async def test_zeroconf(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( @@ -324,7 +318,6 @@ async def test_zeroconf_confirm_error(hass, error): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( @@ -362,7 +355,6 @@ async def test_zeroconf_already_configured(hass): "mac": "test-mac", "type": "SHSW-1", "auth": False, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( @@ -418,7 +410,6 @@ async def test_zeroconf_require_auth(hass): "mac": "test-mac", "type": "SHSW-1", "auth": True, - "fw": "20200827-070306/v1.8.4@4a8bc427", }, ): result = await hass.config_entries.flow.async_init( From 511e07252cca0ec139729da26eae0d739220603c Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 24 Sep 2020 09:52:42 +0200 Subject: [PATCH 6/7] Black --- tests/components/shelly/test_config_flow.py | 81 ++++----------------- 1 file changed, 14 insertions(+), 67 deletions(-) diff --git a/tests/components/shelly/test_config_flow.py b/tests/components/shelly/test_config_flow.py index eabbe965061fef..03eb907d09b27d 100644 --- a/tests/components/shelly/test_config_flow.py +++ b/tests/components/shelly/test_config_flow.py @@ -23,11 +23,7 @@ async def test_form(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, ), patch( "aioshelly.Device.create", new=AsyncMock( @@ -67,11 +63,7 @@ async def test_form_auth(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": True, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True}, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -122,10 +114,7 @@ async def test_form_errors_get_info(hass, error): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "aioshelly.get_info", - side_effect=exc, - ): + with patch("aioshelly.get_info", side_effect=exc): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -146,15 +135,8 @@ async def test_form_errors_test_connection(hass, error): ) with patch( - "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "auth": False, - }, - ), patch( - "aioshelly.Device.create", - new=AsyncMock(side_effect=exc), - ): + "aioshelly.get_info", return_value={"mac": "test-mac", "auth": False} + ), patch("aioshelly.Device.create", new=AsyncMock(side_effect=exc)): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -178,11 +160,7 @@ async def test_form_already_configured(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], @@ -202,10 +180,7 @@ async def test_form_firmware_unsupported(hass): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "aioshelly.get_info", - side_effect=aioshelly.FirmwareUnsupported, - ): + with patch("aioshelly.get_info", side_effect=aioshelly.FirmwareUnsupported): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -231,13 +206,7 @@ async def test_form_auth_errors_test_connection(hass, error): DOMAIN, context={"source": config_entries.SOURCE_USER} ) - with patch( - "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "auth": True, - }, - ): + with patch("aioshelly.get_info", return_value={"mac": "test-mac", "auth": True}): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], {"host": "1.1.1.1"}, @@ -261,11 +230,7 @@ async def test_zeroconf(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -314,11 +279,7 @@ async def test_zeroconf_confirm_error(hass, error): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -351,11 +312,7 @@ async def test_zeroconf_already_configured(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": False, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": False}, ): result = await hass.config_entries.flow.async_init( DOMAIN, @@ -371,10 +328,7 @@ async def test_zeroconf_already_configured(hass): async def test_zeroconf_firmware_unsupported(hass): """Test we abort if device firmware is unsupported.""" - with patch( - "aioshelly.get_info", - side_effect=aioshelly.FirmwareUnsupported, - ): + with patch("aioshelly.get_info", side_effect=aioshelly.FirmwareUnsupported): result = await hass.config_entries.flow.async_init( DOMAIN, data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, @@ -387,10 +341,7 @@ async def test_zeroconf_firmware_unsupported(hass): async def test_zeroconf_cannot_connect(hass): """Test we get the form.""" - with patch( - "aioshelly.get_info", - side_effect=asyncio.TimeoutError, - ): + with patch("aioshelly.get_info", side_effect=asyncio.TimeoutError): result = await hass.config_entries.flow.async_init( DOMAIN, data={"host": "1.1.1.1", "name": "shelly1pm-12345"}, @@ -406,11 +357,7 @@ async def test_zeroconf_require_auth(hass): with patch( "aioshelly.get_info", - return_value={ - "mac": "test-mac", - "type": "SHSW-1", - "auth": True, - }, + return_value={"mac": "test-mac", "type": "SHSW-1", "auth": True}, ): result = await hass.config_entries.flow.async_init( DOMAIN, From 8156c27f3703192861db0b97f01666ce0f70d684 Mon Sep 17 00:00:00 2001 From: Maciej Bieniek Date: Thu, 24 Sep 2020 17:06:05 +0200 Subject: [PATCH 7/7] Bump aioshelly to version 0.3.3 --- homeassistant/components/shelly/manifest.json | 2 +- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/shelly/manifest.json b/homeassistant/components/shelly/manifest.json index eebf53dd69c1fd..1757bb28d9d30d 100644 --- a/homeassistant/components/shelly/manifest.json +++ b/homeassistant/components/shelly/manifest.json @@ -3,7 +3,7 @@ "name": "Shelly", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/shelly", - "requirements": ["aioshelly==0.3.2"], + "requirements": ["aioshelly==0.3.3"], "zeroconf": [{"type": "_http._tcp.local.", "name":"shelly*"}], "codeowners": ["@balloob", "@bieniu"] } diff --git a/requirements_all.txt b/requirements_all.txt index c3f431ff8fbf5e..b830db24b266cf 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -221,7 +221,7 @@ aiopvpc==2.0.2 aiopylgtv==0.3.3 # homeassistant.components.shelly -aioshelly==0.3.2 +aioshelly==0.3.3 # homeassistant.components.switcher_kis aioswitcher==1.2.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 0c04b2f707ceb0..6778d4fc76c0c3 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -134,7 +134,7 @@ aiopvpc==2.0.2 aiopylgtv==0.3.3 # homeassistant.components.shelly -aioshelly==0.3.2 +aioshelly==0.3.3 # homeassistant.components.switcher_kis aioswitcher==1.2.1