Add config flow to Uptime (#67408)
This commit is contained in:
parent
23b8229143
commit
0467dc55fe
13 changed files with 308 additions and 20 deletions
|
@ -1066,6 +1066,8 @@ homeassistant/components/updater/* @home-assistant/core
|
||||||
tests/components/updater/* @home-assistant/core
|
tests/components/updater/* @home-assistant/core
|
||||||
homeassistant/components/upnp/* @StevenLooman @ehendrix23
|
homeassistant/components/upnp/* @StevenLooman @ehendrix23
|
||||||
tests/components/upnp/* @StevenLooman @ehendrix23
|
tests/components/upnp/* @StevenLooman @ehendrix23
|
||||||
|
homeassistant/components/uptime/* @frenck
|
||||||
|
tests/components/uptime/* @frenck
|
||||||
homeassistant/components/uptimerobot/* @ludeeus @chemelli74
|
homeassistant/components/uptimerobot/* @ludeeus @chemelli74
|
||||||
tests/components/uptimerobot/* @ludeeus @chemelli74
|
tests/components/uptimerobot/* @ludeeus @chemelli74
|
||||||
homeassistant/components/usb/* @bdraco
|
homeassistant/components/usb/* @bdraco
|
||||||
|
|
|
@ -1 +1,16 @@
|
||||||
"""The uptime component."""
|
"""The Uptime integration."""
|
||||||
|
from homeassistant.config_entries import ConfigEntry
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from .const import PLATFORMS
|
||||||
|
|
||||||
|
|
||||||
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Set up from a config entry."""
|
||||||
|
hass.config_entries.async_setup_platforms(entry, PLATFORMS)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
|
"""Unload a config entry."""
|
||||||
|
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||||
|
|
37
homeassistant/components/uptime/config_flow.py
Normal file
37
homeassistant/components/uptime/config_flow.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
"""Config flow to configure the Uptime integration."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import voluptuous as vol
|
||||||
|
|
||||||
|
from homeassistant.config_entries import ConfigFlow
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
|
|
||||||
|
from .const import DEFAULT_NAME, DOMAIN
|
||||||
|
|
||||||
|
|
||||||
|
class UptimeConfigFlow(ConfigFlow, domain=DOMAIN):
|
||||||
|
"""Config flow for Uptime."""
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
async def async_step_user(
|
||||||
|
self, user_input: dict[str, Any] | None = None
|
||||||
|
) -> FlowResult:
|
||||||
|
"""Handle a flow initialized by the user."""
|
||||||
|
if self._async_current_entries():
|
||||||
|
return self.async_abort(reason="single_instance_allowed")
|
||||||
|
|
||||||
|
if user_input is not None:
|
||||||
|
return self.async_create_entry(
|
||||||
|
title=user_input.get(CONF_NAME, DEFAULT_NAME),
|
||||||
|
data={},
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.async_show_form(step_id="user", data_schema=vol.Schema({}))
|
||||||
|
|
||||||
|
async def async_step_import(self, user_input: dict[str, Any]) -> FlowResult:
|
||||||
|
"""Handle import from configuration.yaml."""
|
||||||
|
return await self.async_step_user(user_input)
|
9
homeassistant/components/uptime/const.py
Normal file
9
homeassistant/components/uptime/const.py
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
"""Constants for the Uptime integration."""
|
||||||
|
from typing import Final
|
||||||
|
|
||||||
|
from homeassistant.const import Platform
|
||||||
|
|
||||||
|
DOMAIN: Final = "uptime"
|
||||||
|
PLATFORMS: Final = [Platform.SENSOR]
|
||||||
|
|
||||||
|
DEFAULT_NAME: Final = "Uptime"
|
|
@ -2,7 +2,8 @@
|
||||||
"domain": "uptime",
|
"domain": "uptime",
|
||||||
"name": "Uptime",
|
"name": "Uptime",
|
||||||
"documentation": "https://www.home-assistant.io/integrations/uptime",
|
"documentation": "https://www.home-assistant.io/integrations/uptime",
|
||||||
"codeowners": [],
|
"codeowners": ["@frenck"],
|
||||||
"quality_scale": "internal",
|
"quality_scale": "internal",
|
||||||
"iot_class": "local_push"
|
"iot_class": "local_push",
|
||||||
|
"config_flow": true
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ from homeassistant.components.sensor import (
|
||||||
SensorDeviceClass,
|
SensorDeviceClass,
|
||||||
SensorEntity,
|
SensorEntity,
|
||||||
)
|
)
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
||||||
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
from homeassistant.const import CONF_NAME, CONF_UNIT_OF_MEASUREMENT
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
import homeassistant.helpers.config_validation as cv
|
import homeassistant.helpers.config_validation as cv
|
||||||
|
@ -15,17 +16,15 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||||
import homeassistant.util.dt as dt_util
|
import homeassistant.util.dt as dt_util
|
||||||
|
|
||||||
DEFAULT_NAME = "Uptime"
|
from .const import DEFAULT_NAME, DOMAIN
|
||||||
|
|
||||||
PLATFORM_SCHEMA = vol.All(
|
PLATFORM_SCHEMA = vol.All(
|
||||||
cv.deprecated(CONF_UNIT_OF_MEASUREMENT),
|
cv.removed(CONF_UNIT_OF_MEASUREMENT, raise_if_present=False),
|
||||||
PLATFORM_SCHEMA.extend(
|
PLATFORM_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||||
vol.Optional(CONF_UNIT_OF_MEASUREMENT, default="days"): vol.All(
|
vol.Remove(CONF_UNIT_OF_MEASUREMENT): cv.string,
|
||||||
cv.string, vol.In(["minutes", "hours", "days", "seconds"])
|
},
|
||||||
),
|
|
||||||
}
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -37,9 +36,22 @@ async def async_setup_platform(
|
||||||
discovery_info: DiscoveryInfoType | None = None,
|
discovery_info: DiscoveryInfoType | None = None,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the uptime sensor platform."""
|
"""Set up the uptime sensor platform."""
|
||||||
name = config[CONF_NAME]
|
hass.async_create_task(
|
||||||
|
hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_IMPORT},
|
||||||
|
data=config,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
async_add_entities([UptimeSensor(name)], True)
|
|
||||||
|
async def async_setup_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
entry: ConfigEntry,
|
||||||
|
async_add_entities: AddEntitiesCallback,
|
||||||
|
) -> None:
|
||||||
|
"""Set up the platform from config_entry."""
|
||||||
|
async_add_entities([UptimeSensor(entry)])
|
||||||
|
|
||||||
|
|
||||||
class UptimeSensor(SensorEntity):
|
class UptimeSensor(SensorEntity):
|
||||||
|
@ -48,7 +60,8 @@ class UptimeSensor(SensorEntity):
|
||||||
_attr_device_class = SensorDeviceClass.TIMESTAMP
|
_attr_device_class = SensorDeviceClass.TIMESTAMP
|
||||||
_attr_should_poll = False
|
_attr_should_poll = False
|
||||||
|
|
||||||
def __init__(self, name: str) -> None:
|
def __init__(self, entry: ConfigEntry) -> None:
|
||||||
"""Initialize the uptime sensor."""
|
"""Initialize the uptime sensor."""
|
||||||
self._attr_name = name
|
self._attr_name = entry.title
|
||||||
self._attr_native_value = dt_util.utcnow()
|
self._attr_native_value = dt_util.utcnow()
|
||||||
|
self._attr_unique_id = entry.entry_id
|
||||||
|
|
13
homeassistant/components/uptime/strings.json
Normal file
13
homeassistant/components/uptime/strings.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"title": "Uptime",
|
||||||
|
"config": {
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"description": "[%key:common::config_flow::description::confirm_setup%]"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "[%key:common::config_flow::abort::single_instance_allowed%]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
homeassistant/components/uptime/translations/en.json
Normal file
13
homeassistant/components/uptime/translations/en.json
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{
|
||||||
|
"config": {
|
||||||
|
"abort": {
|
||||||
|
"single_instance_allowed": "Already configured. Only a single configuration possible."
|
||||||
|
},
|
||||||
|
"step": {
|
||||||
|
"user": {
|
||||||
|
"description": "Do you want to start set up?"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"title": "Uptime"
|
||||||
|
}
|
|
@ -355,6 +355,7 @@ FLOWS = [
|
||||||
"upb",
|
"upb",
|
||||||
"upcloud",
|
"upcloud",
|
||||||
"upnp",
|
"upnp",
|
||||||
|
"uptime",
|
||||||
"uptimerobot",
|
"uptimerobot",
|
||||||
"vallox",
|
"vallox",
|
||||||
"velbus",
|
"velbus",
|
||||||
|
|
41
tests/components/uptime/conftest.py
Normal file
41
tests/components/uptime/conftest.py
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
"""Fixtures for Uptime integration tests."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Generator
|
||||||
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.uptime.const import DOMAIN
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_config_entry() -> MockConfigEntry:
|
||||||
|
"""Return the default mocked config entry."""
|
||||||
|
return MockConfigEntry(
|
||||||
|
title="Uptime",
|
||||||
|
domain=DOMAIN,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_setup_entry() -> Generator[None, None, None]:
|
||||||
|
"""Mock setting up a config entry."""
|
||||||
|
with patch("homeassistant.components.uptime.async_setup_entry", return_value=True):
|
||||||
|
yield
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
async def init_integration(
|
||||||
|
hass: HomeAssistant, mock_config_entry: MockConfigEntry
|
||||||
|
) -> MockConfigEntry:
|
||||||
|
"""Set up the Uptime integration for testing."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
return mock_config_entry
|
72
tests/components/uptime/test_config_flow.py
Normal file
72
tests/components/uptime/test_config_flow.py
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
"""Tests for the Uptime config flow."""
|
||||||
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from homeassistant.components.uptime.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.data_entry_flow import (
|
||||||
|
RESULT_TYPE_ABORT,
|
||||||
|
RESULT_TYPE_CREATE_ENTRY,
|
||||||
|
RESULT_TYPE_FORM,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_full_user_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the full user configuration flow."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": SOURCE_USER}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.get("type") == RESULT_TYPE_FORM
|
||||||
|
assert result.get("step_id") == SOURCE_USER
|
||||||
|
assert "flow_id" in result
|
||||||
|
|
||||||
|
result2 = await hass.config_entries.flow.async_configure(
|
||||||
|
result["flow_id"],
|
||||||
|
user_input={},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result2.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result2.get("title") == "Uptime"
|
||||||
|
assert result2.get("data") == {}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("source", [SOURCE_USER, SOURCE_IMPORT])
|
||||||
|
async def test_single_instance_allowed(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
source: str,
|
||||||
|
) -> None:
|
||||||
|
"""Test we abort if already setup."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN, context={"source": source}
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.get("type") == RESULT_TYPE_ABORT
|
||||||
|
assert result.get("reason") == "single_instance_allowed"
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_flow(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: MagicMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test the import configuration flow."""
|
||||||
|
result = await hass.config_entries.flow.async_init(
|
||||||
|
DOMAIN,
|
||||||
|
context={"source": SOURCE_IMPORT},
|
||||||
|
data={CONF_NAME: "My Uptime"},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result.get("type") == RESULT_TYPE_CREATE_ENTRY
|
||||||
|
assert result.get("title") == "My Uptime"
|
||||||
|
assert result.get("data") == {}
|
55
tests/components/uptime/test_init.py
Normal file
55
tests/components/uptime/test_init.py
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
"""Tests for the Uptime integration."""
|
||||||
|
from unittest.mock import AsyncMock
|
||||||
|
|
||||||
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||||
|
from homeassistant.components.uptime.const import DOMAIN
|
||||||
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.setup import async_setup_component
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
|
async def test_load_unload_config_entry(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
) -> None:
|
||||||
|
"""Test the Uptime configuration entry loading/unloading."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.LOADED
|
||||||
|
|
||||||
|
await hass.config_entries.async_unload(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
assert not hass.data.get(DOMAIN)
|
||||||
|
assert mock_config_entry.state is ConfigEntryState.NOT_LOADED
|
||||||
|
|
||||||
|
|
||||||
|
async def test_import_config(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_setup_entry: AsyncMock,
|
||||||
|
) -> None:
|
||||||
|
"""Test Uptime being set up from config via import."""
|
||||||
|
assert await async_setup_component(
|
||||||
|
hass,
|
||||||
|
SENSOR_DOMAIN,
|
||||||
|
{
|
||||||
|
SENSOR_DOMAIN: {
|
||||||
|
"platform": DOMAIN,
|
||||||
|
CONF_NAME: "My Uptime",
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||||
|
assert len(config_entries) == 1
|
||||||
|
|
||||||
|
entry = config_entries[0]
|
||||||
|
assert entry.title == "My Uptime"
|
||||||
|
assert entry.unique_id is None
|
||||||
|
assert entry.data == {}
|
|
@ -1,11 +1,27 @@
|
||||||
"""The tests for the uptime sensor platform."""
|
"""The tests for the uptime sensor platform."""
|
||||||
|
import pytest
|
||||||
|
|
||||||
from homeassistant.setup import async_setup_component
|
from homeassistant.components.sensor import SensorDeviceClass
|
||||||
|
from homeassistant.const import ATTR_DEVICE_CLASS
|
||||||
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
|
|
||||||
async def test_uptime_sensor_name_change(hass):
|
@pytest.mark.freeze_time("2022-03-01 00:00:00+00:00")
|
||||||
"""Test uptime sensor with different name."""
|
async def test_uptime_sensor(
|
||||||
config = {"sensor": {"platform": "uptime", "name": "foobar"}}
|
hass: HomeAssistant,
|
||||||
assert await async_setup_component(hass, "sensor", config)
|
init_integration: MockConfigEntry,
|
||||||
await hass.async_block_till_done()
|
) -> None:
|
||||||
assert hass.states.get("sensor.foobar")
|
"""Test Uptime sensor."""
|
||||||
|
state = hass.states.get("sensor.uptime")
|
||||||
|
assert state
|
||||||
|
assert state.state == "2022-03-01T00:00:00+00:00"
|
||||||
|
assert state.attributes["friendly_name"] == "Uptime"
|
||||||
|
assert state.attributes[ATTR_DEVICE_CLASS] == SensorDeviceClass.TIMESTAMP
|
||||||
|
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
entry = entity_registry.async_get("sensor.uptime")
|
||||||
|
assert entry
|
||||||
|
assert entry.unique_id == init_integration.entry_id
|
||||||
|
|
Loading…
Add table
Reference in a new issue