From f01a0f9151e6e382984f4b3018843532eaf2b5bb Mon Sep 17 00:00:00 2001 From: jjlawren Date: Fri, 4 Sep 2020 09:58:40 -0500 Subject: [PATCH] Allow separate URL for REST switch state (#39557) --- homeassistant/components/rest/switch.py | 8 ++- tests/components/rest/test_switch.py | 87 +++++++++++++++++++------ 2 files changed, 73 insertions(+), 22 deletions(-) diff --git a/homeassistant/components/rest/switch.py b/homeassistant/components/rest/switch.py index 6a9b4b2ac5b..865f4d01b3a 100644 --- a/homeassistant/components/rest/switch.py +++ b/homeassistant/components/rest/switch.py @@ -30,6 +30,7 @@ _LOGGER = logging.getLogger(__name__) CONF_BODY_OFF = "body_off" CONF_BODY_ON = "body_on" CONF_IS_ON_TEMPLATE = "is_on_template" +CONF_STATE_RESOURCE = "state_resource" DEFAULT_METHOD = "post" DEFAULT_BODY_OFF = "OFF" @@ -43,6 +44,7 @@ SUPPORT_REST_METHODS = ["post", "put"] PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( { vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_STATE_RESOURCE): cv.url, vol.Optional(CONF_HEADERS): {cv.string: cv.string}, vol.Optional(CONF_BODY_OFF, default=DEFAULT_BODY_OFF): 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) password = config.get(CONF_PASSWORD) resource = config.get(CONF_RESOURCE) + state_resource = config.get(CONF_STATE_RESOURCE) or resource verify_ssl = config.get(CONF_VERIFY_SSL) auth = None @@ -91,6 +94,7 @@ async def async_setup_platform(hass, config, async_add_entities, discovery_info= switch = RestSwitch( name, resource, + state_resource, method, headers, auth, @@ -122,6 +126,7 @@ class RestSwitch(SwitchEntity): self, name, resource, + state_resource, method, headers, auth, @@ -135,6 +140,7 @@ class RestSwitch(SwitchEntity): self._state = None self._name = name self._resource = resource + self._state_resource = state_resource self._method = method self._headers = headers self._auth = auth @@ -213,7 +219,7 @@ class RestSwitch(SwitchEntity): with async_timeout.timeout(self._timeout): 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() diff --git a/tests/components/rest/test_switch.py b/tests/components/rest/test_switch.py index bba6ac3dc89..065645fffd1 100644 --- a/tests/components/rest/test_switch.py +++ b/tests/components/rest/test_switch.py @@ -4,7 +4,17 @@ import asyncio import aiohttp 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.setup import setup_component @@ -25,7 +35,7 @@ class TestRestSwitchSetup: def test_setup_missing_config(self): """Test setup with configuration missing required entries.""" 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, ).result() @@ -33,7 +43,9 @@ class TestRestSwitchSetup: """Test setup with resource missing schema.""" assert not asyncio.run_coroutine_threadsafe( rest.async_setup_platform( - self.hass, {"platform": "rest", "resource": "localhost"}, None + self.hass, + {CONF_PLATFORM: rest.DOMAIN, CONF_RESOURCE: "localhost"}, + None, ), self.hass.loop, ).result() @@ -43,7 +55,9 @@ class TestRestSwitchSetup: aioclient_mock.get("http://localhost", exc=aiohttp.ClientError) assert not asyncio.run_coroutine_threadsafe( 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, ).result() @@ -53,41 +67,70 @@ class TestRestSwitchSetup: aioclient_mock.get("http://localhost", exc=asyncio.TimeoutError()) assert not asyncio.run_coroutine_threadsafe( 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, ).result() def test_setup_minimum(self, aioclient_mock): """Test setup with minimum configuration.""" - aioclient_mock.get("http://localhost", status=200) - with assert_setup_component(1, "switch"): + aioclient_mock.get("http://localhost", status=HTTP_OK) + with assert_setup_component(1, SWITCH_DOMAIN): assert setup_component( self.hass, - "switch", - {"switch": {"platform": "rest", "resource": "http://localhost"}}, + SWITCH_DOMAIN, + { + SWITCH_DOMAIN: { + CONF_PLATFORM: rest.DOMAIN, + CONF_RESOURCE: "http://localhost", + } + }, ) assert aioclient_mock.call_count == 1 def test_setup(self, aioclient_mock): """Test setup with valid configuration.""" - aioclient_mock.get("http://localhost", status=200) + aioclient_mock.get("http://localhost", status=HTTP_OK) assert setup_component( self.hass, - "switch", + SWITCH_DOMAIN, { - "switch": { - "platform": "rest", - "name": "foo", - "resource": "http://localhost", - "headers": {"Content-type": "application/json"}, - "body_on": "custom on text", - "body_off": "custom off text", + SWITCH_DOMAIN: { + CONF_PLATFORM: rest.DOMAIN, + CONF_NAME: "foo", + CONF_RESOURCE: "http://localhost", + CONF_HEADERS: {"Content-type": CONTENT_TYPE_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") + 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: @@ -99,6 +142,7 @@ class TestRestSwitch: self.name = "foo" self.method = "post" self.resource = "http://localhost/" + self.state_resource = self.resource self.headers = {"Content-type": "application/json"} self.auth = None self.body_on = Template("on", self.hass) @@ -106,6 +150,7 @@ class TestRestSwitch: self.switch = rest.RestSwitch( self.name, self.resource, + self.state_resource, self.method, self.headers, self.auth, @@ -131,7 +176,7 @@ class TestRestSwitch: def test_turn_on_success(self, aioclient_mock): """Test turn_on.""" - aioclient_mock.post(self.resource, status=200) + aioclient_mock.post(self.resource, status=HTTP_OK) asyncio.run_coroutine_threadsafe( self.switch.async_turn_on(), self.hass.loop ).result() @@ -160,7 +205,7 @@ class TestRestSwitch: def test_turn_off_success(self, aioclient_mock): """Test turn_off.""" - aioclient_mock.post(self.resource, status=200) + aioclient_mock.post(self.resource, status=HTTP_OK) asyncio.run_coroutine_threadsafe( self.switch.async_turn_off(), self.hass.loop ).result()