diff --git a/.coveragerc b/.coveragerc index 5d9c5e9c5c8..8088bbece78 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1115,10 +1115,6 @@ omit = homeassistant/components/upcloud/switch.py homeassistant/components/upnp/* homeassistant/components/upc_connect/* - homeassistant/components/uptimerobot/__init__.py - homeassistant/components/uptimerobot/binary_sensor.py - homeassistant/components/uptimerobot/const.py - homeassistant/components/uptimerobot/entity.py homeassistant/components/uscis/sensor.py homeassistant/components/vallox/* homeassistant/components/vasttrafik/sensor.py diff --git a/homeassistant/components/uptimerobot/binary_sensor.py b/homeassistant/components/uptimerobot/binary_sensor.py index f99689f2507..ac0dc0c1186 100644 --- a/homeassistant/components/uptimerobot/binary_sensor.py +++ b/homeassistant/components/uptimerobot/binary_sensor.py @@ -53,7 +53,7 @@ async def async_setup_entry( name=monitor.friendly_name, device_class=DEVICE_CLASS_CONNECTIVITY, ), - target=monitor.url, + monitor=monitor, ) for monitor in coordinator.data ], diff --git a/homeassistant/components/uptimerobot/entity.py b/homeassistant/components/uptimerobot/entity.py index b9783c88b9c..8ef60b3848b 100644 --- a/homeassistant/components/uptimerobot/entity.py +++ b/homeassistant/components/uptimerobot/entity.py @@ -20,11 +20,12 @@ class UptimeRobotEntity(CoordinatorEntity): self, coordinator: DataUpdateCoordinator, description: EntityDescription, - target: str, + monitor: UptimeRobotMonitor, ) -> None: """Initialize Uptime Robot entities.""" super().__init__(coordinator) self.entity_description = description + self._monitor = monitor self._attr_device_info = { "identifiers": {(DOMAIN, str(self.monitor.id))}, "name": "Uptime Robot", @@ -34,7 +35,7 @@ class UptimeRobotEntity(CoordinatorEntity): } self._attr_extra_state_attributes = { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_TARGET: target, + ATTR_TARGET: self.monitor.url, } self._attr_unique_id = str(self.monitor.id) @@ -47,9 +48,12 @@ class UptimeRobotEntity(CoordinatorEntity): def monitor(self) -> UptimeRobotMonitor: """Return the monitor for this entity.""" return next( - monitor - for monitor in self._monitors - if str(monitor.id) == self.entity_description.key + ( + monitor + for monitor in self._monitors + if str(monitor.id) == self.entity_description.key + ), + self._monitor, ) @property diff --git a/tests/components/uptimerobot/common.py b/tests/components/uptimerobot/common.py new file mode 100644 index 00000000000..aa241ce5a92 --- /dev/null +++ b/tests/components/uptimerobot/common.py @@ -0,0 +1,95 @@ +"""Common constants and functions for Uptime Robot tests.""" +from __future__ import annotations + +from enum import Enum +from typing import Any +from unittest.mock import patch + +from pyuptimerobot import ( + APIStatus, + UptimeRobotAccount, + UptimeRobotApiError, + UptimeRobotApiResponse, + UptimeRobotMonitor, +) + +from homeassistant import config_entries +from homeassistant.components.uptimerobot.const import DOMAIN +from homeassistant.const import STATE_ON +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry + +MOCK_UPTIMEROBOT_API_KEY = "1234" +MOCK_UPTIMEROBOT_UNIQUE_ID = "1234567890" + +MOCK_UPTIMEROBOT_ACCOUNT = {"email": "test@test.test", "user_id": 1234567890} +MOCK_UPTIMEROBOT_ERROR = {"message": "test error from API."} +MOCK_UPTIMEROBOT_MONITOR = { + "id": 1234, + "friendly_name": "Test monitor", + "status": 2, + "type": 1, + "url": "http://example.com", +} + +MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA = { + "domain": DOMAIN, + "title": "test@test.test", + "data": {"platform": DOMAIN, "api_key": MOCK_UPTIMEROBOT_API_KEY}, + "unique_id": MOCK_UPTIMEROBOT_UNIQUE_ID, + "source": config_entries.SOURCE_USER, +} + +UPTIMEROBOT_TEST_ENTITY = "binary_sensor.test_monitor" + + +class MockApiResponseKey(str, Enum): + """Mock API response key.""" + + ACCOUNT = "account" + ERROR = "error" + MONITORS = "monitors" + + +def mock_uptimerobot_api_response( + data: dict[str, Any] + | None + | list[UptimeRobotMonitor] + | UptimeRobotAccount + | UptimeRobotApiError = None, + status: APIStatus = APIStatus.OK, + key: MockApiResponseKey = MockApiResponseKey.MONITORS, +) -> UptimeRobotApiResponse: + """Mock API response for Uptime Robot.""" + 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, {}), + } + ) + + +async def setup_uptimerobot_integration(hass: HomeAssistant) -> MockConfigEntry: + """Set up the Uptime Robot integration.""" + mock_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) + mock_entry.add_to_hass(hass) + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(data=[MOCK_UPTIMEROBOT_MONITOR]), + ): + + assert await hass.config_entries.async_setup(mock_entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + assert mock_entry.state == config_entries.ConfigEntryState.LOADED + + return mock_entry diff --git a/tests/components/uptimerobot/test_binary_sensor.py b/tests/components/uptimerobot/test_binary_sensor.py new file mode 100644 index 00000000000..13bb3b342e9 --- /dev/null +++ b/tests/components/uptimerobot/test_binary_sensor.py @@ -0,0 +1,82 @@ +"""Test Uptime Robot binary_sensor.""" + +from unittest.mock import patch + +from pyuptimerobot import UptimeRobotAuthenticationException + +from homeassistant.components.binary_sensor import DEVICE_CLASS_CONNECTIVITY +from homeassistant.components.uptimerobot.const import ( + ATTRIBUTION, + COORDINATOR_UPDATE_INTERVAL, + DOMAIN, +) +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE +from homeassistant.core import HomeAssistant +from homeassistant.setup import async_setup_component +from homeassistant.util import dt + +from .common import ( + MOCK_UPTIMEROBOT_API_KEY, + MOCK_UPTIMEROBOT_MONITOR, + UPTIMEROBOT_TEST_ENTITY, + MockApiResponseKey, + mock_uptimerobot_api_response, + setup_uptimerobot_integration, +) + +from tests.common import async_fire_time_changed + + +async def test_config_import(hass: HomeAssistant) -> None: + """Test importing YAML configuration.""" + config = { + "binary_sensor": { + "platform": DOMAIN, + "api_key": MOCK_UPTIMEROBOT_API_KEY, + } + } + with patch( + "pyuptimerobot.UptimeRobot.async_get_account_details", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), + ), patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(), + ): + assert await async_setup_component(hass, "binary_sensor", config) + await hass.async_block_till_done() + + config_entries = hass.config_entries.async_entries(DOMAIN) + + assert len(config_entries) == 1 + config_entry = config_entries[0] + assert config_entry.source == "import" + + +async def test_presentation(hass: HomeAssistant) -> None: + """Test the presenstation of Uptime Robot binary_sensors.""" + await setup_uptimerobot_integration(hass) + + entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY) + + assert entity.state == STATE_ON + assert entity.attributes["device_class"] == DEVICE_CLASS_CONNECTIVITY + assert entity.attributes["attribution"] == ATTRIBUTION + assert entity.attributes["target"] == MOCK_UPTIMEROBOT_MONITOR["url"] + + +async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None: + """Test entity unaviable on update failure.""" + await setup_uptimerobot_integration(hass) + + entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY) + assert entity.state == STATE_ON + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + side_effect=UptimeRobotAuthenticationException, + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + + entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY) + assert entity.state == STATE_UNAVAILABLE diff --git a/tests/components/uptimerobot/test_config_flow.py b/tests/components/uptimerobot/test_config_flow.py index 967e1b499f5..966483970d0 100644 --- a/tests/components/uptimerobot/test_config_flow.py +++ b/tests/components/uptimerobot/test_config_flow.py @@ -1,15 +1,13 @@ """Test the Uptime Robot config flow.""" from unittest.mock import patch +import pytest from pytest import LogCaptureFixture -from pyuptimerobot import UptimeRobotApiResponse -from pyuptimerobot.exceptions import ( - UptimeRobotAuthenticationException, - UptimeRobotException, -) +from pyuptimerobot import UptimeRobotAuthenticationException, UptimeRobotException from homeassistant import config_entries, setup from homeassistant.components.uptimerobot.const import DOMAIN +from homeassistant.const import CONF_API_KEY from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import ( RESULT_TYPE_ABORT, @@ -17,6 +15,15 @@ from homeassistant.data_entry_flow import ( RESULT_TYPE_FORM, ) +from .common import ( + MOCK_UPTIMEROBOT_ACCOUNT, + MOCK_UPTIMEROBOT_API_KEY, + MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, + MOCK_UPTIMEROBOT_UNIQUE_ID, + MockApiResponseKey, + mock_uptimerobot_api_response, +) + from tests.common import MockConfigEntry @@ -31,82 +38,49 @@ async def test_form(hass: HomeAssistant) -> None: with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), 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": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() - assert result2["result"].unique_id == "1234567890" + assert result2["result"].unique_id == MOCK_UPTIMEROBOT_UNIQUE_ID assert result2["type"] == RESULT_TYPE_CREATE_ENTRY - assert result2["title"] == "test@test.test" - assert result2["data"] == {"api_key": "1234"} + assert result2["title"] == MOCK_UPTIMEROBOT_ACCOUNT["email"] + assert result2["data"] == {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY} assert len(mock_setup_entry.mock_calls) == 1 -async def test_form_cannot_connect(hass: HomeAssistant) -> None: - """Test we handle cannot connect error.""" +@pytest.mark.parametrize( + "exception,error_key", + [ + (Exception, "unknown"), + (UptimeRobotException, "cannot_connect"), + (UptimeRobotAuthenticationException, "invalid_api_key"), + ], +) +async def test_form_exception_thrown(hass: HomeAssistant, exception, error_key) -> None: + """Test that we handle exceptions.""" 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=UptimeRobotException, + side_effect=exception, ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) assert result2["type"] == RESULT_TYPE_FORM - 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" + assert result2["errors"]["base"] == error_key async def test_form_api_error(hass: HomeAssistant, caplog: LogCaptureFixture) -> None: @@ -117,32 +91,24 @@ async def test_form_api_error(hass: HomeAssistant, caplog: LogCaptureFixture) -> with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "fail", - "error": {"message": "test error from API."}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), ): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) 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: HomeAssistant, +) -> None: """Test an import flow.""" with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -150,22 +116,17 @@ async def test_flow_import(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={"platform": DOMAIN, "api_key": "1234"}, + data={"platform": DOMAIN, CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() assert len(mock_setup_entry.mock_calls) == 1 assert result["type"] == RESULT_TYPE_CREATE_ENTRY - assert result["data"] == {"api_key": "1234"} + assert result["data"] == {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY} with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -173,7 +134,7 @@ async def test_flow_import(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={"platform": DOMAIN, "api_key": "1234"}, + data={"platform": DOMAIN, CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() @@ -183,7 +144,9 @@ async def test_flow_import(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict({"stat": "ok"}), + return_value=mock_uptimerobot_api_response( + key=MockApiResponseKey.ACCOUNT, data={} + ), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -191,7 +154,7 @@ async def test_flow_import(hass): result = await hass.config_entries.flow.async_init( DOMAIN, context={"source": config_entries.SOURCE_IMPORT}, - data={"platform": DOMAIN, "api_key": "12345"}, + data={"platform": DOMAIN, CONF_API_KEY: "12345"}, ) await hass.async_block_till_done() @@ -199,13 +162,11 @@ async def test_flow_import(hass): assert result["reason"] == "unknown" -async def test_user_unique_id_already_exists(hass): +async def test_user_unique_id_already_exists( + hass: HomeAssistant, +) -> None: """Test creating an entry where the unique_id already exists.""" - entry = MockConfigEntry( - domain=DOMAIN, - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - ) + entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -216,19 +177,14 @@ async def test_user_unique_id_already_exists(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), 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"}, + {CONF_API_KEY: "12345"}, ) await hass.async_block_till_done() @@ -237,13 +193,11 @@ async def test_user_unique_id_already_exists(hass): assert result2["reason"] == "already_configured" -async def test_reauthentication(hass): +async def test_reauthentication( + hass: HomeAssistant, +) -> None: """Test Uptime Robot reauthentication.""" - old_entry = MockConfigEntry( - domain=DOMAIN, - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - ) + old_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) old_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -262,12 +216,7 @@ async def test_reauthentication(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -275,7 +224,7 @@ async def test_reauthentication(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() @@ -283,13 +232,11 @@ async def test_reauthentication(hass): assert result2["reason"] == "reauth_successful" -async def test_reauthentication_failure(hass): +async def test_reauthentication_failure( + hass: HomeAssistant, +) -> None: """Test Uptime Robot reauthentication failure.""" - old_entry = MockConfigEntry( - domain=DOMAIN, - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - ) + old_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) old_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -308,12 +255,7 @@ async def test_reauthentication_failure(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "fail", - "error": {"message": "test error from API."}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -321,7 +263,7 @@ async def test_reauthentication_failure(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() @@ -330,11 +272,12 @@ async def test_reauthentication_failure(hass): assert result2["errors"]["base"] == "unknown" -async def test_reauthentication_failure_no_existing_entry(hass): +async def test_reauthentication_failure_no_existing_entry( + hass: HomeAssistant, +) -> None: """Test Uptime Robot reauthentication with no existing entry.""" old_entry = MockConfigEntry( - domain=DOMAIN, - data={"platform": DOMAIN, "api_key": "1234"}, + **{**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, "unique_id": None} ) old_entry.add_to_hass(hass) @@ -354,12 +297,7 @@ async def test_reauthentication_failure_no_existing_entry(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567890}, - } - ), + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ACCOUNT), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", return_value=True, @@ -367,7 +305,7 @@ async def test_reauthentication_failure_no_existing_entry(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() @@ -375,13 +313,11 @@ async def test_reauthentication_failure_no_existing_entry(hass): assert result2["reason"] == "reauth_failed_existing" -async def test_reauthentication_failure_account_not_matching(hass): +async def test_reauthentication_failure_account_not_matching( + hass: HomeAssistant, +) -> None: """Test Uptime Robot reauthentication failure when using another account.""" - old_entry = MockConfigEntry( - domain=DOMAIN, - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - ) + old_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) old_entry.add_to_hass(hass) result = await hass.config_entries.flow.async_init( @@ -400,11 +336,9 @@ async def test_reauthentication_failure_account_not_matching(hass): with patch( "pyuptimerobot.UptimeRobot.async_get_account_details", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "account": {"email": "test@test.test", "user_id": 1234567891}, - } + return_value=mock_uptimerobot_api_response( + key=MockApiResponseKey.ACCOUNT, + data={**MOCK_UPTIMEROBOT_ACCOUNT, "user_id": 1234567891}, ), ), patch( "homeassistant.components.uptimerobot.async_setup_entry", @@ -413,7 +347,7 @@ async def test_reauthentication_failure_account_not_matching(hass): result2 = await hass.config_entries.flow.async_configure( result["flow_id"], - {"api_key": "1234"}, + {CONF_API_KEY: MOCK_UPTIMEROBOT_API_KEY}, ) await hass.async_block_till_done() diff --git a/tests/components/uptimerobot/test_init.py b/tests/components/uptimerobot/test_init.py index b4534af763a..756831e7615 100644 --- a/tests/components/uptimerobot/test_init.py +++ b/tests/components/uptimerobot/test_init.py @@ -1,16 +1,31 @@ """Test the Uptime Robot init.""" -import datetime from unittest.mock import patch from pytest import LogCaptureFixture -from pyuptimerobot import UptimeRobotApiResponse -from pyuptimerobot.exceptions import UptimeRobotAuthenticationException +from pyuptimerobot import UptimeRobotAuthenticationException, UptimeRobotException from homeassistant import config_entries -from homeassistant.components.uptimerobot.const import DOMAIN +from homeassistant.components.uptimerobot.const import ( + COORDINATOR_UPDATE_INTERVAL, + DOMAIN, +) +from homeassistant.const import STATE_ON, STATE_UNAVAILABLE from homeassistant.core import HomeAssistant +from homeassistant.helpers.device_registry import ( + async_entries_for_config_entry, + async_get_registry, +) from homeassistant.util import dt +from .common import ( + MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA, + MOCK_UPTIMEROBOT_MONITOR, + UPTIMEROBOT_TEST_ENTITY, + MockApiResponseKey, + mock_uptimerobot_api_response, + setup_uptimerobot_integration, +) + from tests.common import MockConfigEntry, async_fire_time_changed @@ -18,13 +33,7 @@ async def test_reauthentication_trigger_in_setup( hass: HomeAssistant, caplog: LogCaptureFixture ): """Test reauthentication trigger.""" - mock_config_entry = MockConfigEntry( - domain=DOMAIN, - title="test@test.test", - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - source=config_entries.SOURCE_USER, - ) + mock_config_entry = MockConfigEntry(**MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA) mock_config_entry.add_to_hass(hass) with patch( @@ -57,46 +66,23 @@ async def test_reauthentication_trigger_after_setup( hass: HomeAssistant, caplog: LogCaptureFixture ): """Test reauthentication trigger.""" - mock_config_entry = MockConfigEntry( - domain=DOMAIN, - title="test@test.test", - data={"platform": DOMAIN, "api_key": "1234"}, - unique_id="1234567890", - source=config_entries.SOURCE_USER, - ) - mock_config_entry.add_to_hass(hass) + mock_config_entry = await setup_uptimerobot_integration(hass) - with patch( - "pyuptimerobot.UptimeRobot.async_get_monitors", - return_value=UptimeRobotApiResponse.from_dict( - { - "stat": "ok", - "monitors": [ - {"id": 1234, "friendly_name": "Test monitor", "status": 2} - ], - } - ), - ): - - await hass.config_entries.async_setup(mock_config_entry.entry_id) - await hass.async_block_till_done() - - binary_sensor = hass.states.get("binary_sensor.test_monitor") + binary_sensor = hass.states.get(UPTIMEROBOT_TEST_ENTITY) assert mock_config_entry.state == config_entries.ConfigEntryState.LOADED - assert binary_sensor.state == "on" + assert binary_sensor.state == STATE_ON with patch( "pyuptimerobot.UptimeRobot.async_get_monitors", side_effect=UptimeRobotAuthenticationException, ): - async_fire_time_changed(hass, dt.utcnow() + datetime.timedelta(seconds=10)) + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) await hass.async_block_till_done() flows = hass.config_entries.flow.async_progress() - binary_sensor = hass.states.get("binary_sensor.test_monitor") + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE - assert binary_sensor.state == "unavailable" assert "Authentication failed while fetching uptimerobot data" in caplog.text assert len(flows) == 1 @@ -105,3 +91,96 @@ async def test_reauthentication_trigger_after_setup( assert flow["handler"] == DOMAIN assert flow["context"]["source"] == config_entries.SOURCE_REAUTH assert flow["context"]["entry_id"] == mock_config_entry.entry_id + + +async def test_integration_reload(hass: HomeAssistant): + """Test integration reload.""" + mock_entry = await setup_uptimerobot_integration(hass) + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(), + ): + assert await hass.config_entries.async_reload(mock_entry.entry_id) + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + + entry = hass.config_entries.async_get_entry(mock_entry.entry_id) + assert entry.state == config_entries.ConfigEntryState.LOADED + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + + +async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture): + """Test errors during updates.""" + await setup_uptimerobot_integration(hass) + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + side_effect=UptimeRobotException, + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(), + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(key=MockApiResponseKey.ERROR), + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE + + assert "Error fetching uptimerobot data: test error from API" in caplog.text + + +async def test_device_management(hass: HomeAssistant): + """Test that we are adding and removing devices for monitors returned from the API.""" + mock_entry = await setup_uptimerobot_integration(hass) + dev_reg = await async_get_registry(hass) + + devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + assert len(devices) == 1 + + assert devices[0].identifiers == {(DOMAIN, "1234")} + + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response( + data=[MOCK_UPTIMEROBOT_MONITOR, {**MOCK_UPTIMEROBOT_MONITOR, "id": 12345}] + ), + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + + devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + assert len(devices) == 2 + assert devices[0].identifiers == {(DOMAIN, "1234")} + assert devices[1].identifiers == {(DOMAIN, "12345")} + + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2").state == STATE_ON + + with patch( + "pyuptimerobot.UptimeRobot.async_get_monitors", + return_value=mock_uptimerobot_api_response(), + ): + async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL) + await hass.async_block_till_done() + + devices = async_entries_for_config_entry(dev_reg, mock_entry.entry_id) + assert len(devices) == 1 + assert devices[0].identifiers == {(DOMAIN, "1234")} + + assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON + assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None