mirror of
https://github.com/home-assistant/core.git
synced 2026-09-28 02:18:10 -04:00
Convert rest sensors to async using httpx (#41973)
This commit is contained in:
@@ -1,267 +1,381 @@
|
||||
"""The tests for the REST binary sensor platform."""
|
||||
import unittest
|
||||
|
||||
import pytest
|
||||
from pytest import raises
|
||||
import requests
|
||||
from requests.exceptions import Timeout
|
||||
import requests_mock
|
||||
import asyncio
|
||||
from os import path
|
||||
|
||||
import httpx
|
||||
import respx
|
||||
|
||||
from homeassistant import config as hass_config
|
||||
import homeassistant.components.binary_sensor as binary_sensor
|
||||
import homeassistant.components.rest.binary_sensor as rest
|
||||
from homeassistant.const import CONTENT_TYPE_JSON, STATE_OFF, STATE_ON
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import template
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
CONTENT_TYPE_JSON,
|
||||
SERVICE_RELOAD,
|
||||
STATE_OFF,
|
||||
STATE_ON,
|
||||
STATE_UNAVAILABLE,
|
||||
)
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.async_mock import Mock, patch
|
||||
from tests.common import assert_setup_component, get_test_home_assistant
|
||||
|
||||
|
||||
class TestRestBinarySensorSetup(unittest.TestCase):
|
||||
"""Tests for setting up the REST binary sensor platform."""
|
||||
|
||||
DEVICES = []
|
||||
|
||||
def add_devices(self, devices, update_before_add=False):
|
||||
"""Mock add devices."""
|
||||
for device in devices:
|
||||
self.DEVICES.append(device)
|
||||
|
||||
def setUp(self):
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
# Reset for this test.
|
||||
self.DEVICES = []
|
||||
self.addCleanup(self.hass.stop)
|
||||
|
||||
def test_setup_missing_config(self):
|
||||
"""Test setup with configuration missing required entries."""
|
||||
with assert_setup_component(0):
|
||||
assert setup_component(
|
||||
self.hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "rest"}}
|
||||
)
|
||||
|
||||
def test_setup_missing_schema(self):
|
||||
"""Test setup with resource missing schema."""
|
||||
with pytest.raises(PlatformNotReady):
|
||||
rest.setup_platform(
|
||||
self.hass,
|
||||
{"platform": "rest", "resource": "localhost", "method": "GET"},
|
||||
None,
|
||||
)
|
||||
|
||||
@patch("requests.Session.send", side_effect=requests.exceptions.ConnectionError())
|
||||
def test_setup_failed_connect(self, mock_req):
|
||||
"""Test setup when connection error occurs."""
|
||||
with raises(PlatformNotReady):
|
||||
rest.setup_platform(
|
||||
self.hass,
|
||||
{"platform": "rest", "resource": "http://localhost", "method": "GET"},
|
||||
self.add_devices,
|
||||
None,
|
||||
)
|
||||
assert len(self.DEVICES) == 0
|
||||
|
||||
@patch("requests.Session.send", side_effect=Timeout())
|
||||
def test_setup_timeout(self, mock_req):
|
||||
"""Test setup when connection timeout occurs."""
|
||||
with raises(PlatformNotReady):
|
||||
rest.setup_platform(
|
||||
self.hass,
|
||||
{"platform": "rest", "resource": "http://localhost", "method": "GET"},
|
||||
self.add_devices,
|
||||
None,
|
||||
)
|
||||
assert len(self.DEVICES) == 0
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_setup_minimum(self, mock_req):
|
||||
"""Test setup with minimum configuration."""
|
||||
mock_req.get("http://localhost", status_code=200)
|
||||
with assert_setup_component(1, "binary_sensor"):
|
||||
assert setup_component(
|
||||
self.hass,
|
||||
"binary_sensor",
|
||||
{"binary_sensor": {"platform": "rest", "resource": "http://localhost"}},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
assert 1 == mock_req.call_count
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_setup_minimum_resource_template(self, mock_req):
|
||||
"""Test setup with minimum configuration (resource_template)."""
|
||||
mock_req.get("http://localhost", status_code=200)
|
||||
with assert_setup_component(1, "binary_sensor"):
|
||||
assert setup_component(
|
||||
self.hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource_template": "http://localhost",
|
||||
}
|
||||
},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
assert mock_req.call_count == 1
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_setup_duplicate_resource(self, mock_req):
|
||||
"""Test setup with duplicate resources."""
|
||||
mock_req.get("http://localhost", status_code=200)
|
||||
with assert_setup_component(0, "binary_sensor"):
|
||||
assert setup_component(
|
||||
self.hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"resource_template": "http://localhost",
|
||||
}
|
||||
},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_setup_get(self, mock_req):
|
||||
"""Test setup with valid configuration."""
|
||||
mock_req.get("http://localhost", status_code=200)
|
||||
with assert_setup_component(1, "binary_sensor"):
|
||||
assert setup_component(
|
||||
self.hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.key }}",
|
||||
"name": "foo",
|
||||
"verify_ssl": "true",
|
||||
"authentication": "basic",
|
||||
"username": "my username",
|
||||
"password": "my password",
|
||||
"headers": {"Accept": CONTENT_TYPE_JSON},
|
||||
}
|
||||
},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
assert 1 == mock_req.call_count
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_setup_post(self, mock_req):
|
||||
"""Test setup with valid configuration."""
|
||||
mock_req.post("http://localhost", status_code=200)
|
||||
with assert_setup_component(1, "binary_sensor"):
|
||||
assert setup_component(
|
||||
self.hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "POST",
|
||||
"value_template": "{{ value_json.key }}",
|
||||
"payload": '{ "device": "toaster"}',
|
||||
"name": "foo",
|
||||
"verify_ssl": "true",
|
||||
"authentication": "basic",
|
||||
"username": "my username",
|
||||
"password": "my password",
|
||||
"headers": {"Accept": CONTENT_TYPE_JSON},
|
||||
}
|
||||
},
|
||||
)
|
||||
self.hass.block_till_done()
|
||||
assert 1 == mock_req.call_count
|
||||
async def test_setup_missing_basic_config(hass):
|
||||
"""Test setup with configuration missing required entries."""
|
||||
assert await async_setup_component(
|
||||
hass, binary_sensor.DOMAIN, {"binary_sensor": {"platform": "rest"}}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
class TestRestBinarySensor(unittest.TestCase):
|
||||
"""Tests for REST binary sensor platform."""
|
||||
async def test_setup_missing_config(hass):
|
||||
"""Test setup with configuration missing required entries."""
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "localhost",
|
||||
"method": "GET",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
def setUp(self):
|
||||
"""Set up things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
self.rest = Mock("RestData")
|
||||
self.rest.update = Mock(
|
||||
"RestData.update", side_effect=self.update_side_effect('{ "key": false }')
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_failed_connect(hass):
|
||||
"""Test setup when connection error occurs."""
|
||||
respx.get(
|
||||
"http://localhost", content=httpx.RequestError(message="any", request=Mock())
|
||||
)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_timeout(hass):
|
||||
"""Test setup when connection timeout occurs."""
|
||||
respx.get("http://localhost", content=asyncio.TimeoutError())
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "localhost",
|
||||
"method": "GET",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_minimum(hass):
|
||||
"""Test setup with minimum configuration."""
|
||||
respx.get("http://localhost", status_code=200)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_minimum_resource_template(hass):
|
||||
"""Test setup with minimum configuration (resource_template)."""
|
||||
respx.get("http://localhost", status_code=200)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource_template": "http://localhost",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_duplicate_resource_template(hass):
|
||||
"""Test setup with duplicate resources."""
|
||||
respx.get("http://localhost", status_code=200)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
binary_sensor.DOMAIN,
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"resource_template": "http://localhost",
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 0
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_get(hass):
|
||||
"""Test setup with valid configuration."""
|
||||
respx.get("http://localhost", status_code=200, content="{}")
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.key }}",
|
||||
"name": "foo",
|
||||
"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()) == 1
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_get_digest_auth(hass):
|
||||
"""Test setup with valid configuration."""
|
||||
respx.get("http://localhost", status_code=200, content="{}")
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.key }}",
|
||||
"name": "foo",
|
||||
"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()) == 1
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_post(hass):
|
||||
"""Test setup with valid configuration."""
|
||||
respx.post("http://localhost", status_code=200, content="{}")
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "POST",
|
||||
"value_template": "{{ value_json.key }}",
|
||||
"payload": '{ "device": "toaster"}',
|
||||
"name": "foo",
|
||||
"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()) == 1
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_get_off(hass):
|
||||
"""Test setup with valid off configuration."""
|
||||
respx.get(
|
||||
"http://localhost",
|
||||
status_code=200,
|
||||
headers={"content-type": "text/json"},
|
||||
content='{"dog": false}',
|
||||
)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.dog }}",
|
||||
"name": "foo",
|
||||
"verify_ssl": "true",
|
||||
"timeout": 30,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
state = hass.states.get("binary_sensor.foo")
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_get_on(hass):
|
||||
"""Test setup with valid on configuration."""
|
||||
respx.get(
|
||||
"http://localhost",
|
||||
status_code=200,
|
||||
headers={"content-type": "text/json"},
|
||||
content='{"dog": true}',
|
||||
)
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.dog }}",
|
||||
"name": "foo",
|
||||
"verify_ssl": "true",
|
||||
"timeout": 30,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
state = hass.states.get("binary_sensor.foo")
|
||||
assert state.state == STATE_ON
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_setup_with_exception(hass):
|
||||
"""Test setup with exception."""
|
||||
respx.get("http://localhost", status_code=200, content="{}")
|
||||
assert await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"resource": "http://localhost",
|
||||
"method": "GET",
|
||||
"value_template": "{{ value_json.dog }}",
|
||||
"name": "foo",
|
||||
"verify_ssl": "true",
|
||||
"timeout": 30,
|
||||
}
|
||||
},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
assert len(hass.states.async_all()) == 1
|
||||
|
||||
state = hass.states.get("binary_sensor.foo")
|
||||
assert state.state == STATE_OFF
|
||||
|
||||
await async_setup_component(hass, "homeassistant", {})
|
||||
await hass.async_block_till_done()
|
||||
|
||||
respx.clear()
|
||||
respx.get(
|
||||
"http://localhost", content=httpx.RequestError(message="any", request=Mock())
|
||||
)
|
||||
await hass.services.async_call(
|
||||
"homeassistant",
|
||||
"update_entity",
|
||||
{ATTR_ENTITY_ID: ["binary_sensor.foo"]},
|
||||
blocking=True,
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
state = hass.states.get("binary_sensor.foo")
|
||||
assert state.state == STATE_UNAVAILABLE
|
||||
|
||||
|
||||
@respx.mock
|
||||
async def test_reload(hass):
|
||||
"""Verify we can reload reset sensors."""
|
||||
|
||||
respx.get("http://localhost", status_code=200)
|
||||
|
||||
await async_setup_component(
|
||||
hass,
|
||||
"binary_sensor",
|
||||
{
|
||||
"binary_sensor": {
|
||||
"platform": "rest",
|
||||
"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()) == 1
|
||||
|
||||
assert hass.states.get("binary_sensor.mockrest")
|
||||
|
||||
yaml_path = path.join(
|
||||
_get_fixtures_base_path(),
|
||||
"fixtures",
|
||||
"rest/configuration.yaml",
|
||||
)
|
||||
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
|
||||
await hass.services.async_call(
|
||||
"rest",
|
||||
SERVICE_RELOAD,
|
||||
{},
|
||||
blocking=True,
|
||||
)
|
||||
self.name = "foo"
|
||||
self.device_class = "light"
|
||||
self.value_template = template.Template("{{ value_json.key }}", self.hass)
|
||||
self.force_update = False
|
||||
self.resource_template = None
|
||||
await hass.async_block_till_done()
|
||||
|
||||
self.binary_sensor = rest.RestBinarySensor(
|
||||
self.hass,
|
||||
self.rest,
|
||||
self.name,
|
||||
self.device_class,
|
||||
self.value_template,
|
||||
self.force_update,
|
||||
self.resource_template,
|
||||
)
|
||||
self.addCleanup(self.hass.stop)
|
||||
assert hass.states.get("binary_sensor.mockreset") is None
|
||||
assert hass.states.get("binary_sensor.rollout")
|
||||
|
||||
def update_side_effect(self, data):
|
||||
"""Side effect function for mocking RestData.update()."""
|
||||
self.rest.data = data
|
||||
|
||||
def test_name(self):
|
||||
"""Test the name."""
|
||||
assert self.name == self.binary_sensor.name
|
||||
|
||||
def test_device_class(self):
|
||||
"""Test the device class."""
|
||||
assert self.device_class == self.binary_sensor.device_class
|
||||
|
||||
def test_initial_state(self):
|
||||
"""Test the initial state."""
|
||||
self.binary_sensor.update()
|
||||
assert STATE_OFF == self.binary_sensor.state
|
||||
|
||||
def test_update_when_value_is_none(self):
|
||||
"""Test state gets updated to unknown when sensor returns no data."""
|
||||
self.rest.update = Mock(
|
||||
"RestData.update", side_effect=self.update_side_effect(None)
|
||||
)
|
||||
self.binary_sensor.update()
|
||||
assert not self.binary_sensor.available
|
||||
|
||||
def test_update_when_value_changed(self):
|
||||
"""Test state gets updated when sensor returns a new status."""
|
||||
self.rest.update = Mock(
|
||||
"rest.RestData.update",
|
||||
side_effect=self.update_side_effect('{ "key": true }'),
|
||||
)
|
||||
self.binary_sensor.update()
|
||||
assert STATE_ON == self.binary_sensor.state
|
||||
assert self.binary_sensor.available
|
||||
|
||||
def test_update_when_failed_request(self):
|
||||
"""Test state gets updated when sensor returns a new status."""
|
||||
self.rest.update = Mock(
|
||||
"rest.RestData.update", side_effect=self.update_side_effect(None)
|
||||
)
|
||||
self.binary_sensor.update()
|
||||
assert not self.binary_sensor.available
|
||||
|
||||
def test_update_with_no_template(self):
|
||||
"""Test update when there is no value template."""
|
||||
self.rest.update = Mock(
|
||||
"rest.RestData.update", side_effect=self.update_side_effect("true")
|
||||
)
|
||||
self.binary_sensor = rest.RestBinarySensor(
|
||||
self.hass,
|
||||
self.rest,
|
||||
self.name,
|
||||
self.device_class,
|
||||
None,
|
||||
self.force_update,
|
||||
self.resource_template,
|
||||
)
|
||||
self.binary_sensor.update()
|
||||
assert STATE_ON == self.binary_sensor.state
|
||||
assert self.binary_sensor.available
|
||||
def _get_fixtures_base_path():
|
||||
return path.dirname(path.dirname(path.dirname(__file__)))
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+6
@@ -4,6 +4,12 @@ sensor:
|
||||
method: GET
|
||||
name: rollout
|
||||
|
||||
binary_sensor:
|
||||
- platform: rest
|
||||
resource: "http://localhost"
|
||||
method: GET
|
||||
name: rollout
|
||||
|
||||
notify:
|
||||
- name: rest_reloaded
|
||||
platform: rest
|
||||
|
||||
Reference in New Issue
Block a user