mirror of
https://github.com/home-assistant/core.git
synced 2025-07-28 07:37:34 +00:00
Add unique_id to Uptime Robot config_flow (#54055)
This commit is contained in:
parent
debcc6689f
commit
786a83f844
@ -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
|
||||||
|
@ -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)
|
||||||
|
if account:
|
||||||
return self.async_create_entry(title=account.email, data=imported_config)
|
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."""
|
|
||||||
|
@ -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"
|
||||||
|
@ -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%]"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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": {
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user