core/tests/components/rest/test_sensor.py

1057 lines
34 KiB
Python

"""The tests for the REST sensor platform."""
from http import HTTPStatus
import ssl
from unittest.mock import AsyncMock, MagicMock, patch
import httpx
import pytest
import respx
from homeassistant import config as hass_config
from homeassistant.components.homeassistant import SERVICE_UPDATE_ENTITY
from homeassistant.components.rest import DOMAIN
from homeassistant.components.sensor import (
ATTR_STATE_CLASS,
DOMAIN as SENSOR_DOMAIN,
SensorDeviceClass,
SensorStateClass,
)
from homeassistant.const import (
ATTR_DEVICE_CLASS,
ATTR_ENTITY_ID,
ATTR_UNIT_OF_MEASUREMENT,
CONTENT_TYPE_JSON,
SERVICE_RELOAD,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfInformation,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.setup import async_setup_component
from homeassistant.util.ssl import SSLCipherList
from tests.common import get_fixture_path
async def test_setup_missing_config(hass: HomeAssistant) -> None:
"""Test setup with configuration missing required entries."""
assert await async_setup_component(
hass, SENSOR_DOMAIN, {SENSOR_DOMAIN: {"platform": DOMAIN}}
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
async def test_setup_missing_schema(hass: HomeAssistant) -> None:
"""Test setup with resource missing schema."""
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{SENSOR_DOMAIN: {"platform": DOMAIN, "resource": "localhost", "method": "GET"}},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
@respx.mock
async def test_setup_failed_connect(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test setup when connection error occurs."""
respx.get("http://localhost").mock(
side_effect=httpx.RequestError("server offline", request=MagicMock())
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
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
async def test_setup_timeout(hass: HomeAssistant) -> None:
"""Test setup when connection timeout occurs."""
respx.get("http://localhost").mock(side_effect=TimeoutError())
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{SENSOR_DOMAIN: {"platform": DOMAIN, "resource": "localhost", "method": "GET"}},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
@respx.mock
async def test_setup_minimum(hass: HomeAssistant) -> None:
"""Test setup with minimum configuration."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
@respx.mock
async def test_setup_encoding(hass: HomeAssistant) -> None:
"""Test setup with non-utf8 encoding."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
stream=httpx.ByteStream("tack själv".encode(encoding="iso-8859-1")),
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"name": "mysensor",
"encoding": "iso-8859-1",
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
assert hass.states.get("sensor.mysensor").state == "tack själv"
@respx.mock
@pytest.mark.parametrize(
("ssl_cipher_list", "ssl_cipher_list_expected"),
[
("python_default", SSLCipherList.PYTHON_DEFAULT),
("intermediate", SSLCipherList.INTERMEDIATE),
("modern", SSLCipherList.MODERN),
],
)
async def test_setup_ssl_ciphers(
hass: HomeAssistant, ssl_cipher_list: str, ssl_cipher_list_expected: SSLCipherList
) -> None:
"""Test setup with minimum configuration."""
with patch(
"homeassistant.components.rest.data.create_async_httpx_client",
return_value=MagicMock(request=AsyncMock(return_value=respx.MockResponse())),
) as httpx:
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"ssl_cipher_list": ssl_cipher_list,
}
},
)
await hass.async_block_till_done()
httpx.assert_called_once_with(
hass,
verify_ssl=True,
default_encoding="UTF-8",
ssl_cipher_list=ssl_cipher_list_expected,
)
@respx.mock
async def test_manual_update(hass: HomeAssistant) -> None:
"""Test setup with minimum configuration."""
await async_setup_component(hass, "homeassistant", {})
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"data": "first"}
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"name": "mysensor",
"value_template": "{{ value_json.data }}",
"platform": DOMAIN,
"resource_template": "{% set url = 'http://localhost' %}{{ url }}",
"method": "GET",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
assert hass.states.get("sensor.mysensor").state == "first"
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"data": "second"}
)
await hass.services.async_call(
"homeassistant",
"update_entity",
{ATTR_ENTITY_ID: ["sensor.mysensor"]},
blocking=True,
)
assert hass.states.get("sensor.mysensor").state == "second"
@respx.mock
async def test_setup_minimum_resource_template(hass: HomeAssistant) -> None:
"""Test setup with minimum configuration (resource_template)."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource_template": "{% set url = 'http://localhost' %}{{ url }}",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
@respx.mock
async def test_setup_duplicate_resource_template(hass: HomeAssistant) -> None:
"""Test setup with duplicate resources."""
respx.get("http://localhost") % HTTPStatus.OK
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"resource_template": "http://localhost",
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 0
@respx.mock
async def test_setup_get(hass: HomeAssistant) -> None:
"""Test setup with valid configuration."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "123"}
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"name": "foo",
"unit_of_measurement": UnitOfTemperature.CELSIUS,
"verify_ssl": "true",
"timeout": 30,
"authentication": "basic",
"username": "my username",
"password": "my password",
"headers": {"Accept": CONTENT_TYPE_JSON},
"device_class": SensorDeviceClass.TEMPERATURE,
"state_class": SensorStateClass.MEASUREMENT,
}
},
)
await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
assert hass.states.get("sensor.foo").state == "123"
await hass.services.async_call(
"homeassistant",
SERVICE_UPDATE_ENTITY,
{ATTR_ENTITY_ID: "sensor.foo"},
blocking=True,
)
await hass.async_block_till_done()
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfTemperature.CELSIUS
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TEMPERATURE
assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.MEASUREMENT
@respx.mock
async def test_setup_timestamp(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test setup with valid configuration."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "2021-11-11 11:39Z"}
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"device_class": SensorDeviceClass.TIMESTAMP,
}
},
)
await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.rest_sensor")
assert state.state == "2021-11-11T11:39:00+00:00"
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
assert "sensor.rest_sensor rendered invalid timestamp" not in caplog.text
assert "sensor.rest_sensor rendered timestamp without timezone" not in caplog.text
# Bad response: Not a timestamp
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "invalid time stamp"}
)
await hass.services.async_call(
"homeassistant",
"update_entity",
{ATTR_ENTITY_ID: ["sensor.rest_sensor"]},
blocking=True,
)
state = hass.states.get("sensor.rest_sensor")
assert state.state == "unknown"
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
assert "sensor.rest_sensor rendered invalid timestamp" in caplog.text
# Bad response: No timezone
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "2021-10-11 11:39"}
)
await hass.services.async_call(
"homeassistant",
"update_entity",
{ATTR_ENTITY_ID: ["sensor.rest_sensor"]},
blocking=True,
)
state = hass.states.get("sensor.rest_sensor")
assert state.state == "unknown"
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
assert "sensor.rest_sensor rendered timestamp without timezone" in caplog.text
@respx.mock
async def test_setup_get_templated_headers_params(hass: HomeAssistant) -> None:
"""Test setup with valid configuration."""
respx.get("http://localhost").respond(status_code=200, json={})
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"name": "foo",
"verify_ssl": "true",
"timeout": 30,
"headers": {
"Accept": CONTENT_TYPE_JSON,
"User-Agent": "Mozilla/{{ 3 + 2 }}.0",
},
"params": {
"start": 0,
"end": "{{ 3 + 2 }}",
},
}
},
)
await async_setup_component(hass, "homeassistant", {})
await hass.async_block_till_done()
assert respx.calls.last.request.headers["Accept"] == CONTENT_TYPE_JSON
assert respx.calls.last.request.headers["User-Agent"] == "Mozilla/5.0"
assert respx.calls.last.request.url.query == b"start=0&end=5"
@respx.mock
async def test_setup_get_digest_auth(hass: HomeAssistant) -> None:
"""Test setup with valid configuration."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "123"}
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
"authentication": "digest",
"username": "my username",
"password": "my password",
"headers": {"Accept": CONTENT_TYPE_JSON},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
@respx.mock
async def test_setup_post(hass: HomeAssistant) -> None:
"""Test setup with valid configuration."""
respx.post("http://localhost").respond(
status_code=HTTPStatus.OK, json={"key": "123"}
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "POST",
"value_template": "{{ value_json.key }}",
"payload": '{ "device": "toaster"}',
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
"authentication": "basic",
"username": "my username",
"password": "my password",
"headers": {"Accept": CONTENT_TYPE_JSON},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
@respx.mock
async def test_setup_get_xml(hass: HomeAssistant) -> None:
"""Test setup with valid xml configuration."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
content="<dog>123</dog>",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.dog }}",
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes[ATTR_UNIT_OF_MEASUREMENT] == UnitOfInformation.MEGABYTES
@respx.mock
async def test_setup_query_params(hass: HomeAssistant) -> None:
"""Test setup with query params."""
respx.get("http://localhost", params={"search": "something"}) % HTTPStatus.OK
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"params": {"search": "something"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
@respx.mock
async def test_update_with_json_attrs(hass: HomeAssistant) -> None:
"""Test attributes get extracted from a JSON result."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={"key": "123", "other_key": "some_json_value"},
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"json_attributes": ["other_key"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes["other_key"] == "some_json_value"
@respx.mock
async def test_update_with_no_template(hass: HomeAssistant) -> None:
"""Test update when there is no value template."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={"key": "some_json_value"},
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"json_attributes": ["key"],
"name": "foo",
"verify_ssl": "true",
"timeout": 30,
"headers": {"Accept": "text/xml"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == '{"key": "some_json_value"}'
@respx.mock
async def test_update_with_json_attrs_no_data(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test attributes when no JSON result fetched."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": CONTENT_TYPE_JSON},
content="",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"json_attributes": ["key"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
"headers": {"Accept": "text/xml"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == STATE_UNKNOWN
assert state.attributes == {"unit_of_measurement": "MB", "friendly_name": "foo"}
assert "Empty reply" in caplog.text
@respx.mock
async def test_update_with_json_attrs_not_dict(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test attributes get extracted from a JSON result."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json=["list", "of", "things"],
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"json_attributes": ["key"],
"name": "foo",
"verify_ssl": "true",
"timeout": 30,
"headers": {"Accept": "text/xml"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == ""
assert state.attributes == {"friendly_name": "foo"}
assert "not a dictionary or list" in caplog.text
@respx.mock
async def test_update_with_json_attrs_bad_JSON(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test attributes get extracted from a JSON result."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": CONTENT_TYPE_JSON},
content="This is text rather than JSON data.",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.key }}",
"json_attributes": ["key"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
"headers": {"Accept": "text/xml"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == STATE_UNKNOWN
assert state.attributes == {"unit_of_measurement": "MB", "friendly_name": "foo"}
assert "Erroneous JSON" in caplog.text
@respx.mock
async def test_update_with_json_attrs_with_json_attrs_path(hass: HomeAssistant) -> None:
"""Test attributes get extracted from a JSON result with a template for the attributes."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
json={
"toplevel": {
"master_value": "123",
"second_level": {
"some_json_key": "some_json_value",
"some_json_key2": "some_json_value2",
},
},
},
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.toplevel.master_value }}",
"json_attributes_path": "$.toplevel.second_level",
"json_attributes": ["some_json_key", "some_json_key2"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
"headers": {"Accept": "text/xml"},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes["some_json_key"] == "some_json_value"
assert state.attributes["some_json_key2"] == "some_json_value2"
@respx.mock
async def test_update_with_xml_convert_json_attrs_with_json_attrs_path(
hass: HomeAssistant,
) -> None:
"""Test attributes get extracted from a JSON result that was converted from XML with a template for the attributes."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
content="<toplevel><master_value>123</master_value><second_level><some_json_key>some_json_value</some_json_key><some_json_key2>some_json_value2</some_json_key2></second_level></toplevel>",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.toplevel.master_value }}",
"json_attributes_path": "$.toplevel.second_level",
"json_attributes": ["some_json_key", "some_json_key2"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes["some_json_key"] == "some_json_value"
assert state.attributes["some_json_key2"] == "some_json_value2"
@respx.mock
async def test_update_with_xml_convert_json_attrs_with_jsonattr_template(
hass: HomeAssistant,
) -> None:
"""Test attributes get extracted from a JSON result that was converted from XML."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
content='<?xml version="1.0" encoding="utf-8"?><response><scan>0</scan><ver>12556</ver><count>48</count><ssid>alexander</ssid><bss><valid>0</valid><name>0</name><privacy>0</privacy><wlan>123</wlan><strength>0</strength></bss><led0>0</led0><led1>0</led1><led2>0</led2><led3>0</led3><led4>0</led4><led5>0</led5><led6>0</led6><led7>0</led7><btn0>up</btn0><btn1>up</btn1><btn2>up</btn2><btn3>up</btn3><pot0>0</pot0><usr0>0</usr0><temp0>0x0XF0x0XF</temp0><time0> 0</time0></response>',
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.response.bss.wlan }}",
"json_attributes_path": "$.response",
"json_attributes": ["led0", "led1", "temp0", "time0", "ver"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "123"
assert state.attributes["led0"] == "0"
assert state.attributes["led1"] == "0"
assert state.attributes["temp0"] == "0x0XF0x0XF"
assert state.attributes["time0"] == "0"
assert state.attributes["ver"] == "12556"
@respx.mock
async def test_update_with_application_xml_convert_json_attrs_with_jsonattr_template(
hass: HomeAssistant,
) -> None:
"""Test attributes get extracted from a JSON result that was converted from XML with application/xml mime type."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "application/xml"},
content="<main><dog>1</dog><cat>3</cat></main>",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.main.dog }}",
"json_attributes_path": "$.main",
"json_attributes": ["dog", "cat"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == "1"
assert state.attributes["dog"] == "1"
assert state.attributes["cat"] == "3"
@respx.mock
@pytest.mark.parametrize(
("content", "error_message"),
[
("", "Empty reply"),
("<open></close>", "Erroneous JSON"),
],
)
async def test_update_with_xml_convert_bad_xml(
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
content: str,
error_message: str,
) -> None:
"""Test attributes get extracted from a XML result with bad xml."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
content=content,
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.toplevel.master_value }}",
"json_attributes": ["key"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == STATE_UNKNOWN
assert "REST xml result could not be parsed" in caplog.text
assert error_message in caplog.text
@respx.mock
async def test_update_with_failed_get(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
) -> None:
"""Test attributes get extracted from a XML result with bad xml."""
respx.get("http://localhost").respond(
status_code=HTTPStatus.OK,
headers={"content-type": "text/xml"},
content="",
)
assert await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"resource": "http://localhost",
"method": "GET",
"value_template": "{{ value_json.toplevel.master_value }}",
"json_attributes": ["key"],
"name": "foo",
"unit_of_measurement": UnitOfInformation.MEGABYTES,
"verify_ssl": "true",
"timeout": 30,
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
state = hass.states.get("sensor.foo")
assert state.state == STATE_UNKNOWN
assert "REST xml result could not be parsed" in caplog.text
assert "Empty reply" in caplog.text
@respx.mock
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload reset sensors."""
respx.get("http://localhost") % HTTPStatus.OK
await async_setup_component(
hass,
SENSOR_DOMAIN,
{
SENSOR_DOMAIN: {
"platform": DOMAIN,
"method": "GET",
"name": "mockrest",
"resource": "http://localhost",
}
},
)
await hass.async_block_till_done()
await hass.async_start()
await hass.async_block_till_done()
assert len(hass.states.async_all(SENSOR_DOMAIN)) == 1
assert hass.states.get("sensor.mockrest")
yaml_path = get_fixture_path("configuration.yaml", DOMAIN)
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
DOMAIN,
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert hass.states.get("sensor.mockreset") is None
assert hass.states.get("sensor.rollout")
@respx.mock
async def test_entity_config(
hass: HomeAssistant, entity_registry: er.EntityRegistry
) -> None:
"""Test entity configuration."""
config = {
SENSOR_DOMAIN: {
# REST configuration
"platform": DOMAIN,
"method": "GET",
"resource": "http://localhost",
# Entity configuration
"icon": "{{'mdi:one_two_three'}}",
"picture": "{{'blabla.png'}}",
"device_class": "temperature",
"name": "{{'REST' + ' ' + 'Sensor'}}",
"state_class": "measurement",
"unique_id": "very_unique",
"unit_of_measurement": "°C",
},
}
respx.get("http://localhost").respond(status_code=HTTPStatus.OK, text="123")
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
await hass.async_block_till_done()
assert entity_registry.async_get("sensor.rest_sensor").unique_id == "very_unique"
state = hass.states.get("sensor.rest_sensor")
assert state.state == "123"
assert state.attributes == {
"device_class": "temperature",
"entity_picture": "blabla.png",
"friendly_name": "REST Sensor",
"icon": "mdi:one_two_three",
"state_class": "measurement",
"unit_of_measurement": "°C",
}
@respx.mock
async def test_availability_in_config(hass: HomeAssistant) -> None:
"""Test entity configuration."""
config = {
SENSOR_DOMAIN: {
# REST configuration
"platform": DOMAIN,
"method": "GET",
"resource": "http://localhost",
# Entity configuration
"availability": "{{value==1}}",
"name": "{{'REST' + ' ' + 'Sensor'}}",
},
}
respx.get("http://localhost").respond(status_code=HTTPStatus.OK, text="123")
assert await async_setup_component(hass, SENSOR_DOMAIN, config)
await hass.async_block_till_done()
state = hass.states.get("sensor.rest_sensor")
assert state.state == STATE_UNAVAILABLE