1
0
mirror of https://github.com/home-assistant/core.git synced 2026-05-08 17:49:37 +01:00

Update UptimeRobot to API v3 (#153508)

This commit is contained in:
Simone Chemelli
2026-02-12 21:28:11 +01:00
committed by GitHub
parent 38531033a1
commit 1bca0ba5f8
17 changed files with 150 additions and 225 deletions
@@ -12,6 +12,7 @@ from homeassistant.components.binary_sensor import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import STATUS_UP
from .coordinator import UptimeRobotConfigEntry
from .entity import UptimeRobotEntity
from .utils import new_device_listener
@@ -53,4 +54,4 @@ class UptimeRobotBinarySensor(UptimeRobotEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Return True if the entity is on."""
return bool(self.monitor.status == 2)
return bool(self.monitor.status == STATUS_UP)
@@ -3,12 +3,11 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from typing import TYPE_CHECKING, Any
from pyuptimerobot import (
UptimeRobot,
UptimeRobotAccount,
UptimeRobotApiError,
UptimeRobotApiResponse,
UptimeRobotAuthenticationException,
UptimeRobotException,
@@ -19,7 +18,7 @@ from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import API_ATTR_OK, DOMAIN, LOGGER
from .const import DOMAIN, LOGGER
STEP_USER_DATA_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str})
@@ -34,7 +33,7 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN):
) -> tuple[dict[str, str], UptimeRobotAccount | None]:
"""Validate the user input allows us to connect."""
errors: dict[str, str] = {}
response: UptimeRobotApiResponse | UptimeRobotApiError | None = None
response: UptimeRobotApiResponse | None = None
key: str = data[CONF_API_KEY]
if key.startswith(("ur", "m")):
LOGGER.error("Wrong API key type detected, use the 'main' API key")
@@ -51,16 +50,12 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN):
except Exception as exception: # noqa: BLE001
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 TYPE_CHECKING:
assert response is not None
assert isinstance(response.data, UptimeRobotAccount)
account: UptimeRobotAccount | None = response.data if response else None
return errors, account
@@ -75,7 +70,7 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN):
errors, account = await self._validate_input(user_input)
if account:
await self.async_set_unique_id(str(account.user_id))
await self.async_set_unique_id(account.email)
self._abort_if_unique_id_configured()
return self.async_create_entry(title=account.email, data=user_input)
@@ -99,12 +94,13 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN):
)
errors, account = await self._validate_input(user_input)
if account:
if self.context.get("unique_id") and self.context["unique_id"] != str(
account.user_id
if (
self.context.get("unique_id")
and self.context["unique_id"] != account.email
):
errors["base"] = "reauth_failed_matching_account"
else:
existing_entry = await self.async_set_unique_id(str(account.user_id))
existing_entry = await self.async_set_unique_id(account.email)
if existing_entry:
self.hass.config_entries.async_update_entry(
existing_entry, data=user_input
@@ -134,7 +130,7 @@ class UptimeRobotConfigFlow(ConfigFlow, domain=DOMAIN):
errors, account = await self._validate_input(user_input)
if account:
await self.async_set_unique_id(str(account.user_id))
await self.async_set_unique_id(account.email)
self._abort_if_unique_id_configured()
return self.async_update_reload_and_abort(
reconfigure_entry, data_updates=user_input
@@ -21,3 +21,6 @@ ATTRIBUTION: Final = "Data provided by UptimeRobot"
ATTR_TARGET: Final = "target"
API_ATTR_OK: Final = "ok"
STATUS_UP = "UP"
STATUS_DOWN = "DOWN"
@@ -17,7 +17,7 @@ from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import API_ATTR_OK, COORDINATOR_UPDATE_INTERVAL, DOMAIN, LOGGER
from .const import COORDINATOR_UPDATE_INTERVAL, DOMAIN, LOGGER
type UptimeRobotConfigEntry = ConfigEntry[UptimeRobotDataUpdateCoordinator]
@@ -52,11 +52,6 @@ class UptimeRobotDataUpdateCoordinator(DataUpdateCoordinator[list[UptimeRobotMon
except UptimeRobotException as exception:
raise UpdateFailed(exception) from exception
if response.status != API_ATTR_OK:
raise UpdateFailed(
response.error.message if response.error else "Unknown error"
)
if TYPE_CHECKING:
assert isinstance(response.data, list)
@@ -6,10 +6,13 @@ from typing import Any
from pyuptimerobot import UptimeRobotException
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.core import HomeAssistant
from .coordinator import UptimeRobotConfigEntry
TO_REDACT = {"email"}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
@@ -25,17 +28,16 @@ async def async_get_config_entry_diagnostics(
else:
if (details := response.data) is not None:
account = {
"up_monitors": details.up_monitors,
"down_monitors": details.down_monitors,
"paused_monitors": details.paused_monitors,
"monitorsCount": details.monitorsCount,
"email": details.email,
}
return {
"account": account,
"account": async_redact_data(account, TO_REDACT),
"monitors": [
{
"id": monitor.id,
"type": str(monitor.type),
"type": monitor.type,
"interval": monitor.interval,
"status": monitor.status,
}
@@ -31,10 +31,10 @@ class UptimeRobotEntity(CoordinatorEntity[UptimeRobotDataUpdateCoordinator]):
self._monitor = monitor
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, str(self.monitor.id))},
name=self.monitor.friendly_name,
name=self.monitor.friendlyName,
manufacturer="UptimeRobot Team",
entry_type=DeviceEntryType.SERVICE,
model=self.monitor.type.name,
model=self.monitor.type,
configuration_url=f"https://uptimerobot.com/dashboard#{self.monitor.id}",
)
self._attr_extra_state_attributes = {
@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["pyuptimerobot"],
"quality_scale": "bronze",
"requirements": ["pyuptimerobot==22.2.0"]
"requirements": ["pyuptimerobot==24.0.1"]
}
@@ -17,14 +17,6 @@ from .coordinator import UptimeRobotConfigEntry
from .entity import UptimeRobotEntity
from .utils import new_device_listener
SENSORS_INFO = {
0: "pause",
1: "not_checked_yet",
2: "up",
8: "seems_down",
9: "down",
}
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -71,4 +63,7 @@ class UptimeRobotSensor(UptimeRobotEntity, SensorEntity):
@property
def native_value(self) -> str:
"""Return the status of the monitor."""
return SENSORS_INFO[self.monitor.status]
status = self.monitor.status.lower()
# The API returns "paused"
# but the entity state will be "pause" to avoid a breaking change
return {"paused": "pause"}.get(status, status) # type: ignore[no-any-return]
+8 -15
View File
@@ -19,7 +19,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import API_ATTR_OK, DOMAIN
from .const import DOMAIN, STATUS_DOWN, STATUS_UP
from .coordinator import UptimeRobotConfigEntry
from .entity import UptimeRobotEntity
from .utils import new_device_listener
@@ -63,12 +63,12 @@ class UptimeRobotSwitch(UptimeRobotEntity, SwitchEntity):
@property
def is_on(self) -> bool:
"""Return True if the entity is on."""
return bool(self.monitor.status != 0)
return bool(self.monitor.status == STATUS_UP)
async def _async_edit_monitor(self, **kwargs: Any) -> None:
"""Edit monitor status."""
try:
response = await self.api.async_edit_monitor(**kwargs)
await self.api.async_edit_monitor(**kwargs)
except UptimeRobotAuthenticationException:
self.coordinator.config_entry.async_start_reauth(self.hass)
return
@@ -76,22 +76,15 @@ class UptimeRobotSwitch(UptimeRobotEntity, SwitchEntity):
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="api_exception",
translation_placeholders={"error": repr(exception)},
translation_placeholders={"error": "Generic UptimeRobot exception"},
) from exception
if response.status != API_ATTR_OK:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="api_exception",
translation_placeholders={"error": response.error.message},
)
await self.coordinator.async_request_refresh()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn on switch."""
await self._async_edit_monitor(id=self.monitor.id, status=0)
"""Turn off switch."""
await self._async_edit_monitor(monitor_id=self.monitor.id, status=STATUS_DOWN)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn off switch."""
await self._async_edit_monitor(id=self.monitor.id, status=1)
"""Turn on switch."""
await self._async_edit_monitor(monitor_id=self.monitor.id, status=STATUS_UP)
@@ -1,5 +1,7 @@
"""Utility functions for the UptimeRobot integration."""
from __future__ import annotations
from collections.abc import Callable
from pyuptimerobot import UptimeRobotMonitor
@@ -16,9 +18,6 @@ def new_device_listener(
def _check_devices() -> None:
"""Check for new devices and call callback with any new monitors."""
if not coordinator.data:
return
new_monitors: list[UptimeRobotMonitor] = []
for monitor in coordinator.data:
if monitor.id not in known_devices:
+1 -1
View File
@@ -2670,7 +2670,7 @@ pytrafikverket==1.1.1
pytrydan==0.8.0
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
pyuptimerobot==24.0.1
# homeassistant.components.vera
pyvera==0.3.16
+1 -1
View File
@@ -2251,7 +2251,7 @@ pytrafikverket==1.1.1
pytrydan==0.8.0
# homeassistant.components.uptimerobot
pyuptimerobot==22.2.0
pyuptimerobot==24.0.1
# homeassistant.components.vera
pyvera==0.3.16
+27 -35
View File
@@ -6,13 +6,7 @@ from enum import StrEnum
from typing import Any
from unittest.mock import patch
from pyuptimerobot import (
APIStatus,
UptimeRobotAccount,
UptimeRobotApiError,
UptimeRobotApiResponse,
UptimeRobotMonitor,
)
from pyuptimerobot import API_PATH_MONITORS, UptimeRobotApiResponse
from homeassistant import config_entries
from homeassistant.components.uptimerobot.const import DOMAIN
@@ -29,29 +23,31 @@ MOCK_UPTIMEROBOT_UNIQUE_ID = "1234567890"
MOCK_UPTIMEROBOT_ACCOUNT = {
"email": MOCK_UPTIMEROBOT_EMAIL,
"user_id": 1234567890,
"up_monitors": 1,
"monitorsCount": 1,
}
MOCK_UPTIMEROBOT_ERROR = {"message": "test error from API."}
MOCK_UPTIMEROBOT_MONITOR = {
"id": 1234,
"friendly_name": "Test monitor",
"status": 2,
"type": 1,
"friendlyName": "Test monitor",
"interval": 300,
"status": "UP",
"type": "HTTP",
"url": "http://example.com",
}
MOCK_UPTIMEROBOT_MONITOR_PAUSED = {
"id": 1234,
"friendly_name": "Test monitor",
"status": 0,
"type": 1,
"friendlyName": "Test monitor",
"interval": 300,
"status": "PAUSED",
"type": "HTTP",
"url": "http://example.com",
}
MOCK_UPTIMEROBOT_MONITOR_2 = {
"id": 5678,
"friendly_name": "Test monitor 2",
"status": 2,
"type": 1,
"friendlyName": "Test monitor 2",
"interval": 300,
"status": "UP",
"type": "HTTP",
"url": "http://example2.com",
}
@@ -59,14 +55,14 @@ MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA = {
"domain": DOMAIN,
"title": MOCK_UPTIMEROBOT_EMAIL,
"data": {"platform": DOMAIN, "api_key": MOCK_UPTIMEROBOT_API_KEY},
"unique_id": MOCK_UPTIMEROBOT_UNIQUE_ID,
"unique_id": MOCK_UPTIMEROBOT_EMAIL,
"source": config_entries.SOURCE_USER,
}
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA_KEY_READ_ONLY = {
"domain": DOMAIN,
"title": MOCK_UPTIMEROBOT_EMAIL,
"data": {"platform": DOMAIN, "api_key": MOCK_UPTIMEROBOT_API_KEY_READ_ONLY},
"unique_id": MOCK_UPTIMEROBOT_UNIQUE_ID,
"unique_id": MOCK_UPTIMEROBOT_EMAIL,
"source": config_entries.SOURCE_USER,
}
@@ -86,25 +82,21 @@ class MockApiResponseKey(StrEnum):
def mock_uptimerobot_api_response(
data: list[dict[str, Any]]
| list[UptimeRobotMonitor]
| UptimeRobotAccount
| UptimeRobotApiError
| None = None,
status: APIStatus = APIStatus.OK,
key: MockApiResponseKey = MockApiResponseKey.MONITORS,
data: list[dict[str, Any]] | dict[str, Any],
api_path: str = API_PATH_MONITORS,
) -> UptimeRobotApiResponse:
"""Mock API response for UptimeRobot."""
if api_path == API_PATH_MONITORS:
data_dict = {"data": data}
elif isinstance(data, dict):
data_dict = data
return UptimeRobotApiResponse.from_dict(
{
"stat": {"error": APIStatus.FAIL}.get(key, status),
key: data
if data is not None
else {
"account": MOCK_UPTIMEROBOT_ACCOUNT,
"error": MOCK_UPTIMEROBOT_ERROR,
"monitors": [MOCK_UPTIMEROBOT_MONITOR],
}.get(key, {}),
"_method": "GET",
"_api_path": api_path,
**data_dict,
}
)
@@ -4,8 +4,9 @@ from unittest.mock import patch
import pytest
from pyuptimerobot import (
UptimeRobotApiResponse,
API_PATH_USER_ME,
UptimeRobotAuthenticationException,
UptimeRobotConnectionException,
UptimeRobotException,
)
@@ -20,8 +21,7 @@ from .common import (
MOCK_UPTIMEROBOT_API_KEY,
MOCK_UPTIMEROBOT_API_KEY_READ_ONLY,
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
MOCK_UPTIMEROBOT_UNIQUE_ID,
MockApiResponseKey,
MOCK_UPTIMEROBOT_EMAIL,
mock_uptimerobot_api_response,
)
@@ -40,7 +40,9 @@ async def test_user(hass: HomeAssistant) -> None:
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME, data=MOCK_UPTIMEROBOT_ACCOUNT
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -53,7 +55,7 @@ async def test_user(hass: HomeAssistant) -> None:
)
await hass.async_block_till_done()
assert result2["result"].unique_id == MOCK_UPTIMEROBOT_UNIQUE_ID
assert result2["result"].unique_id == MOCK_UPTIMEROBOT_EMAIL
assert result2["type"] is FlowResultType.CREATE_ENTRY
assert result2["title"] == MOCK_UPTIMEROBOT_ACCOUNT["email"]
assert result2["data"] == {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}
@@ -71,7 +73,10 @@ async def test_user_key_read_only(hass: HomeAssistant) -> None:
with patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME,
data=MOCK_UPTIMEROBOT_ACCOUNT,
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -120,7 +125,7 @@ async def test_api_error(hass: HomeAssistant, caplog: pytest.LogCaptureFixture)
with patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR),
side_effect=UptimeRobotConnectionException,
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
@@ -128,8 +133,7 @@ async def test_api_error(hass: HomeAssistant, caplog: pytest.LogCaptureFixture)
)
assert result2["errors"]
assert result2["errors"]["base"] == "unknown"
assert "test error from API." in caplog.text
assert result2["errors"]["base"] == "cannot_connect"
async def test_user_unique_id_already_exists(
@@ -148,7 +152,10 @@ async def test_user_unique_id_already_exists(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME,
data=MOCK_UPTIMEROBOT_ACCOUNT,
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -182,7 +189,10 @@ async def test_reauthentication(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME,
data=MOCK_UPTIMEROBOT_ACCOUNT,
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -215,7 +225,7 @@ async def test_reauthentication_failure(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR),
side_effect=UptimeRobotException,
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -231,7 +241,7 @@ async def test_reauthentication_failure(
assert result2["step_id"] == "reauth_confirm"
assert result2["type"] is FlowResultType.FORM
assert result2["errors"]
assert result2["errors"]["base"] == "unknown"
assert result2["errors"]["base"] == "cannot_connect"
async def test_reauthentication_failure_no_existing_entry(
@@ -252,7 +262,10 @@ async def test_reauthentication_failure_no_existing_entry(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME,
data=MOCK_UPTIMEROBOT_ACCOUNT,
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -286,8 +299,11 @@ async def test_reauthentication_failure_account_not_matching(
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(
key=MockApiResponseKey.ACCOUNT,
data={**MOCK_UPTIMEROBOT_ACCOUNT, "user_id": 1234567891},
api_path=API_PATH_USER_ME,
data={
"email": "wrong_account",
"monitorsCount": 1,
},
),
),
patch(
@@ -327,7 +343,9 @@ async def test_reconfigure_successful(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME, data=MOCK_UPTIMEROBOT_ACCOUNT
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
@@ -387,90 +405,14 @@ async def test_reconfigure_failed(
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
return_value=True,
),
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
user_input={CONF_API_KEY: new_key},
)
assert result3["type"] is FlowResultType.ABORT
assert result3["reason"] == "reconfigure_successful"
# changed entry
assert config_entry.data[CONF_API_KEY] == new_key
async def test_reconfigure_with_key_present(
hass: HomeAssistant,
) -> None:
"""Test that the entry reconfigure fails with a key from another entry."""
config_entry = MockConfigEntry(
**{**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, "unique_id": None}
)
config_entry.add_to_hass(hass)
api_key_2 = "u0242ac120003-2"
email_2 = "test2@test.test"
user_id_2 = "abcdefghil"
data2 = {
"domain": DOMAIN,
"title": email_2,
"data": {"platform": DOMAIN, "api_key": api_key_2},
"unique_id": user_id_2,
"source": config_entries.SOURCE_USER,
}
config_entry_2 = MockConfigEntry(**{**data2, "unique_id": None})
config_entry_2.add_to_hass(hass)
result = await config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["errors"] is None
assert result["step_id"] == "reconfigure"
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=UptimeRobotApiResponse.from_dict(
{
"stat": "ok",
"email": email_2,
"user_id": user_id_2,
"up_monitors": 1,
}
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_USER_ME, data=MOCK_UPTIMEROBOT_ACCOUNT
),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
return_value=True,
),
):
result2 = await hass.config_entries.flow.async_configure(
result["flow_id"],
user_input={CONF_API_KEY: api_key_2},
)
assert result2["type"] is FlowResultType.FORM
assert result2["errors"] == {}
assert result2["step_id"] == "reconfigure"
new_key = "u0242ac120003-new"
with (
patch(
"homeassistant.components.uptimerobot.config_flow.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT),
),
patch(
"homeassistant.components.uptimerobot.async_setup_entry",
return_value=True,
),
):
result3 = await hass.config_entries.flow.async_configure(
result2["flow_id"],
@@ -3,7 +3,7 @@
import json
from unittest.mock import patch
from pyuptimerobot import UptimeRobotException
from pyuptimerobot import API_PATH_USER_ME, UptimeRobotException
from homeassistant.core import HomeAssistant
@@ -11,7 +11,6 @@ from .common import (
MOCK_UPTIMEROBOT_ACCOUNT,
MOCK_UPTIMEROBOT_API_KEY,
MOCK_UPTIMEROBOT_EMAIL,
MockApiResponseKey,
mock_uptimerobot_api_response,
setup_uptimerobot_integration,
)
@@ -30,7 +29,7 @@ async def test_entry_diagnostics(
with patch(
"pyuptimerobot.UptimeRobot.async_get_account_details",
return_value=mock_uptimerobot_api_response(
key=MockApiResponseKey.ACCOUNT,
api_path=API_PATH_USER_ME,
data=MOCK_UPTIMEROBOT_ACCOUNT,
),
):
@@ -41,13 +40,12 @@ async def test_entry_diagnostics(
)
assert result["account"] == {
"down_monitors": 0,
"paused_monitors": 0,
"up_monitors": 1,
"monitorsCount": 1,
"email": "**REDACTED**",
}
assert result["monitors"] == [
{"id": 1234, "interval": 0, "status": 2, "type": "MonitorType.HTTP"}
{"id": 1234, "interval": 300, "status": "UP", "type": "HTTP"}
]
assert list(result.keys()) == ["account", "monitors"]
+5 -6
View File
@@ -21,7 +21,6 @@ from .common import (
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA_KEY_READ_ONLY,
MOCK_UPTIMEROBOT_MONITOR,
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
MockApiResponseKey,
mock_uptimerobot_api_response,
setup_uptimerobot_integration,
)
@@ -137,7 +136,7 @@ async def test_integration_reload(
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
return_value=mock_uptimerobot_api_response(),
return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]),
):
assert await hass.config_entries.async_reload(mock_entry.entry_id)
freezer.tick(COORDINATOR_UPDATE_INTERVAL)
@@ -170,7 +169,7 @@ async def test_update_errors(
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
return_value=mock_uptimerobot_api_response(),
return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]),
):
freezer.tick(COORDINATOR_UPDATE_INTERVAL)
async_fire_time_changed(hass)
@@ -180,7 +179,7 @@ async def test_update_errors(
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR),
side_effect=Exception("Unexpected error"),
):
freezer.tick(COORDINATOR_UPDATE_INTERVAL)
async_fire_time_changed(hass)
@@ -188,7 +187,7 @@ async def test_update_errors(
assert (entity := hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY))
assert entity.state == STATE_UNAVAILABLE
assert "Error fetching uptimerobot data: test error from API" in caplog.text
assert "Error fetching uptimerobot data:" in caplog.text
async def test_device_management(
@@ -231,7 +230,7 @@ async def test_device_management(
with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
return_value=mock_uptimerobot_api_response(),
return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]),
):
freezer.tick(COORDINATOR_UPDATE_INTERVAL)
async_fire_time_changed(hass)
+30 -20
View File
@@ -3,7 +3,11 @@
from unittest.mock import patch
import pytest
from pyuptimerobot import UptimeRobotAuthenticationException, UptimeRobotException
from pyuptimerobot import (
API_PATH_MONITOR_DETAIL,
UptimeRobotAuthenticationException,
UptimeRobotException,
)
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.components.uptimerobot.const import COORDINATOR_UPDATE_INTERVAL
@@ -24,7 +28,6 @@ from .common import (
MOCK_UPTIMEROBOT_MONITOR_2,
MOCK_UPTIMEROBOT_MONITOR_PAUSED,
UPTIMEROBOT_SWITCH_TEST_ENTITY,
MockApiResponseKey,
mock_uptimerobot_api_response,
setup_uptimerobot_integration,
)
@@ -56,7 +59,9 @@ async def test_switch_off(hass: HomeAssistant) -> None:
),
patch(
"pyuptimerobot.UptimeRobot.async_edit_monitor",
return_value=mock_uptimerobot_api_response(),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_MONITOR_DETAIL, data=MOCK_UPTIMEROBOT_MONITOR_PAUSED
),
),
):
assert await hass.config_entries.async_setup(mock_entry.entry_id)
@@ -86,7 +91,10 @@ async def test_switch_on(hass: HomeAssistant) -> None:
),
patch(
"pyuptimerobot.UptimeRobot.async_edit_monitor",
return_value=mock_uptimerobot_api_response(),
return_value=mock_uptimerobot_api_response(
api_path=API_PATH_MONITOR_DETAIL,
data=MOCK_UPTIMEROBOT_MONITOR,
),
),
):
assert await hass.config_entries.async_setup(mock_entry.entry_id)
@@ -155,7 +163,7 @@ async def test_action_execution_failure(hass: HomeAssistant) -> None:
assert exc_info.value.translation_domain == "uptimerobot"
assert exc_info.value.translation_key == "api_exception"
assert exc_info.value.translation_placeholders == {
"error": "UptimeRobotException()"
"error": "Generic UptimeRobot exception"
}
@@ -166,23 +174,25 @@ async def test_switch_api_failure(hass: HomeAssistant) -> None:
assert (entity := hass.states.get(UPTIMEROBOT_SWITCH_TEST_ENTITY)) is not None
assert entity.state == STATE_ON
with patch(
"pyuptimerobot.UptimeRobot.async_edit_monitor",
return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR),
with (
patch(
"pyuptimerobot.UptimeRobot.async_edit_monitor",
side_effect=UptimeRobotException,
),
pytest.raises(HomeAssistantError) as exc_info,
):
with pytest.raises(HomeAssistantError) as exc_info:
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: UPTIMEROBOT_SWITCH_TEST_ENTITY},
blocking=True,
)
await hass.services.async_call(
SWITCH_DOMAIN,
SERVICE_TURN_OFF,
{ATTR_ENTITY_ID: UPTIMEROBOT_SWITCH_TEST_ENTITY},
blocking=True,
)
assert exc_info.value.translation_domain == "uptimerobot"
assert exc_info.value.translation_key == "api_exception"
assert exc_info.value.translation_placeholders == {
"error": "test error from API."
}
assert exc_info.value.translation_domain == "uptimerobot"
assert exc_info.value.translation_key == "api_exception"
assert exc_info.value.translation_placeholders == {
"error": "Generic UptimeRobot exception"
}
async def test_switch_dynamic(hass: HomeAssistant) -> None: