home-assistant-core/tests/components/rest/test_init.py
Michael 0916701a0b
Catch ssl errors in rest (#91074)
* catch ssl.SSLError

* add test

* fail setup on ssl error

* adjust tests
2023-04-12 06:51:41 +02:00

479 lines
15 KiB
Python

"""Tests for rest component."""
import asyncio
from datetime import timedelta
from http import HTTPStatus
import ssl
from unittest.mock import patch
import pytest
import respx
from homeassistant import config as hass_config
from homeassistant.components.rest.const import DOMAIN
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_RELOAD,
STATE_UNAVAILABLE,
UnitOfInformation,
)
from homeassistant.core import HomeAssistant
from homeassistant.setup import async_setup_component
from homeassistant.util.dt import utcnow
from tests.common import (
assert_setup_component,
async_fire_time_changed,
get_fixture_path,
)
@respx.mock
async def test_setup_with_endpoint_timeout_with_recovery(hass: HomeAssistant) -> None:
"""Test setup with an endpoint that times out that recovers."""
await async_setup_component(hass, "homeassistant", {})
respx.get("http://localhost").mock(side_effect=asyncio.TimeoutError())
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "http://localhost",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"name": "sensor1",
"value_template": "{{ value_json.sensor1 }}",
},
{
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"name": "sensor2",
"value_template": "{{ value_json.sensor2 }}",
},
],
"binary_sensor": [
{
"name": "binary_sensor1",
"value_template": "{{ value_json.binary_sensor1 }}",
},
{
"name": "binary_sensor2",
"value_template": "{{ value_json.binary_sensor2 }}",
},
],
}
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 0
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={
"sensor1": "1",
"sensor2": "2",
"binary_sensor1": "on",
"binary_sensor2": "off",
},
)
# Refresh the coordinator
async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
# Wait for platform setup retry
async_fire_time_changed(hass, utcnow() + timedelta(seconds=61))
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
assert hass.states.get("sensor.sensor1").state == "1"
assert hass.states.get("sensor.sensor2").state == "2"
assert hass.states.get("binary_sensor.binary_sensor1").state == "on"
assert hass.states.get("binary_sensor.binary_sensor2").state == "off"
# Now the end point flakes out again
respx.get("http://localhost").mock(side_effect=asyncio.TimeoutError())
# Refresh the coordinator
async_fire_time_changed(hass, utcnow() + timedelta(seconds=31))
await hass.async_block_till_done()
assert hass.states.get("sensor.sensor1").state == STATE_UNAVAILABLE
assert hass.states.get("sensor.sensor2").state == STATE_UNAVAILABLE
assert hass.states.get("binary_sensor.binary_sensor1").state == STATE_UNAVAILABLE
assert hass.states.get("binary_sensor.binary_sensor2").state == STATE_UNAVAILABLE
# We request a manual refresh when the
# endpoint is working again
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={
"sensor1": "1",
"sensor2": "2",
"binary_sensor1": "on",
"binary_sensor2": "off",
},
)
await hass.services.async_call(
"homeassistant",
"update_entity",
{ATTR_ENTITY_ID: ["sensor.sensor1"]},
blocking=True,
)
assert hass.states.get("sensor.sensor1").state == "1"
assert hass.states.get("sensor.sensor2").state == "2"
assert hass.states.get("binary_sensor.binary_sensor1").state == "on"
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
async def test_setup_minimum_resource_template(hass: HomeAssistant) -> None:
"""Test setup with minimum configuration (resource_template)."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={
"sensor1": "1",
"sensor2": "2",
"binary_sensor1": "on",
"binary_sensor2": "off",
},
)
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource_template": "{% set url = 'http://localhost' %}{{ url }}",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"name": "sensor1",
"value_template": "{{ value_json.sensor1 }}",
},
{
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"name": "sensor2",
"value_template": "{{ value_json.sensor2 }}",
},
],
"binary_sensor": [
{
"name": "binary_sensor1",
"value_template": "{{ value_json.binary_sensor1 }}",
},
{
"name": "binary_sensor2",
"value_template": "{{ value_json.binary_sensor2 }}",
},
],
}
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
assert hass.states.get("sensor.sensor1").state == "1"
assert hass.states.get("sensor.sensor2").state == "2"
assert hass.states.get("binary_sensor.binary_sensor1").state == "on"
assert hass.states.get("binary_sensor.binary_sensor2").state == "off"
@respx.mock
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "http://localhost",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"name": "mockrest",
},
],
}
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert hass.states.get("sensor.mockrest")
yaml_path = get_fixture_path("configuration_top_level.yaml", "rest")
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
"rest",
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("sensor.mockreset") is None
assert hass.states.get("sensor.rollout")
assert hass.states.get("sensor.fallover")
@respx.mock
async def test_reload_and_remove_all(hass: HomeAssistant) -> None:
"""Verify we can reload and remove all."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "http://localhost",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"name": "mockrest",
},
],
}
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
assert hass.states.get("sensor.mockrest")
yaml_path = get_fixture_path("configuration_empty.yaml", "rest")
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
"rest",
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("sensor.mockreset") is None
@respx.mock
async def test_reload_fails_to_read_configuration(hass: HomeAssistant) -> None:
"""Verify reload when configuration is missing or broken."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "http://localhost",
"method": "GET",
"verify_ssl": "false",
"timeout": 30,
"sensor": [
{
"name": "mockrest",
},
],
}
]
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
yaml_path = get_fixture_path("configuration_invalid.notyaml", "rest")
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
"rest",
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
@respx.mock
async def test_multiple_rest_endpoints(hass: HomeAssistant) -> None:
"""Test multiple rest endpoints."""
respx.get("http://date.jsontest.com").respond(
status_code=HTTPStatus.OK,
json={
"date": "03-17-2021",
"milliseconds_since_epoch": 1616008268573,
"time": "07:11:08 PM",
},
)
respx.get("http://time.jsontest.com").respond(
status_code=HTTPStatus.OK,
json={
"date": "03-17-2021",
"milliseconds_since_epoch": 1616008299665,
"time": "07:11:39 PM",
},
)
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={
"value": "1",
},
)
assert await async_setup_component(
hass,
DOMAIN,
{
DOMAIN: [
{
"resource": "http://date.jsontest.com",
"sensor": [
{
"name": "JSON Date",
"value_template": "{{ value_json.date }}",
},
{
"name": "JSON Date Time",
"value_template": "{{ value_json.time }}",
},
],
},
{
"resource": "http://time.jsontest.com",
"sensor": [
{
"name": "JSON Time",
"value_template": "{{ value_json.time }}",
},
],
},
{
"resource": "http://localhost",
"binary_sensor": [
{
"name": "Binary Sensor",
"value_template": "{{ value_json.value }}",
},
],
},
]
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 4
assert hass.states.get("sensor.json_date").state == "03-17-2021"
assert hass.states.get("sensor.json_date_time").state == "07:11:08 PM"
assert hass.states.get("sensor.json_time").state == "07:11:39 PM"
assert hass.states.get("binary_sensor.binary_sensor").state == "on"
async def test_empty_config(hass: HomeAssistant) -> None:
"""Test setup with empty configuration.
For example (with rest.yaml an empty file):
rest: !include rest.yaml
"""
assert await async_setup_component(
hass,
DOMAIN,
{DOMAIN: {}},
)
assert_setup_component(0, DOMAIN)
async def test_config_schema_via_packages(hass: HomeAssistant) -> None:
"""Test configuration via packages."""
packages = {
"pack_dict": {"rest": {}},
"pack_11": {"rest": {"resource": "http://url1"}},
"pack_list": {"rest": [{"resource": "http://url2"}]},
}
config = {hass_config.CONF_CORE: {hass_config.CONF_PACKAGES: packages}}
await hass_config.merge_packages_config(hass, config, packages)
assert len(config) == 2
assert len(config["rest"]) == 2
assert config["rest"][0]["resource"] == "http://url1"
assert config["rest"][1]["resource"] == "http://url2"