Add unique_id to Uptime Robot config_flow (#54055)

This commit is contained in:
Joakim Sørensen 2021-08-05 14:58:29 +02:00 committed by GitHub
parent debcc6689f
commit 786a83f844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 196 additions and 42 deletions

View File

@ -1114,6 +1114,7 @@ omit =
homeassistant/components/upcloud/switch.py homeassistant/components/upcloud/switch.py
homeassistant/components/upnp/* homeassistant/components/upnp/*
homeassistant/components/upc_connect/* homeassistant/components/upc_connect/*
homeassistant/components/uptimerobot/__init__.py
homeassistant/components/uptimerobot/binary_sensor.py homeassistant/components/uptimerobot/binary_sensor.py
homeassistant/components/uptimerobot/const.py homeassistant/components/uptimerobot/const.py
homeassistant/components/uptimerobot/entity.py homeassistant/components/uptimerobot/entity.py

View File

@ -1,14 +1,19 @@
"""Config flow for Uptime Robot integration.""" """Config flow for Uptime Robot integration."""
from __future__ import annotations from __future__ import annotations
from pyuptimerobot import UptimeRobot, UptimeRobotAccount, UptimeRobotException from pyuptimerobot import (
UptimeRobot,
UptimeRobotAccount,
UptimeRobotApiError,
UptimeRobotApiResponse,
UptimeRobotAuthenticationException,
UptimeRobotException,
)
import voluptuous as vol import voluptuous as vol
from homeassistant import config_entries from homeassistant import config_entries
from homeassistant.const import CONF_API_KEY from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.typing import ConfigType
@ -17,41 +22,58 @@ from .const import API_ATTR_OK, DOMAIN, LOGGER
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str}) STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str})
async def validate_input(hass: HomeAssistant, data: ConfigType) -> UptimeRobotAccount:
"""Validate the user input allows us to connect."""
uptime_robot_api = UptimeRobot(data[CONF_API_KEY], async_get_clientsession(hass))
try:
response = await uptime_robot_api.async_get_account_details()
except UptimeRobotException as exception:
raise CannotConnect(exception) from exception
else:
if response.status == API_ATTR_OK:
return response.data
raise CannotConnect(response.error.message)
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Uptime Robot.""" """Handle a config flow for Uptime Robot."""
VERSION = 1 VERSION = 1
async def _validate_input(
self, data: ConfigType
) -> tuple[dict[str, str], UptimeRobotAccount | None]:
"""Validate the user input allows us to connect."""
errors: dict[str, str] = {}
response: UptimeRobotApiResponse | UptimeRobotApiError | None = None
uptime_robot_api = UptimeRobot(
data[CONF_API_KEY], async_get_clientsession(self.hass)
)
try:
response = await uptime_robot_api.async_get_account_details()
except UptimeRobotAuthenticationException as exception:
LOGGER.error(exception)
errors["base"] = "invalid_api_key"
except UptimeRobotException as exception:
LOGGER.error(exception)
errors["base"] = "cannot_connect"
except Exception as exception: # pylint: disable=broad-except
LOGGER.exception(exception)
errors["base"] = "unknown"
else:
if response.status != API_ATTR_OK:
errors["base"] = "unknown"
LOGGER.error(response.error.message)
account: UptimeRobotAccount | None = (
response.data
if response and response.data and response.data.email
else None
)
if account:
await self.async_set_unique_id(str(account.user_id))
self._abort_if_unique_id_configured()
return errors, account
async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult: async def async_step_user(self, user_input: ConfigType | None = None) -> FlowResult:
"""Handle the initial step.""" """Handle the initial step."""
errors: dict[str, str] = {} errors: dict[str, str] = {}
if user_input is None: if user_input is None:
return self.async_show_form( return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors step_id="user", data_schema=STEP_USER_DATA_SCHEMA
) )
try: errors, account = await self._validate_input(user_input)
account = await validate_input(self.hass, user_input) if account:
except CannotConnect:
errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(title=account.email, data=user_input) return self.async_create_entry(title=account.email, data=user_input)
return self.async_show_form( return self.async_show_form(
@ -69,9 +91,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
imported_config = {CONF_API_KEY: import_config[CONF_API_KEY]} imported_config = {CONF_API_KEY: import_config[CONF_API_KEY]}
account = await validate_input(self.hass, imported_config) _, account = await self._validate_input(imported_config)
return self.async_create_entry(title=account.email, data=imported_config) if account:
return self.async_create_entry(title=account.email, data=imported_config)
return self.async_abort(reason="unknown")
class CannotConnect(HomeAssistantError):
"""Error to indicate we cannot connect."""

View File

@ -3,7 +3,7 @@
"name": "Uptime Robot", "name": "Uptime Robot",
"documentation": "https://www.home-assistant.io/integrations/uptimerobot", "documentation": "https://www.home-assistant.io/integrations/uptimerobot",
"requirements": [ "requirements": [
"pyuptimerobot==21.8.1" "pyuptimerobot==21.8.2"
], ],
"codeowners": [ "codeowners": [
"@ludeeus" "@ludeeus"

View File

@ -9,10 +9,12 @@
}, },
"error": { "error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_api_key": "[%key:common::config_flow::error::invalid_api_key%]",
"unknown": "[%key:common::config_flow::error::unknown%]" "unknown": "[%key:common::config_flow::error::unknown%]"
}, },
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]" "already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
} }
} }
} }

View File

@ -1,10 +1,12 @@
{ {
"config": { "config": {
"abort": { "abort": {
"already_configured": "Account already configured" "already_configured": "Account already configured",
"unknown": "Unexpected error"
}, },
"error": { "error": {
"cannot_connect": "Failed to connect", "cannot_connect": "Failed to connect",
"invalid_api_key": "Invalid API key",
"unknown": "Unexpected error" "unknown": "Unexpected error"
}, },
"step": { "step": {

View File

@ -1951,7 +1951,7 @@ pytradfri[async]==7.0.6
pytrafikverket==0.1.6.2 pytrafikverket==0.1.6.2
# homeassistant.components.uptimerobot # homeassistant.components.uptimerobot
pyuptimerobot==21.8.1 pyuptimerobot==21.8.2
# homeassistant.components.keyboard # homeassistant.components.keyboard
# pyuserinput==0.1.11 # pyuserinput==0.1.11

View File

@ -1080,7 +1080,7 @@ pytraccar==0.9.0
pytradfri[async]==7.0.6 pytradfri[async]==7.0.6
# homeassistant.components.uptimerobot # homeassistant.components.uptimerobot
pyuptimerobot==21.8.1 pyuptimerobot==21.8.2
# homeassistant.components.vera # homeassistant.components.vera
pyvera==0.3.13 pyvera==0.3.13

View File

@ -1,7 +1,12 @@
"""Test the Uptime Robot config flow.""" """Test the Uptime Robot config flow."""
from unittest.mock import patch from unittest.mock import patch
from pytest import LogCaptureFixture
from pyuptimerobot import UptimeRobotApiResponse from pyuptimerobot import UptimeRobotApiResponse
from pyuptimerobot.exceptions import (
UptimeRobotAuthenticationException,
UptimeRobotException,
)
from homeassistant import config_entries, setup from homeassistant import config_entries, setup
from homeassistant.components.uptimerobot.const import DOMAIN from homeassistant.components.uptimerobot.const import DOMAIN
@ -12,6 +17,8 @@ from homeassistant.data_entry_flow import (
RESULT_TYPE_FORM, RESULT_TYPE_FORM,
) )
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant) -> None: async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form.""" """Test we get the form."""
@ -20,14 +27,14 @@ async def test_form(hass: HomeAssistant) -> None:
DOMAIN, context={"source": config_entries.SOURCE_USER} DOMAIN, context={"source": config_entries.SOURCE_USER}
) )
assert result["type"] == RESULT_TYPE_FORM assert result["type"] == RESULT_TYPE_FORM
assert result["errors"] == {} assert result["errors"] is None
with patch( with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details", "pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict( return_value=UptimeRobotApiResponse.from_dict(
{ {
"stat": "ok", "stat": "ok",
"account": {"email": "test@test.test"}, "account": {"email": "test@test.test", "user_id": 1234567890},
} }
), ),
), patch( ), patch(
@ -40,6 +47,7 @@ async def test_form(hass: HomeAssistant) -> None:
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert result2["result"].unique_id == "1234567890"
assert result2["type"] == RESULT_TYPE_CREATE_ENTRY assert result2["type"] == RESULT_TYPE_CREATE_ENTRY
assert result2["title"] == "test@test.test" assert result2["title"] == "test@test.test"
assert result2["data"] == {"api_key": "1234"} assert result2["data"] == {"api_key": "1234"}
@ -54,7 +62,7 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
with patch( with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details", "pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict({"stat": "fail", "error": {}}), side_effect=UptimeRobotException,
): ):
result2 = await hass.config_entries.flow.async_configure( result2 = await hass.config_entries.flow.async_configure(
result["flow_id"], result["flow_id"],
@ -65,6 +73,66 @@ async def test_form_cannot_connect(hass: HomeAssistant) -> None:
assert result2["errors"] == {"base": "cannot_connect"} assert result2["errors"] == {"base": "cannot_connect"}
async def test_form_unexpected_error(hass: HomeAssistant) -> None:
"""Test we handle unexpected error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
side_effect=Exception,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"api_key": "1234"},
)
assert result2["errors"] == {"base": "unknown"}
async def test_form_api_key_error(hass: HomeAssistant) -> None:
"""Test we handle unexpected error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
side_effect=UptimeRobotAuthenticationException,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"api_key": "1234"},
)
assert result2["errors"] == {"base": "invalid_api_key"}
async def test_form_api_error(hass: HomeAssistant, caplog: LogCaptureFixture) -> None:
"""Test we handle unexpected error."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict(
{
"stat": "fail",
"error": {"message": "test error from API."},
}
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"api_key": "1234"},
)
assert result2["errors"] == {"base": "unknown"}
assert "test error from API." in caplog.text
async def test_flow_import(hass): async def test_flow_import(hass):
"""Test an import flow.""" """Test an import flow."""
with patch( with patch(
@ -72,7 +140,7 @@ async def test_flow_import(hass):
return_value=UptimeRobotApiResponse.from_dict( return_value=UptimeRobotApiResponse.from_dict(
{ {
"stat": "ok", "stat": "ok",
"account": {"email": "test@test.test"}, "account": {"email": "test@test.test", "user_id": 1234567890},
} }
), ),
), patch( ), patch(
@ -92,7 +160,12 @@ async def test_flow_import(hass):
with patch( with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details", "pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict({"stat": "ok", "monitors": []}), return_value=UptimeRobotApiResponse.from_dict(
{
"stat": "ok",
"account": {"email": "test@test.test", "user_id": 1234567890},
}
),
), patch( ), patch(
"homeassistant.components.uptimerobot.async_setup_entry", "homeassistant.components.uptimerobot.async_setup_entry",
return_value=True, return_value=True,
@ -104,5 +177,61 @@ async def test_flow_import(hass):
) )
await hass.async_block_till_done() await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0
assert result["type"] == RESULT_TYPE_ABORT assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "already_configured" assert result["reason"] == "already_configured"
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict({"stat": "ok"}),
), patch(
"homeassistant.components.uptimerobot.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": config_entries.SOURCE_IMPORT},
data={"platform": DOMAIN, "api_key": "12345"},
)
await hass.async_block_till_done()
assert result["type"] == RESULT_TYPE_ABORT
assert result["reason"] == "unknown"
async def test_user_unique_id_already_exists(hass):
"""Test creating an entry where the unique_id already exists."""
entry = MockConfigEntry(
domain=DOMAIN,
data={"platform": DOMAIN, "api_key": "1234"},
unique_id="1234567890",
)
entry.add_to_hass(hass)
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["type"] == "form"
assert result["errors"] is None
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict(
{
"stat": "ok",
"account": {"email": "test@test.test", "user_id": 1234567890},
}
),
), patch(
"homeassistant.components.uptimerobot.async_setup_entry",
return_value=True,
) as mock_setup_entry:
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"api_key": "12345"},
)
await hass.async_block_till_done()
assert len(mock_setup_entry.mock_calls) == 0
assert result2["type"] == "abort"
assert result2["reason"] == "already_configured"