478 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
			
		
		
	
	
			478 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
"""Tests for rest component."""
 | 
						|
 | 
						|
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=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=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.HA_DOMAIN: {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"
 |