Skip to content

Commit

Permalink
Add detailed status for UptimeRobot (home-assistant#64879)
Browse files Browse the repository at this point in the history
Co-authored-by: Joakim Sørensen <[email protected]>
  • Loading branch information
chemelli74 and ludeeus authored Jan 26, 2022
1 parent eb5c607 commit 3f12ce0
Show file tree
Hide file tree
Showing 10 changed files with 178 additions and 23 deletions.
4 changes: 2 additions & 2 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -999,8 +999,8 @@ homeassistant/components/updater/* @home-assistant/core
tests/components/updater/* @home-assistant/core
homeassistant/components/upnp/* @StevenLooman @ehendrix23
tests/components/upnp/* @StevenLooman @ehendrix23
homeassistant/components/uptimerobot/* @ludeeus
tests/components/uptimerobot/* @ludeeus
homeassistant/components/uptimerobot/* @ludeeus @chemelli74
tests/components/uptimerobot/* @ludeeus @chemelli74
homeassistant/components/usb/* @bdraco
tests/components/usb/* @bdraco
homeassistant/components/usgs_earthquakes_feed/* @exxamalte
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/uptimerobot/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
COORDINATOR_UPDATE_INTERVAL: timedelta = timedelta(seconds=10)

DOMAIN: Final = "uptimerobot"
PLATFORMS: Final = [Platform.BINARY_SENSOR]
PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]

ATTRIBUTION: Final = "Data provided by UptimeRobot"

Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/uptimerobot/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"pyuptimerobot==21.11.0"
],
"codeowners": [
"@ludeeus"
"@ludeeus", "@chemelli74"
],
"quality_scale": "platinum",
"iot_class": "cloud_polling",
Expand Down
68 changes: 68 additions & 0 deletions homeassistant/components/uptimerobot/sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
"""UptimeRobot sensor platform."""
from __future__ import annotations

from typing import TypedDict

from homeassistant.components.sensor import SensorEntity, SensorEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import EntityCategory
from homeassistant.helpers.entity_platform import AddEntitiesCallback

from . import UptimeRobotDataUpdateCoordinator
from .const import DOMAIN
from .entity import UptimeRobotEntity


class StatusValue(TypedDict):
"""Sensor details."""

value: str
icon: str


SENSORS_INFO = {
0: StatusValue(value="pause", icon="mdi:television-pause"),
1: StatusValue(value="not_checked_yet", icon="mdi:television"),
2: StatusValue(value="up", icon="mdi:television-shimmer"),
8: StatusValue(value="seems_down", icon="mdi:television-off"),
9: StatusValue(value="down", icon="mdi:television-off"),
}


async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the UptimeRobot sensors."""
coordinator: UptimeRobotDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
[
UptimeRobotSensor(
coordinator,
SensorEntityDescription(
key=str(monitor.id),
name=monitor.friendly_name,
entity_category=EntityCategory.DIAGNOSTIC,
device_class="uptimerobot__monitor_status",
),
monitor=monitor,
)
for monitor in coordinator.data
],
)


class UptimeRobotSensor(UptimeRobotEntity, SensorEntity):
"""Representation of a UptimeRobot sensor."""

@property
def native_value(self) -> str:
"""Return the status of the monitor."""
return SENSORS_INFO[self.monitor.status]["value"]

@property
def icon(self) -> str:
"""Return the status of the monitor."""
return SENSORS_INFO[self.monitor.status]["icon"]
11 changes: 11 additions & 0 deletions homeassistant/components/uptimerobot/strings.sensor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"state": {
"uptimerobot__monitor_status": {
"pause": "Pause",
"not_checked_yet": "Not checked yet",
"up": "Up",
"seems_down": "Seems down",
"down": "Down"
}
}
}
11 changes: 11 additions & 0 deletions homeassistant/components/uptimerobot/translations/sensor.en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"state": {
"uptimerobot__monitor_status": {
"down": "Down",
"not_checked_yet": "Not checked yet",
"pause": "Pause",
"seems_down": "Seems down",
"up": "Up"
}
}
}
8 changes: 6 additions & 2 deletions tests/components/uptimerobot/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@
"source": config_entries.SOURCE_USER,
}

UPTIMEROBOT_TEST_ENTITY = "binary_sensor.test_monitor"
STATE_UP = "up"

UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY = "binary_sensor.test_monitor"
UPTIMEROBOT_SENSOR_TEST_ENTITY = "sensor.test_monitor"


class MockApiResponseKey(str, Enum):
Expand Down Expand Up @@ -94,7 +97,8 @@ async def setup_uptimerobot_integration(hass: HomeAssistant) -> MockConfigEntry:
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 hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY).state == STATE_UP
assert mock_entry.state == config_entries.ConfigEntryState.LOADED

return mock_entry
8 changes: 4 additions & 4 deletions tests/components/uptimerobot/test_binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .common import (
MOCK_UPTIMEROBOT_MONITOR,
UPTIMEROBOT_TEST_ENTITY,
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
setup_uptimerobot_integration,
)

Expand All @@ -26,7 +26,7 @@ async def test_presentation(hass: HomeAssistant) -> None:
"""Test the presenstation of UptimeRobot binary_sensors."""
await setup_uptimerobot_integration(hass)

entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)

assert entity.state == STATE_ON
assert entity.attributes["device_class"] == BinarySensorDeviceClass.CONNECTIVITY
Expand All @@ -38,7 +38,7 @@ 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)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert entity.state == STATE_ON

with patch(
Expand All @@ -48,5 +48,5 @@ async def test_unaviable_on_update_failure(hass: HomeAssistant) -> None:
async_fire_time_changed(hass, dt.utcnow() + COORDINATOR_UPDATE_INTERVAL)
await hass.async_block_till_done()

entity = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
entity = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UNAVAILABLE
37 changes: 24 additions & 13 deletions tests/components/uptimerobot/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
from .common import (
MOCK_UPTIMEROBOT_CONFIG_ENTRY_DATA,
MOCK_UPTIMEROBOT_MONITOR,
UPTIMEROBOT_TEST_ENTITY,
UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY,
MockApiResponseKey,
mock_uptimerobot_api_response,
setup_uptimerobot_integration,
Expand Down Expand Up @@ -68,7 +68,7 @@ async def test_reauthentication_trigger_after_setup(
"""Test reauthentication trigger."""
mock_config_entry = await setup_uptimerobot_integration(hass)

binary_sensor = hass.states.get(UPTIMEROBOT_TEST_ENTITY)
binary_sensor = hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY)
assert mock_config_entry.state == config_entries.ConfigEntryState.LOADED
assert binary_sensor.state == STATE_ON

Expand All @@ -81,7 +81,10 @@ async def test_reauthentication_trigger_after_setup(
await hass.async_block_till_done()

flows = hass.config_entries.flow.async_progress()
assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_UNAVAILABLE
assert (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state
== STATE_UNAVAILABLE
)

assert "Authentication failed while fetching uptimerobot data" in caplog.text

Expand All @@ -107,7 +110,7 @@ async def test_integration_reload(hass: HomeAssistant):

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
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON


async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
Expand All @@ -120,23 +123,29 @@ async def test_update_errors(hass: HomeAssistant, caplog: LogCaptureFixture):
):
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 (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_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
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_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 (
hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state
== STATE_UNAVAILABLE
)

assert "Error fetching uptimerobot data: test error from API" in caplog.text

Expand All @@ -152,8 +161,8 @@ async def test_device_management(hass: HomeAssistant):
assert devices[0].identifiers == {(DOMAIN, "1234")}
assert devices[0].name == "Test monitor"

assert hass.states.get(UPTIMEROBOT_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_TEST_ENTITY}_2") is None
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None

with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
Expand All @@ -169,8 +178,10 @@ async def test_device_management(hass: HomeAssistant):
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
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert (
hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2").state == STATE_ON
)

with patch(
"pyuptimerobot.UptimeRobot.async_get_monitors",
Expand All @@ -183,5 +194,5 @@ async def test_device_management(hass: HomeAssistant):
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
assert hass.states.get(UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY).state == STATE_ON
assert hass.states.get(f"{UPTIMEROBOT_BINARY_SENSOR_TEST_ENTITY}_2") is None
50 changes: 50 additions & 0 deletions tests/components/uptimerobot/test_sensor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
"""Test UptimeRobot sensor."""

from unittest.mock import patch

from pyuptimerobot import UptimeRobotAuthenticationException

from homeassistant.components.uptimerobot.const import COORDINATOR_UPDATE_INTERVAL
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import HomeAssistant
from homeassistant.util import dt

from .common import (
MOCK_UPTIMEROBOT_MONITOR,
STATE_UP,
UPTIMEROBOT_SENSOR_TEST_ENTITY,
setup_uptimerobot_integration,
)

from tests.common import async_fire_time_changed

SENSOR_ICON = "mdi:television-shimmer"


async def test_presentation(hass: HomeAssistant) -> None:
"""Test the presenstation of UptimeRobot sensors."""
await setup_uptimerobot_integration(hass)

entity = hass.states.get(UPTIMEROBOT_SENSOR_TEST_ENTITY)

assert entity.state == STATE_UP
assert entity.attributes["icon"] == SENSOR_ICON
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_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UP

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_SENSOR_TEST_ENTITY)
assert entity.state == STATE_UNAVAILABLE

0 comments on commit 3f12ce0

Please sign in to comment.