Catch ssl errors in rest (#91074)

* catch ssl.SSLError

* add test

* fail setup on ssl error

* adjust tests
This commit is contained in:
Michael 2023-04-12 06:51:41 +02:00 committed by GitHub
parent 2c9e9d0fde
commit 0916701a0b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 117 additions and 0 deletions

View File

@ -1,6 +1,9 @@
"""Support for RESTful binary sensors.""" """Support for RESTful binary sensors."""
from __future__ import annotations from __future__ import annotations
import logging
import ssl
import voluptuous as vol import voluptuous as vol
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
@ -31,6 +34,8 @@ from .data import RestData
from .entity import RestEntity from .entity import RestEntity
from .schema import BINARY_SENSOR_SCHEMA, RESOURCE_SCHEMA from .schema import BINARY_SENSOR_SCHEMA, RESOURCE_SCHEMA
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **BINARY_SENSOR_SCHEMA}) PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({**RESOURCE_SCHEMA, **BINARY_SENSOR_SCHEMA})
PLATFORM_SCHEMA = vol.All( PLATFORM_SCHEMA = vol.All(
@ -59,6 +64,13 @@ async def async_setup_platform(
if rest.data is None: if rest.data is None:
if rest.last_exception: if rest.last_exception:
if isinstance(rest.last_exception, ssl.SSLError):
_LOGGER.error(
"Error connecting %s failed with %s",
conf[CONF_RESOURCE],
rest.last_exception,
)
return
raise PlatformNotReady from rest.last_exception raise PlatformNotReady from rest.last_exception
raise PlatformNotReady raise PlatformNotReady

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import ssl
import httpx import httpx
@ -88,3 +89,11 @@ class RestData:
self.last_exception = ex self.last_exception = ex
self.data = None self.data = None
self.headers = None self.headers = None
except ssl.SSLError as ex:
if log_errors:
_LOGGER.error(
"Error connecting to %s failed with %s", self._resource, ex
)
self.last_exception = ex
self.data = None
self.headers = None

View File

@ -2,6 +2,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import ssl
from xml.parsers.expat import ExpatError from xml.parsers.expat import ExpatError
from jsonpath import jsonpath from jsonpath import jsonpath
@ -67,6 +68,13 @@ async def async_setup_platform(
if rest.data is None: if rest.data is None:
if rest.last_exception: if rest.last_exception:
if isinstance(rest.last_exception, ssl.SSLError):
_LOGGER.error(
"Error connecting %s failed with %s",
conf[CONF_RESOURCE],
rest.last_exception,
)
return
raise PlatformNotReady from rest.last_exception raise PlatformNotReady from rest.last_exception
raise PlatformNotReady raise PlatformNotReady

View File

@ -2,6 +2,7 @@
import asyncio import asyncio
from http import HTTPStatus from http import HTTPStatus
import ssl
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import httpx import httpx
@ -81,6 +82,28 @@ async def test_setup_failed_connect(
assert "server offline" in caplog.text assert "server offline" in caplog.text
@respx.mock
async def test_setup_fail_on_ssl_erros(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test setup when connection error occurs."""
respx.get("https://localhost").mock(side_effect=ssl.SSLError("ssl error"))
assert await async_setup_component(
hass,
BINARY_SENSOR_DOMAIN,
{
BINARY_SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "https://localhost",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(BINARY_SENSOR_DOMAIN)) == 0
assert "ssl error" in caplog.text
@respx.mock @respx.mock
async def test_setup_timeout(hass: HomeAssistant) -> None: async def test_setup_timeout(hass: HomeAssistant) -> None:
"""Test setup when connection timeout occurs.""" """Test setup when connection timeout occurs."""

View File

@ -3,8 +3,10 @@
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
from http import HTTPStatus from http import HTTPStatus
import ssl
from unittest.mock import patch from unittest.mock import patch
import pytest
import respx import respx
from homeassistant import config as hass_config from homeassistant import config as hass_config
@ -133,6 +135,46 @@ async def test_setup_with_endpoint_timeout_with_recovery(hass: HomeAssistant) ->
assert hass.states.get("binary_sensor.binary_sensor2").state == "off" assert hass.states.get("binary_sensor.binary_sensor2").state == "off"
@respx.mock
async def test_setup_with_ssl_error(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test setup with an ssl error."""
await async_setup_component(hass, "homeassistant", {})
respx.get("https://localhost").mock(side_effect=ssl.SSLError("ssl error"))
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "https://localhost",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"name": "sensor1",
"value_template": "{{ value_json.sensor1 }}",
},
],
"binary_sensor": [
{
"name": "binary_sensor1",
"value_template": "{{ value_json.binary_sensor1 }}",
},
],
}
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
assert "ssl error" in caplog.text
@respx.mock @respx.mock
async def test_setup_minimum_resource_template(hass: HomeAssistant) -> None: async def test_setup_minimum_resource_template(hass: HomeAssistant) -> None:
"""Test setup with minimum configuration (resource_template).""" """Test setup with minimum configuration (resource_template)."""

View File

@ -1,6 +1,7 @@
"""The tests for the REST sensor platform.""" """The tests for the REST sensor platform."""
import asyncio import asyncio
from http import HTTPStatus from http import HTTPStatus
import ssl
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
import httpx import httpx
@ -77,6 +78,28 @@ async def test_setup_failed_connect(
assert "server offline" in caplog.text assert "server offline" in caplog.text
@respx.mock
async def test_setup_fail_on_ssl_erros(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test setup when connection error occurs."""
respx.get("https://localhost").mock(side_effect=ssl.SSLError("ssl error"))
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "https://localhost",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
assert "ssl error" in caplog.text
@respx.mock @respx.mock
async def test_setup_timeout(hass: HomeAssistant) -> None: async def test_setup_timeout(hass: HomeAssistant) -> None:
"""Test setup when connection timeout occurs.""" """Test setup when connection timeout occurs."""