diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index 0fed98ed4a6..772bc794285 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -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) diff --git a/homeassistant/components/uptimerobot/config_flow.py b/homeassistant/components/uptimerobot/config_flow.py index ccbf6c39655..3e419d6827c 100644 --- a/homeassistant/components/uptimerobot/config_flow.py +++ b/homeassistant/components/uptimerobot/config_flow.py @@ -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 diff --git a/homeassistant/components/uptimerobot/const.py b/homeassistant/components/uptimerobot/const.py index 1ac234afa64..b0fa6346ae2 100644 --- a/homeassistant/components/uptimerobot/const.py +++ b/homeassistant/components/uptimerobot/const.py @@ -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" diff --git a/homeassistant/components/uptimerobot/coordinator.py b/homeassistant/components/uptimerobot/coordinator.py index 7ea6e54bd9f..c8c03f2c9cc 100644 --- a/homeassistant/components/uptimerobot/coordinator.py +++ b/homeassistant/components/uptimerobot/coordinator.py @@ -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) diff --git a/homeassistant/components/uptimerobot/diagnostics.py b/homeassistant/components/uptimerobot/diagnostics.py index c3c2acbfbf1..8f9f67fb47b 100644 --- a/homeassistant/components/uptimerobot/diagnostics.py +++ b/homeassistant/components/uptimerobot/diagnostics.py @@ -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, } diff --git a/homeassistant/components/uptimerobot/entity.py b/homeassistant/components/uptimerobot/entity.py index a27d4a6f80e..5cd1cbb9a93 100644 --- a/homeassistant/components/uptimerobot/entity.py +++ b/homeassistant/components/uptimerobot/entity.py @@ -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 = { diff --git a/homeassistant/components/uptimerobot/manifest.json b/homeassistant/components/uptimerobot/manifest.json index 6fe8083ffc6..58bb79c361d 100644 --- a/homeassistant/components/uptimerobot/manifest.json +++ b/homeassistant/components/uptimerobot/manifest.json @@ -7,5 +7,5 @@ "iot_class": "cloud_polling", "loggers": ["pyuptimerobot"], "quality_scale": "bronze", - "requirements": ["pyuptimerobot==22.2.0"] + "requirements": ["pyuptimerobot==24.0.1"] } diff --git a/homeassistant/components/uptimerobot/sensor.py b/homeassistant/components/uptimerobot/sensor.py index 633ac8243ff..350cfc0ecd0 100644 --- a/homeassistant/components/uptimerobot/sensor.py +++ b/homeassistant/components/uptimerobot/sensor.py @@ -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] diff --git a/homeassistant/components/uptimerobot/switch.py b/homeassistant/components/uptimerobot/switch.py index b75f099db73..e5762853cc5 100644 --- a/homeassistant/components/uptimerobot/switch.py +++ b/homeassistant/components/uptimerobot/switch.py @@ -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) diff --git a/homeassistant/components/uptimerobot/utils.py b/homeassistant/components/uptimerobot/utils.py index 522324cf6f3..ed50b7535fc 100644 --- a/homeassistant/components/uptimerobot/utils.py +++ b/homeassistant/components/uptimerobot/utils.py @@ -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: diff --git a/requirements_all.txt b/requirements_all.txt index ac59bd0373d..3f1c2c779d9 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -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 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index de264e5f688..ad4dfc9f80d 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -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 diff --git a/tests/components/uptimerobot/common.py b/tests/components/uptimerobot/common.py index 4e7b275fea3..48530e2ee4e 100644 --- a/tests/components/uptimerobot/common.py +++ b/tests/components/uptimerobot/common.py @@ -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, } ) diff --git a/tests/components/uptimerobot/test_config_flow.py b/tests/components/uptimerobot/test_config_flow.py index ce6ec7cfcf7..f2be2e2b1c3 100644 --- a/tests/components/uptimerobot/test_config_flow.py +++ b/tests/components/uptimerobot/test_config_flow.py @@ -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"], diff --git a/tests/components/uptimerobot/test_diagnostics.py b/tests/components/uptimerobot/test_diagnostics.py index 12a91077552..5fe7851ccb9 100644 --- a/tests/components/uptimerobot/test_diagnostics.py +++ b/tests/components/uptimerobot/test_diagnostics.py @@ -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"] diff --git a/tests/components/uptimerobot/test_init.py b/tests/components/uptimerobot/test_init.py index d252501aa28..46781fd2e68 100644 --- a/tests/components/uptimerobot/test_init.py +++ b/tests/components/uptimerobot/test_init.py @@ -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) diff --git a/tests/components/uptimerobot/test_switch.py b/tests/components/uptimerobot/test_switch.py index 7dc35021064..2d1cfef8113 100644 --- a/tests/components/uptimerobot/test_switch.py +++ b/tests/components/uptimerobot/test_switch.py @@ -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: