Allow separate URL for REST switch state (#39557)

This commit is contained in:
jjlawren 2020-09-04 09:58:40 -05:00 committed by GitHub
parent ebc31c0f08
commit f01a0f9151
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 73 additions and 22 deletions

View File

@ -30,6 +30,7 @@ _LOGGER = logging.getLogger(__name__)
CONF_BODY_OFF = "body_off" CONF_BODY_OFF = "body_off"
CONF_BODY_ON = "body_on" CONF_BODY_ON = "body_on"
CONF_IS_ON_TEMPLATE = "is_on_template" CONF_IS_ON_TEMPLATE = "is_on_template"
CONF_STATE_RESOURCE = "state_resource"
DEFAULT_METHOD = "post" DEFAULT_METHOD = "post"
DEFAULT_BODY_OFF = "OFF" DEFAULT_BODY_OFF = "OFF"
@ -43,6 +44,7 @@ SUPPORT_REST_METHODS = ["post", "put"]
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{ {
vol.Required(CONF_RESOURCE): cv.url, vol.Required(CONF_RESOURCE): cv.url,
vol.Optional(CONF_STATE_RESOURCE): cv.url,
vol.Optional(CONF_HEADERS): {cv.string: cv.string}, vol.Optional(CONF_HEADERS): {cv.string: cv.string},
vol.Optional(CONF_BODY_OFF, default=DEFAULT_BODY_OFF): cv.template, vol.Optional(CONF_BODY_OFF, default=DEFAULT_BODY_OFF): cv.template,
vol.Optional(CONF_BODY_ON, default=DEFAULT_BODY_ON): cv.template, vol.Optional(CONF_BODY_ON, default=DEFAULT_BODY_ON): cv.template,
@ -73,6 +75,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
username = config.get(CONF_USERNAME) username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
resource = config.get(CONF_RESOURCE) resource = config.get(CONF_RESOURCE)
state_resource = config.get(CONF_STATE_RESOURCE) or resource
verify_ssl = config.get(CONF_VERIFY_SSL) verify_ssl = config.get(CONF_VERIFY_SSL)
auth = None auth = None
@ -91,6 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info=
switch = RestSwitch( switch = RestSwitch(
name, name,
resource, resource,
state_resource,
method, method,
headers, headers,
auth, auth,
@ -122,6 +126,7 @@ class RestSwitch(SwitchEntity):
self, self,
name, name,
resource, resource,
state_resource,
method, method,
headers, headers,
auth, auth,
@ -135,6 +140,7 @@ class RestSwitch(SwitchEntity):
self._state = None self._state = None
self._name = name self._name = name
self._resource = resource self._resource = resource
self._state_resource = state_resource
self._method = method self._method = method
self._headers = headers self._headers = headers
self._auth = auth self._auth = auth
@ -213,7 +219,7 @@ class RestSwitch(SwitchEntity):
with async_timeout.timeout(self._timeout): with async_timeout.timeout(self._timeout):
req = await websession.get( req = await websession.get(
self._resource, auth=self._auth, headers=self._headers self._state_resource, auth=self._auth, headers=self._headers
) )
text = await req.text() text = await req.text()

View File

@ -4,7 +4,17 @@ import asyncio
import aiohttp import aiohttp
import homeassistant.components.rest.switch as rest import homeassistant.components.rest.switch as rest
from homeassistant.const import HTTP_INTERNAL_SERVER_ERROR from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.const import (
CONF_HEADERS,
CONF_NAME,
CONF_PLATFORM,
CONF_RESOURCE,
CONTENT_TYPE_JSON,
HTTP_INTERNAL_SERVER_ERROR,
HTTP_NOT_FOUND,
HTTP_OK,
)
from homeassistant.helpers.template import Template from homeassistant.helpers.template import Template
from homeassistant.setup import setup_component from homeassistant.setup import setup_component
@ -25,7 +35,7 @@ class TestRestSwitchSetup:
def test_setup_missing_config(self): def test_setup_missing_config(self):
"""Test setup with configuration missing required entries.""" """Test setup with configuration missing required entries."""
assert not asyncio.run_coroutine_threadsafe( assert not asyncio.run_coroutine_threadsafe(
rest.async_setup_platform(self.hass, {"platform": "rest"}, None), rest.async_setup_platform(self.hass, {CONF_PLATFORM: rest.DOMAIN}, None),
self.hass.loop, self.hass.loop,
).result() ).result()
@ -33,7 +43,9 @@ class TestRestSwitchSetup:
"""Test setup with resource missing schema.""" """Test setup with resource missing schema."""
assert not asyncio.run_coroutine_threadsafe( assert not asyncio.run_coroutine_threadsafe(
rest.async_setup_platform( rest.async_setup_platform(
self.hass, {"platform": "rest", "resource": "localhost"}, None self.hass,
{CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "localhost"},
None,
), ),
self.hass.loop, self.hass.loop,
).result() ).result()
@ -43,7 +55,9 @@ class TestRestSwitchSetup:
aioclient_mock.get("http://localhost", exc=aiohttp.ClientError) aioclient_mock.get("http://localhost", exc=aiohttp.ClientError)
assert not asyncio.run_coroutine_threadsafe( assert not asyncio.run_coroutine_threadsafe(
rest.async_setup_platform( rest.async_setup_platform(
self.hass, {"platform": "rest", "resource": "http://localhost"}, None self.hass,
{CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "http://localhost"},
None,
), ),
self.hass.loop, self.hass.loop,
).result() ).result()
@ -53,41 +67,70 @@ class TestRestSwitchSetup:
aioclient_mock.get("http://localhost", exc=asyncio.TimeoutError()) aioclient_mock.get("http://localhost", exc=asyncio.TimeoutError())
assert not asyncio.run_coroutine_threadsafe( assert not asyncio.run_coroutine_threadsafe(
rest.async_setup_platform( rest.async_setup_platform(
self.hass, {"platform": "rest", "resource": "http://localhost"}, None self.hass,
{CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "http://localhost"},
None,
), ),
self.hass.loop, self.hass.loop,
).result() ).result()
def test_setup_minimum(self, aioclient_mock): def test_setup_minimum(self, aioclient_mock):
"""Test setup with minimum configuration.""" """Test setup with minimum configuration."""
aioclient_mock.get("http://localhost", status=200) aioclient_mock.get("http://localhost", status=HTTP_OK)
with assert_setup_component(1, "switch"): with assert_setup_component(1, SWITCH_DOMAIN):
assert setup_component( assert setup_component(
self.hass, self.hass,
"switch", SWITCH_DOMAIN,
{"switch": {"platform": "rest", "resource": "http://localhost"}}, {
SWITCH_DOMAIN: {
CONF_PLATFORM: rest.DOMAIN,
CONF_RESOURCE: "http://localhost",
}
},
) )
assert aioclient_mock.call_count == 1 assert aioclient_mock.call_count == 1
def test_setup(self, aioclient_mock): def test_setup(self, aioclient_mock):
"""Test setup with valid configuration.""" """Test setup with valid configuration."""
aioclient_mock.get("http://localhost", status=200) aioclient_mock.get("http://localhost", status=HTTP_OK)
assert setup_component( assert setup_component(
self.hass, self.hass,
"switch", SWITCH_DOMAIN,
{ {
"switch": { SWITCH_DOMAIN: {
"platform": "rest", CONF_PLATFORM: rest.DOMAIN,
"name": "foo", CONF_NAME: "foo",
"resource": "http://localhost", CONF_RESOURCE: "http://localhost",
"headers": {"Content-type": "application/json"}, CONF_HEADERS: {"Content-type": CONTENT_TYPE_JSON},
"body_on": "custom on text", rest.CONF_BODY_ON: "custom on text",
"body_off": "custom off text", rest.CONF_BODY_OFF: "custom off text",
} }
}, },
) )
assert aioclient_mock.call_count == 1 assert aioclient_mock.call_count == 1
assert_setup_component(1, "switch") assert_setup_component(1, SWITCH_DOMAIN)
def test_setup_with_state_resource(self, aioclient_mock):
"""Test setup with valid configuration."""
aioclient_mock.get("http://localhost", status=HTTP_NOT_FOUND)
aioclient_mock.get("http://localhost/state", status=HTTP_OK)
assert setup_component(
self.hass,
SWITCH_DOMAIN,
{
SWITCH_DOMAIN: {
CONF_PLATFORM: rest.DOMAIN,
CONF_NAME: "foo",
CONF_RESOURCE: "http://localhost",
rest.CONF_STATE_RESOURCE: "http://localhost/state",
CONF_HEADERS: {"Content-type": "application/json"},
rest.CONF_BODY_ON: "custom on text",
rest.CONF_BODY_OFF: "custom off text",
}
},
)
assert aioclient_mock.call_count == 1
assert_setup_component(1, SWITCH_DOMAIN)
class TestRestSwitch: class TestRestSwitch:
@ -99,6 +142,7 @@ class TestRestSwitch:
self.name = "foo" self.name = "foo"
self.method = "post" self.method = "post"
self.resource = "http://localhost/" self.resource = "http://localhost/"
self.state_resource = self.resource
self.headers = {"Content-type": "application/json"} self.headers = {"Content-type": "application/json"}
self.auth = None self.auth = None
self.body_on = Template("on", self.hass) self.body_on = Template("on", self.hass)
@ -106,6 +150,7 @@ class TestRestSwitch:
self.switch = rest.RestSwitch( self.switch = rest.RestSwitch(
self.name, self.name,
self.resource, self.resource,
self.state_resource,
self.method, self.method,
self.headers, self.headers,
self.auth, self.auth,
@ -131,7 +176,7 @@ class TestRestSwitch:
def test_turn_on_success(self, aioclient_mock): def test_turn_on_success(self, aioclient_mock):
"""Test turn_on.""" """Test turn_on."""
aioclient_mock.post(self.resource, status=200) aioclient_mock.post(self.resource, status=HTTP_OK)
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.switch.async_turn_on(), self.hass.loop self.switch.async_turn_on(), self.hass.loop
).result() ).result()
@ -160,7 +205,7 @@ class TestRestSwitch:
def test_turn_off_success(self, aioclient_mock): def test_turn_off_success(self, aioclient_mock):
"""Test turn_off.""" """Test turn_off."""
aioclient_mock.post(self.resource, status=200) aioclient_mock.post(self.resource, status=HTTP_OK)
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
self.switch.async_turn_off(), self.hass.loop self.switch.async_turn_off(), self.hass.loop
).result() ).result()