From 955c94e313a476d408967a2a09ae53b4ba60f353 Mon Sep 17 00:00:00 2001 From: Kit Klein <33464407+kit-klein@users.noreply.github.com> Date: Tue, 31 Mar 2020 18:50:37 -0400 Subject: [PATCH] allow overriding host api url in config flow (#33481) * allow overriding host api url in config flow * fix typo * capitalize URL --- .../konnected/.translations/en.json | 7 +++- .../components/konnected/__init__.py | 36 +++++++++++++------ .../components/konnected/config_flow.py | 26 ++++++++++++-- homeassistant/components/konnected/panel.py | 4 ++- .../components/konnected/strings.json | 8 +++-- .../components/konnected/test_config_flow.py | 25 ++++++++++--- tests/components/konnected/test_init.py | 5 +++ 7 files changed, 91 insertions(+), 20 deletions(-) diff --git a/homeassistant/components/konnected/.translations/en.json b/homeassistant/components/konnected/.translations/en.json index ae41b64ad98..3ace7783f8b 100644 --- a/homeassistant/components/konnected/.translations/en.json +++ b/homeassistant/components/konnected/.translations/en.json @@ -33,6 +33,9 @@ "abort": { "not_konn_panel": "Not a recognized Konnected.io device" }, + "error": { + "bad_host": "Invalid Override API host url" + }, "step": { "options_binary": { "data": { @@ -82,7 +85,9 @@ }, "options_misc": { "data": { - "blink": "Blink panel LED on when sending state change" + "api_host": "Override API host URL (optional)", + "blink": "Blink panel LED on when sending state change", + "override_api_host": "Override default Home Assistant API host panel URL" }, "description": "Please select the desired behavior for your panel", "title": "Configure Misc" diff --git a/homeassistant/components/konnected/__init__.py b/homeassistant/components/konnected/__init__.py index 72d82fd31be..e5185ff03bc 100644 --- a/homeassistant/components/konnected/__init__.py +++ b/homeassistant/components/konnected/__init__.py @@ -91,7 +91,7 @@ def ensure_zone(value): return str(value) -def import_validator(config): +def import_device_validator(config): """Validate zones and reformat for import.""" config = copy.deepcopy(config) io_cfgs = {} @@ -117,10 +117,22 @@ def import_validator(config): config.pop(CONF_SWITCHES, None) config.pop(CONF_BLINK, None) config.pop(CONF_DISCOVERY, None) + config.pop(CONF_API_HOST, None) config.pop(CONF_IO, None) return config +def import_validator(config): + """Reformat for import.""" + config = copy.deepcopy(config) + + # push api_host into device configs + for device in config.get(CONF_DEVICES, []): + device[CONF_API_HOST] = config.get(CONF_API_HOST, "") + + return config + + # configuration.yaml schemas (legacy) BINARY_SENSOR_SCHEMA_YAML = vol.All( vol.Schema( @@ -179,23 +191,27 @@ DEVICE_SCHEMA_YAML = vol.All( vol.Inclusive(CONF_HOST, "host_info"): cv.string, vol.Inclusive(CONF_PORT, "host_info"): cv.port, vol.Optional(CONF_BLINK, default=True): cv.boolean, + vol.Optional(CONF_API_HOST, default=""): vol.Any("", cv.url), vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, } ), - import_validator, + import_device_validator, ) # pylint: disable=no-value-for-parameter CONFIG_SCHEMA = vol.Schema( { - DOMAIN: vol.Schema( - { - vol.Required(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_API_HOST): vol.Url(), - vol.Optional(CONF_DEVICES): vol.All( - cv.ensure_list, [DEVICE_SCHEMA_YAML] - ), - } + DOMAIN: vol.All( + import_validator, + vol.Schema( + { + vol.Required(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_API_HOST): vol.Url(), + vol.Optional(CONF_DEVICES): vol.All( + cv.ensure_list, [DEVICE_SCHEMA_YAML] + ), + } + ), ) }, extra=vol.ALLOW_EXTRA, diff --git a/homeassistant/components/konnected/config_flow.py b/homeassistant/components/konnected/config_flow.py index 172f60cd42d..6a3631a8c0d 100644 --- a/homeassistant/components/konnected/config_flow.py +++ b/homeassistant/components/konnected/config_flow.py @@ -31,6 +31,7 @@ from homeassistant.helpers import config_validation as cv from .const import ( CONF_ACTIVATION, + CONF_API_HOST, CONF_BLINK, CONF_DEFAULT_OPTIONS, CONF_DISCOVERY, @@ -61,6 +62,8 @@ CONF_MORE_STATES = "more_states" CONF_YES = "Yes" CONF_NO = "No" +CONF_OVERRIDE_API_HOST = "override_api_host" + KONN_MANUFACTURER = "konnected.io" KONN_PANEL_MODEL_NAMES = { KONN_MODEL: "Konnected Alarm Panel", @@ -138,6 +141,7 @@ OPTIONS_SCHEMA = vol.Schema( vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_SCHEMA]), vol.Optional(CONF_SWITCHES): vol.All(cv.ensure_list, [SWITCH_SCHEMA]), vol.Optional(CONF_BLINK, default=True): cv.boolean, + vol.Optional(CONF_API_HOST, default=""): vol.Any("", cv.url), vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, }, extra=vol.REMOVE_EXTRA, @@ -785,8 +789,19 @@ class OptionsFlowHandler(config_entries.OptionsFlow): """Allow the user to configure the LED behavior.""" errors = {} if user_input is not None: - self.new_opt[CONF_BLINK] = user_input[CONF_BLINK] - return self.async_create_entry(title="", data=self.new_opt) + # config schema only does basic schema val so check url here + try: + if user_input[CONF_OVERRIDE_API_HOST]: + cv.url(user_input.get(CONF_API_HOST, "")) + else: + user_input[CONF_API_HOST] = "" + except vol.Invalid: + errors["base"] = "bad_host" + else: + # no need to store the override - can infer + del user_input[CONF_OVERRIDE_API_HOST] + self.new_opt.update(user_input) + return self.async_create_entry(title="", data=self.new_opt) return self.async_show_form( step_id="options_misc", @@ -795,6 +810,13 @@ class OptionsFlowHandler(config_entries.OptionsFlow): vol.Required( CONF_BLINK, default=self.current_opt.get(CONF_BLINK, True) ): bool, + vol.Required( + CONF_OVERRIDE_API_HOST, + default=bool(self.current_opt.get(CONF_API_HOST)), + ): bool, + vol.Optional( + CONF_API_HOST, default=self.current_opt.get(CONF_API_HOST, "") + ): str, } ), errors=errors, diff --git a/homeassistant/components/konnected/panel.py b/homeassistant/components/konnected/panel.py index 783aa78b8b1..efb1e83a728 100644 --- a/homeassistant/components/konnected/panel.py +++ b/homeassistant/components/konnected/panel.py @@ -294,7 +294,9 @@ class AlarmPanel: @callback def async_desired_settings_payload(self): """Return a dict representing the desired device configuration.""" - desired_api_host = ( + # keeping self.hass.data check for backwards compatibility + # newly configured integrations store this in the config entry + desired_api_host = self.options.get(CONF_API_HOST) or ( self.hass.data[DOMAIN].get(CONF_API_HOST) or self.hass.config.api.base_url ) desired_api_endpoint = desired_api_host + ENDPOINT_ROOT diff --git a/homeassistant/components/konnected/strings.json b/homeassistant/components/konnected/strings.json index f1d7ef43ddc..0ea8a40bc0a 100644 --- a/homeassistant/components/konnected/strings.json +++ b/homeassistant/components/konnected/strings.json @@ -94,11 +94,15 @@ "title": "Configure Misc", "description": "Please select the desired behavior for your panel", "data": { - "blink": "Blink panel LED on when sending state change" + "blink": "Blink panel LED on when sending state change", + "override_api_host": "Override default Home Assistant API host panel URL", + "api_host": "Override API host URL (optional)" } } }, - "error": {}, + "error": { + "bad_host": "Invalid Override API host url" + }, "abort": { "not_konn_panel": "Not a recognized Konnected.io device" } diff --git a/tests/components/konnected/test_config_flow.py b/tests/components/konnected/test_config_flow.py index 35814154f47..917afc5357a 100644 --- a/tests/components/konnected/test_config_flow.py +++ b/tests/components/konnected/test_config_flow.py @@ -450,6 +450,7 @@ async def test_import_existing_config(hass, mock_panel): "alarm1": "Switchable Output", }, "blink": True, + "api_host": "", "discovery": True, "binary_sensors": [ {"zone": "2", "type": "door", "inverse": False}, @@ -628,6 +629,7 @@ async def test_import_pin_config(hass, mock_panel): "out": "Switchable Output", }, "blink": True, + "api_host": "", "discovery": True, "binary_sensors": [ {"zone": "1", "type": "door", "inverse": False}, @@ -778,9 +780,21 @@ async def test_option_flow(hass, mock_panel): assert result["type"] == "form" assert result["step_id"] == "options_misc" - + # make sure we enforce url format result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={"blink": True}, + result["flow_id"], + user_input={"blink": True, "override_api_host": True, "api_host": "badhosturl"}, + ) + + assert result["type"] == "form" + assert result["step_id"] == "options_misc" + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "blink": True, + "override_api_host": True, + "api_host": "http://overridehost:1111", + }, ) assert result["type"] == "create_entry" assert result["data"] == { @@ -792,6 +806,7 @@ async def test_option_flow(hass, mock_panel): "out": "Switchable Output", }, "blink": True, + "api_host": "http://overridehost:1111", "binary_sensors": [ {"zone": "2", "type": "door", "inverse": False}, {"zone": "6", "type": "window", "name": "winder", "inverse": True}, @@ -958,7 +973,7 @@ async def test_option_flow_pro(hass, mock_panel): assert result["step_id"] == "options_misc" result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={"blink": True}, + result["flow_id"], user_input={"blink": True, "override_api_host": False}, ) assert result["type"] == "create_entry" @@ -976,6 +991,7 @@ async def test_option_flow_pro(hass, mock_panel): "out1": "Switchable Output", }, "blink": True, + "api_host": "", "binary_sensors": [ {"zone": "2", "type": "door", "inverse": False}, {"zone": "6", "type": "window", "name": "winder", "inverse": True}, @@ -1121,7 +1137,7 @@ async def test_option_flow_import(hass, mock_panel): schema = result["data_schema"]({}) assert schema["blink"] is True result = await hass.config_entries.options.async_configure( - result["flow_id"], user_input={"blink": False}, + result["flow_id"], user_input={"blink": False, "override_api_host": False}, ) # verify the updated fields @@ -1129,6 +1145,7 @@ async def test_option_flow_import(hass, mock_panel): assert result["data"] == { "io": {"1": "Binary Sensor", "2": "Digital Sensor", "3": "Switchable Output"}, "blink": False, + "api_host": "", "binary_sensors": [ {"zone": "1", "type": "door", "inverse": True, "name": "winder"}, ], diff --git a/tests/components/konnected/test_init.py b/tests/components/konnected/test_init.py index a678716bc03..2a9c3f8cd4f 100644 --- a/tests/components/konnected/test_init.py +++ b/tests/components/konnected/test_init.py @@ -43,6 +43,7 @@ async def test_config_schema(hass): """Test that config schema is imported properly.""" config = { konnected.DOMAIN: { + konnected.CONF_API_HOST: "http://1.1.1.1:8888", konnected.CONF_ACCESS_TOKEN: "abcdefgh", konnected.CONF_DEVICES: [{konnected.CONF_ID: "aabbccddeeff"}], } @@ -50,10 +51,12 @@ async def test_config_schema(hass): assert konnected.CONFIG_SCHEMA(config) == { "konnected": { "access_token": "abcdefgh", + "api_host": "http://1.1.1.1:8888", "devices": [ { "default_options": { "blink": True, + "api_host": "http://1.1.1.1:8888", "discovery": True, "io": { "1": "Disabled", @@ -96,6 +99,7 @@ async def test_config_schema(hass): { "default_options": { "blink": True, + "api_host": "", "discovery": True, "io": { "1": "Disabled", @@ -162,6 +166,7 @@ async def test_config_schema(hass): { "default_options": { "blink": True, + "api_host": "", "discovery": True, "io": { "1": "Binary Sensor",