Add config flow to Uptime (#67408)

This commit is contained in:
Franck Nijhof 2022-03-12 12:36:08 +01:00 committed by GitHub
parent 23b8229143
commit 0467dc55fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 308 additions and 20 deletions

View file

@ -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

View file

@ -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)

View 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)

View 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"

View file

@ -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
} }

View file

@ -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

View 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%]"
}
}
}

View 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"
}

View file

@ -355,6 +355,7 @@ FLOWS = [
"upb", "upb",
"upcloud", "upcloud",
"upnp", "upnp",
"uptime",
"uptimerobot", "uptimerobot",
"vallox", "vallox",
"velbus", "velbus",

View 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

View 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") == {}

View 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 == {}

View file

@ -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