Convert rest sensors to async using httpx (#41973)

This commit is contained in:
J. Nick Koston
2020-10-16 19:21:13 -05:00
committed by GitHub
parent 39adf14079
commit ad6ce5fa83
12 changed files with 1094 additions and 970 deletions
+366 -252
View File
@@ -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
View File
@@ -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