diff --git a/homeassistant/components/nexia/__init__.py b/homeassistant/components/nexia/__init__.py index 25dce69417c..37a141407a4 100644 --- a/homeassistant/components/nexia/__init__.py +++ b/homeassistant/components/nexia/__init__.py @@ -9,18 +9,14 @@ from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry -from homeassistant.const import ( - CONF_PASSWORD, - CONF_USERNAME, - HTTP_BAD_REQUEST, - HTTP_INTERNAL_SERVER_ERROR, -) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryNotReady import homeassistant.helpers.config_validation as cv from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import DOMAIN, NEXIA_DEVICE, PLATFORMS, UPDATE_COORDINATOR +from .util import is_invalid_auth_code _LOGGER = logging.getLogger(__name__) @@ -81,10 +77,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): _LOGGER.error("Unable to connect to Nexia service: %s", ex) raise ConfigEntryNotReady from ex except HTTPError as http_ex: - if ( - http_ex.response.status_code >= HTTP_BAD_REQUEST - and http_ex.response.status_code < HTTP_INTERNAL_SERVER_ERROR - ): + if is_invalid_auth_code(http_ex.response.status_code): _LOGGER.error( "Access error from Nexia service, please check credentials: %s", http_ex ) diff --git a/homeassistant/components/nexia/config_flow.py b/homeassistant/components/nexia/config_flow.py index b5163d88f63..2054a5de421 100644 --- a/homeassistant/components/nexia/config_flow.py +++ b/homeassistant/components/nexia/config_flow.py @@ -6,14 +6,10 @@ from requests.exceptions import ConnectTimeout, HTTPError import voluptuous as vol from homeassistant import config_entries, core, exceptions -from homeassistant.const import ( - CONF_PASSWORD, - CONF_USERNAME, - HTTP_BAD_REQUEST, - HTTP_INTERNAL_SERVER_ERROR, -) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME from .const import DOMAIN # pylint:disable=unused-import +from .util import is_invalid_auth_code _LOGGER = logging.getLogger(__name__) @@ -42,10 +38,7 @@ async def validate_input(hass: core.HomeAssistant, data): raise CannotConnect from ex except HTTPError as http_ex: _LOGGER.error("HTTP error from Nexia service: %s", http_ex) - if ( - http_ex.response.status_code >= HTTP_BAD_REQUEST - and http_ex.response.status_code < HTTP_INTERNAL_SERVER_ERROR - ): + if is_invalid_auth_code(http_ex.response.status_code): raise InvalidAuth from http_ex raise CannotConnect from http_ex diff --git a/homeassistant/components/nexia/util.py b/homeassistant/components/nexia/util.py index d2ff10c8d34..665aa137065 100644 --- a/homeassistant/components/nexia/util.py +++ b/homeassistant/components/nexia/util.py @@ -1,5 +1,15 @@ """Utils for Nexia / Trane XL Thermostats.""" +from homeassistant.const import HTTP_FORBIDDEN, HTTP_UNAUTHORIZED + + +def is_invalid_auth_code(http_status_code): + """HTTP status codes that mean invalid auth.""" + if http_status_code in (HTTP_UNAUTHORIZED, HTTP_FORBIDDEN): + return True + + return False + def percent_conv(val): """Convert an actual percentage (0.0-1.0) to 0-100 scale.""" diff --git a/tests/components/nexia/test_config_flow.py b/tests/components/nexia/test_config_flow.py index 2d9ca00fbe3..944eb612038 100644 --- a/tests/components/nexia/test_config_flow.py +++ b/tests/components/nexia/test_config_flow.py @@ -1,5 +1,5 @@ """Test the nexia config flow.""" -from requests.exceptions import ConnectTimeout +from requests.exceptions import ConnectTimeout, HTTPError from homeassistant import config_entries, setup from homeassistant.components.nexia.const import DOMAIN @@ -80,6 +80,67 @@ async def test_form_cannot_connect(hass): assert result2["errors"] == {"base": "cannot_connect"} +async def test_form_invalid_auth_http_401(hass): + """Test we handle invalid auth error from http 401.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + response_mock = MagicMock() + type(response_mock).status_code = 401 + with patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + side_effect=HTTPError(response=response_mock), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "username", CONF_PASSWORD: "password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "invalid_auth"} + + +async def test_form_cannot_connect_not_found(hass): + """Test we handle cannot connect from an http not found error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + response_mock = MagicMock() + type(response_mock).status_code = 404 + with patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + side_effect=HTTPError(response=response_mock), + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "username", CONF_PASSWORD: "password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_broad_exception(hass): + """Test we handle invalid auth error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + with patch( + "homeassistant.components.nexia.config_flow.NexiaHome.login", + side_effect=ValueError, + ): + result2 = await hass.config_entries.flow.async_configure( + result["flow_id"], + {CONF_USERNAME: "username", CONF_PASSWORD: "password"}, + ) + + assert result2["type"] == "form" + assert result2["errors"] == {"base": "unknown"} + + async def test_form_import(hass): """Test we get the form with import source.""" await setup.async_setup_component(hass, "persistent_notification", {}) diff --git a/tests/components/nexia/test_util.py b/tests/components/nexia/test_util.py new file mode 100644 index 00000000000..0820c7caf0c --- /dev/null +++ b/tests/components/nexia/test_util.py @@ -0,0 +1,20 @@ +"""The sensor tests for the nexia platform.""" + + +from homeassistant.components.nexia import util +from homeassistant.const import HTTP_FORBIDDEN, HTTP_NOT_FOUND, HTTP_UNAUTHORIZED + + +async def test_is_invalid_auth_code(): + """Test for invalid auth.""" + + assert util.is_invalid_auth_code(HTTP_UNAUTHORIZED) is True + assert util.is_invalid_auth_code(HTTP_FORBIDDEN) is True + assert util.is_invalid_auth_code(HTTP_NOT_FOUND) is False + + +async def test_percent_conv(): + """Test percentage conversion.""" + + assert util.percent_conv(0.12) == 12.0 + assert util.percent_conv(0.123) == 12.3