Add config flow to trend (#99761)

* Add config flow to trend

* Remove device_class from options flow

* Add min_samples and import step to config flow

* Fix import

* Fixing tests and some cleanup

* remove unneeded usefixtures

* Apply code review suggestions

* Re-add YAML support

* Re-add reload service

* Fix import

* Apply code review suggestions

* Add test coverage for yaml setup

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
This commit is contained in:
Jan-Philipp Benecke 2023-12-27 14:46:57 +01:00 committed by GitHub
parent 4decc2bbfb
commit e04fda3fad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 508 additions and 126 deletions

View File

@ -1,5 +1,27 @@
"""A sensor that monitors trends in other components."""
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = [Platform.BINARY_SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up Trend from a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(config_entry_update_listener))
return True
async def config_entry_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle an Trend options update."""
await hass.config_entries.async_reload(entry.entry_id)
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

@ -17,6 +17,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_ENTITY_ID,
ATTR_FRIENDLY_NAME,
@ -54,6 +55,10 @@ from .const import (
CONF_MIN_GRADIENT,
CONF_MIN_SAMPLES,
CONF_SAMPLE_DURATION,
DEFAULT_MAX_SAMPLES,
DEFAULT_MIN_GRADIENT,
DEFAULT_MIN_SAMPLES,
DEFAULT_SAMPLE_DURATION,
DOMAIN,
)
@ -101,40 +106,52 @@ async def async_setup_platform(
"""Set up the trend sensors."""
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
sensors = []
for device_id, device_config in config[CONF_SENSORS].items():
entity_id = device_config[ATTR_ENTITY_ID]
attribute = device_config.get(CONF_ATTRIBUTE)
device_class = device_config.get(CONF_DEVICE_CLASS)
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device_id)
invert = device_config[CONF_INVERT]
max_samples = device_config[CONF_MAX_SAMPLES]
min_gradient = device_config[CONF_MIN_GRADIENT]
sample_duration = device_config[CONF_SAMPLE_DURATION]
min_samples = device_config[CONF_MIN_SAMPLES]
sensors.append(
entities = []
for sensor_name, sensor_config in config[CONF_SENSORS].items():
entities.append(
SensorTrend(
hass,
device_id,
friendly_name,
entity_id,
attribute,
device_class,
invert,
max_samples,
min_gradient,
sample_duration,
min_samples,
name=sensor_config.get(CONF_FRIENDLY_NAME, sensor_name),
entity_id=sensor_config[CONF_ENTITY_ID],
attribute=sensor_config.get(CONF_ATTRIBUTE),
invert=sensor_config[CONF_INVERT],
sample_duration=sensor_config[CONF_SAMPLE_DURATION],
min_gradient=sensor_config[CONF_MIN_GRADIENT],
min_samples=sensor_config[CONF_MIN_SAMPLES],
max_samples=sensor_config[CONF_MAX_SAMPLES],
device_class=sensor_config.get(CONF_DEVICE_CLASS),
sensor_entity_id=generate_entity_id(
ENTITY_ID_FORMAT, sensor_name, hass=hass
),
)
)
if not sensors:
_LOGGER.error("No sensors added")
return
async_add_entities(entities)
async_add_entities(sensors)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up trend sensor from config entry."""
async_add_entities(
[
SensorTrend(
name=entry.title,
entity_id=entry.options[CONF_ENTITY_ID],
attribute=entry.options.get(CONF_ATTRIBUTE),
invert=entry.options[CONF_INVERT],
sample_duration=entry.options.get(
CONF_SAMPLE_DURATION, DEFAULT_SAMPLE_DURATION
),
min_gradient=entry.options.get(CONF_MIN_GRADIENT, DEFAULT_MIN_GRADIENT),
min_samples=entry.options.get(CONF_MIN_SAMPLES, DEFAULT_MIN_SAMPLES),
max_samples=entry.options.get(CONF_MAX_SAMPLES, DEFAULT_MAX_SAMPLES),
unique_id=entry.entry_id,
)
]
)
class SensorTrend(BinarySensorEntity, RestoreEntity):
@ -146,30 +163,33 @@ class SensorTrend(BinarySensorEntity, RestoreEntity):
def __init__(
self,
hass: HomeAssistant,
device_id: str,
friendly_name: str,
name: str,
entity_id: str,
attribute: str,
device_class: BinarySensorDeviceClass,
attribute: str | None,
invert: bool,
max_samples: int,
min_gradient: float,
sample_duration: int,
min_gradient: float,
min_samples: int,
max_samples: int,
unique_id: str | None = None,
device_class: BinarySensorDeviceClass | None = None,
sensor_entity_id: str | None = None,
) -> None:
"""Initialize the sensor."""
self._hass = hass
self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass)
self._attr_name = friendly_name
self._attr_device_class = device_class
self._entity_id = entity_id
self._attribute = attribute
self._invert = invert
self._sample_duration = sample_duration
self._min_gradient = min_gradient
self._min_samples = min_samples
self.samples: deque = deque(maxlen=max_samples)
self.samples: deque = deque(maxlen=int(max_samples))
self._attr_name = name
self._attr_device_class = device_class
self._attr_unique_id = unique_id
if sensor_entity_id:
self.entity_id = sensor_entity_id
@property
def is_on(self) -> bool | None:

View File

@ -0,0 +1,111 @@
"""Config flow for Trend integration."""
from __future__ import annotations
from collections.abc import Mapping
from typing import Any, cast
import voluptuous as vol
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.const import CONF_ATTRIBUTE, CONF_ENTITY_ID, CONF_NAME, UnitOfTime
from homeassistant.helpers import selector
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowFormStep,
)
from .const import (
CONF_INVERT,
CONF_MAX_SAMPLES,
CONF_MIN_GRADIENT,
CONF_MIN_SAMPLES,
CONF_SAMPLE_DURATION,
DEFAULT_MAX_SAMPLES,
DEFAULT_MIN_GRADIENT,
DEFAULT_MIN_SAMPLES,
DEFAULT_SAMPLE_DURATION,
DOMAIN,
)
async def get_base_options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Get base options schema."""
return vol.Schema(
{
vol.Optional(CONF_ATTRIBUTE): selector.AttributeSelector(
selector.AttributeSelectorConfig(
entity_id=handler.options[CONF_ENTITY_ID]
)
),
vol.Optional(CONF_INVERT, default=False): selector.BooleanSelector(),
}
)
async def get_extended_options_schema(handler: SchemaCommonFlowHandler) -> vol.Schema:
"""Get extended options schema."""
return (await get_base_options_schema(handler)).extend(
{
vol.Optional(
CONF_MAX_SAMPLES, default=DEFAULT_MAX_SAMPLES
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=2,
mode=selector.NumberSelectorMode.BOX,
),
),
vol.Optional(
CONF_MIN_SAMPLES, default=DEFAULT_MIN_SAMPLES
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=2,
mode=selector.NumberSelectorMode.BOX,
),
),
vol.Optional(
CONF_MIN_GRADIENT, default=DEFAULT_MIN_GRADIENT
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0,
step="any",
mode=selector.NumberSelectorMode.BOX,
),
),
vol.Optional(
CONF_SAMPLE_DURATION, default=DEFAULT_SAMPLE_DURATION
): selector.NumberSelector(
selector.NumberSelectorConfig(
min=0,
mode=selector.NumberSelectorMode.BOX,
unit_of_measurement=UnitOfTime.SECONDS,
),
),
}
)
CONFIG_SCHEMA = vol.Schema(
{
vol.Required(CONF_NAME): selector.TextSelector(),
vol.Required(CONF_ENTITY_ID): selector.EntitySelector(
selector.EntitySelectorConfig(domain=SENSOR_DOMAIN, multiple=False),
),
}
)
class ConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config or options flow for Trend."""
config_flow = {
"user": SchemaFlowFormStep(schema=CONFIG_SCHEMA, next_step="settings"),
"settings": SchemaFlowFormStep(get_base_options_schema),
}
options_flow = {
"init": SchemaFlowFormStep(get_extended_options_schema),
}
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title."""
return cast(str, options[CONF_NAME])

View File

@ -13,3 +13,8 @@ CONF_MAX_SAMPLES = "max_samples"
CONF_MIN_GRADIENT = "min_gradient"
CONF_SAMPLE_DURATION = "sample_duration"
CONF_MIN_SAMPLES = "min_samples"
DEFAULT_MAX_SAMPLES = 2
DEFAULT_MIN_SAMPLES = 2
DEFAULT_MIN_GRADIENT = 0.0
DEFAULT_SAMPLE_DURATION = 0

View File

@ -2,7 +2,9 @@
"domain": "trend",
"name": "Trend",
"codeowners": ["@jpbede"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/trend",
"integration_type": "helper",
"iot_class": "calculated",
"quality_scale": "internal",
"requirements": ["numpy==1.26.0"]

View File

@ -4,5 +4,43 @@
"name": "[%key:common::action::reload%]",
"description": "Reloads trend sensors from the YAML-configuration."
}
},
"config": {
"step": {
"user": {
"title": "Trend helper",
"description": "The trend helper allows you to create a sensor which show the trend of a numeric state or a state attribute from another entity.",
"data": {
"name": "[%key:common::config_flow::data::name%]",
"entity_id": "Entity that this sensor tracks"
}
},
"settings": {
"data": {
"attribute": "Attribute of entity that this sensor tracks",
"invert": "Invert the result"
}
}
}
},
"options": {
"step": {
"init": {
"data": {
"attribute": "[%key:component::trend::config::step::settings::data::attribute%]",
"invert": "[%key:component::trend::config::step::settings::data::invert%]",
"max_samples": "Maximum number of stored samples",
"min_samples": "Minimum number of stored samples",
"min_gradient": "Minimum rate at which the value must be changing",
"sample_duration": "Duration in seconds to store samples for"
},
"data_description": {
"max_samples": "The maximum number of samples to store. If the number of samples exceeds this value, the oldest samples will be discarded.",
"min_samples": "The minimum number of samples that must be collected before the gradient can be calculated.",
"min_gradient": "The minimum rate at which the observed value must be changing for this sensor to switch on. The gradient is measured in sensor units per second.",
"sample_duration": "The duration in seconds to store samples for. Samples older than this value will be discarded."
}
}
}
}
}

View File

@ -14,6 +14,7 @@ FLOWS = {
"template",
"threshold",
"tod",
"trend",
"utility_meter",
],
"integration": [

View File

@ -6113,12 +6113,6 @@
"config_flow": false,
"iot_class": "cloud_polling"
},
"trend": {
"name": "Trend",
"integration_type": "hub",
"config_flow": false,
"iot_class": "calculated"
},
"tuya": {
"name": "Tuya",
"integration_type": "hub",
@ -6944,6 +6938,12 @@
"config_flow": true,
"iot_class": "calculated"
},
"trend": {
"name": "Trend",
"integration_type": "helper",
"config_flow": true,
"iot_class": "calculated"
},
"utility_meter": {
"integration_type": "helper",
"config_flow": true,

View File

@ -0,0 +1,51 @@
"""Fixtures for the trend component tests."""
from collections.abc import Awaitable, Callable
from typing import Any
import pytest
from homeassistant.components.trend.const import DOMAIN
from homeassistant.core import HomeAssistant
from tests.common import MockConfigEntry
ComponentSetup = Callable[[dict[str, Any]], Awaitable[None]]
@pytest.fixture(name="config_entry")
async def mock_config_entry(hass: HomeAssistant) -> MockConfigEntry:
"""Return a MockConfigEntry for testing."""
return MockConfigEntry(
data={},
domain=DOMAIN,
options={
"name": "My trend",
"entity_id": "sensor.cpu_temp",
"invert": False,
"max_samples": 2.0,
"min_gradient": 0.0,
"sample_duration": 0.0,
},
title="My trend",
)
@pytest.fixture(name="setup_component")
async def mock_setup_component(
hass: HomeAssistant, config_entry: MockConfigEntry
) -> ComponentSetup:
"""Set up the trend component."""
async def _setup_func(component_params: dict[str, Any]) -> None:
config_entry.title = "test_trend_sensor"
config_entry.options = {
**config_entry.options,
**component_params,
"name": "test_trend_sensor",
"entity_id": "sensor.test_state",
}
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
return _setup_func

View File

@ -2,22 +2,22 @@
from datetime import timedelta
import logging
from typing import Any
from unittest.mock import patch
from freezegun.api import FrozenDateTimeFactory
import pytest
from homeassistant import config as hass_config, setup
from homeassistant.components.trend.const import DOMAIN
from homeassistant.const import SERVICE_RELOAD, STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant import setup
from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.setup import async_setup_component
from tests.common import assert_setup_component, get_fixture_path, mock_restore_cache
from .conftest import ComponentSetup
from tests.common import MockConfigEntry, assert_setup_component, mock_restore_cache
async def _setup_component(hass: HomeAssistant, params: dict[str, Any]) -> None:
"""Set up the trend component."""
async def _setup_legacy_component(hass: HomeAssistant, params: dict[str, Any]) -> None:
"""Set up the trend component the legacy way."""
assert await async_setup_component(
hass,
"binary_sensor",
@ -43,17 +43,54 @@ async def _setup_component(hass: HomeAssistant, params: dict[str, Any]) -> None:
],
ids=["up", "down", "up inverted", "down inverted"],
)
async def test_basic_trend(
async def test_basic_trend_setup_from_yaml(
hass: HomeAssistant,
states: list[str],
inverted: bool,
expected_state: str,
):
) -> None:
"""Test trend with a basic setup."""
await _setup_component(
await _setup_legacy_component(
hass,
{
"entity_id": "sensor.test_state",
"friendly_name": "Test state",
"entity_id": "sensor.cpu_temp",
"invert": inverted,
"max_samples": 2.0,
"min_gradient": 0.0,
"sample_duration": 0.0,
},
)
for state in states:
hass.states.async_set("sensor.cpu_temp", state)
await hass.async_block_till_done()
assert (sensor_state := hass.states.get("binary_sensor.test_trend_sensor"))
assert sensor_state.state == expected_state
@pytest.mark.parametrize(
("states", "inverted", "expected_state"),
[
(["1", "2"], False, STATE_ON),
(["2", "1"], False, STATE_OFF),
(["1", "2"], True, STATE_OFF),
(["2", "1"], True, STATE_ON),
],
ids=["up", "down", "up inverted", "down inverted"],
)
async def test_basic_trend(
hass: HomeAssistant,
config_entry: MockConfigEntry,
setup_component: ComponentSetup,
states: list[str],
inverted: bool,
expected_state: str,
) -> None:
"""Test trend with a basic setup."""
await setup_component(
{
"invert": inverted,
},
)
@ -89,16 +126,16 @@ async def test_basic_trend(
)
async def test_using_trendline(
hass: HomeAssistant,
config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
setup_component: ComponentSetup,
state_series: list[list[str]],
inverted: bool,
expected_states: list[str],
):
) -> None:
"""Test uptrend using multiple samples and trendline calculation."""
await _setup_component(
hass,
await setup_component(
{
"entity_id": "sensor.test_state",
"sample_duration": 10000,
"min_gradient": 1,
"max_samples": 25,
@ -127,12 +164,13 @@ async def test_using_trendline(
)
async def test_attribute_trend(
hass: HomeAssistant,
config_entry: MockConfigEntry,
setup_component: ComponentSetup,
attr_values: list[str],
expected_state: str,
):
) -> None:
"""Test attribute uptrend."""
await _setup_component(
hass,
await setup_component(
{
"entity_id": "sensor.test_state",
"attribute": "attr",
@ -147,12 +185,12 @@ async def test_attribute_trend(
assert sensor_state.state == expected_state
async def test_max_samples(hass: HomeAssistant):
async def test_max_samples(
hass: HomeAssistant, config_entry: MockConfigEntry, setup_component: ComponentSetup
) -> None:
"""Test that sample count is limited correctly."""
await _setup_component(
hass,
await setup_component(
{
"entity_id": "sensor.test_state",
"max_samples": 3,
"min_gradient": -1,
},
@ -167,39 +205,39 @@ async def test_max_samples(hass: HomeAssistant):
assert state.attributes["sample_count"] == 3
async def test_non_numeric(hass: HomeAssistant):
async def test_non_numeric(
hass: HomeAssistant, config_entry: MockConfigEntry, setup_component: ComponentSetup
) -> None:
"""Test for non-numeric sensor."""
await _setup_component(hass, {"entity_id": "sensor.test_state"})
await setup_component({"entity_id": "sensor.test_state"})
hass.states.async_set("sensor.test_state", "Non")
await hass.async_block_till_done()
hass.states.async_set("sensor.test_state", "Numeric")
await hass.async_block_till_done()
for val in ["Non", "Numeric"]:
hass.states.async_set("sensor.test_state", val)
await hass.async_block_till_done()
assert (state := hass.states.get("binary_sensor.test_trend_sensor"))
assert state.state == STATE_UNKNOWN
async def test_missing_attribute(hass: HomeAssistant):
async def test_missing_attribute(
hass: HomeAssistant, config_entry: MockConfigEntry, setup_component: ComponentSetup
) -> None:
"""Test for missing attribute."""
await _setup_component(
hass,
await setup_component(
{
"entity_id": "sensor.test_state",
"attribute": "missing",
},
)
hass.states.async_set("sensor.test_state", "State", {"attr": "2"})
await hass.async_block_till_done()
hass.states.async_set("sensor.test_state", "State", {"attr": "1"})
await hass.async_block_till_done()
for val in [1, 2]:
hass.states.async_set("sensor.test_state", "State", {"attr": val})
await hass.async_block_till_done()
assert (state := hass.states.get("binary_sensor.test_trend_sensor"))
assert state.state == STATE_UNKNOWN
async def test_invalid_name_does_not_create(hass: HomeAssistant):
async def test_invalid_name_does_not_create(hass: HomeAssistant) -> None:
"""Test for invalid name."""
with assert_setup_component(0):
assert await setup.async_setup_component(
@ -217,7 +255,7 @@ async def test_invalid_name_does_not_create(hass: HomeAssistant):
assert hass.states.async_all("binary_sensor") == []
async def test_invalid_sensor_does_not_create(hass: HomeAssistant):
async def test_invalid_sensor_does_not_create(hass: HomeAssistant) -> None:
"""Test invalid sensor."""
with assert_setup_component(0):
assert await setup.async_setup_component(
@ -235,7 +273,7 @@ async def test_invalid_sensor_does_not_create(hass: HomeAssistant):
assert hass.states.async_all("binary_sensor") == []
async def test_no_sensors_does_not_create(hass: HomeAssistant):
async def test_no_sensors_does_not_create(hass: HomeAssistant) -> None:
"""Test no sensors."""
with assert_setup_component(0):
assert await setup.async_setup_component(
@ -244,59 +282,23 @@ async def test_no_sensors_does_not_create(hass: HomeAssistant):
assert hass.states.async_all("binary_sensor") == []
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload trend sensors."""
hass.states.async_set("sensor.test_state", 1234)
await setup.async_setup_component(
hass,
"binary_sensor",
{
"binary_sensor": {
"platform": "trend",
"sensors": {"test_trend_sensor": {"entity_id": "sensor.test_state"}},
}
},
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
assert hass.states.get("binary_sensor.test_trend_sensor")
yaml_path = get_fixture_path("configuration.yaml", "trend")
with patch.object(hass_config, "YAML_CONFIG_FILE", yaml_path):
await hass.services.async_call(
DOMAIN,
SERVICE_RELOAD,
{},
blocking=True,
)
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 2
assert hass.states.get("binary_sensor.test_trend_sensor") is None
assert hass.states.get("binary_sensor.second_test_trend_sensor")
@pytest.mark.parametrize(
("saved_state", "restored_state"),
[("on", "on"), ("off", "off"), ("unknown", "unknown")],
)
async def test_restore_state(
hass: HomeAssistant,
config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
setup_component: ComponentSetup,
saved_state: str,
restored_state: str,
) -> None:
"""Test we restore the trend state."""
mock_restore_cache(hass, (State("binary_sensor.test_trend_sensor", saved_state),))
await _setup_component(
hass,
await setup_component(
{
"entity_id": "sensor.test_state",
"sample_duration": 10000,
"min_gradient": 1,
"max_samples": 25,
@ -332,7 +334,7 @@ async def test_invalid_min_sample(
) -> None:
"""Test if error is logged when min_sample is larger than max_samples."""
with caplog.at_level(logging.ERROR):
await _setup_component(
await _setup_legacy_component(
hass,
{
"entity_id": "sensor.test_state",

View File

@ -0,0 +1,80 @@
"""Test the Trend config flow."""
from __future__ import annotations
from unittest.mock import patch
from homeassistant import config_entries
from homeassistant.components.trend import async_setup_entry
from homeassistant.components.trend.const import DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from tests.common import MockConfigEntry
async def test_form(hass: HomeAssistant) -> None:
"""Test we get the form."""
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": config_entries.SOURCE_USER}
)
assert result["step_id"] == "user"
assert result["type"] == FlowResultType.FORM
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{"name": "CPU Temperature rising", "entity_id": "sensor.cpu_temp"},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.FORM
# test step 2 of config flow: settings of trend sensor
with patch(
"homeassistant.components.trend.async_setup_entry", wraps=async_setup_entry
):
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{
"invert": False,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["title"] == "CPU Temperature rising"
assert result["data"] == {}
assert result["options"] == {
"entity_id": "sensor.cpu_temp",
"invert": False,
"name": "CPU Temperature rising",
}
async def test_options(hass: HomeAssistant, config_entry: MockConfigEntry) -> None:
"""Test options flow."""
config_entry.add_to_hass(hass)
result = await hass.config_entries.options.async_init(config_entry.entry_id)
assert result["type"] == FlowResultType.FORM
assert result["step_id"] == "init"
result = await hass.config_entries.options.async_configure(
result["flow_id"],
{
"min_samples": 30,
"max_samples": 50,
},
)
await hass.async_block_till_done()
assert result["type"] == FlowResultType.CREATE_ENTRY
assert result["data"] == {
"min_samples": 30,
"max_samples": 50,
"entity_id": "sensor.cpu_temp",
"invert": False,
"min_gradient": 0.0,
"name": "My trend",
"sample_duration": 0.0,
}

View File

@ -0,0 +1,50 @@
"""Test the Trend integration."""
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from tests.common import MockConfigEntry
from tests.components.trend.conftest import ComponentSetup
async def test_setup_and_remove_config_entry(
hass: HomeAssistant, config_entry: MockConfigEntry
) -> None:
"""Test setting up and removing a config entry."""
registry = er.async_get(hass)
trend_entity_id = "binary_sensor.my_trend"
# Set up the config entry
config_entry.add_to_hass(hass)
assert await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
# Check the entity is registered in the entity registry
assert registry.async_get(trend_entity_id) is not None
# Remove the config entry
assert await hass.config_entries.async_remove(config_entry.entry_id)
await hass.async_block_till_done()
# Check the state and entity registry entry are removed
assert hass.states.get(trend_entity_id) is None
assert registry.async_get(trend_entity_id) is None
async def test_reload_config_entry(
hass: HomeAssistant,
config_entry: MockConfigEntry,
setup_component: ComponentSetup,
) -> None:
"""Test config entry reload."""
await setup_component({})
assert config_entry.state is ConfigEntryState.LOADED
assert hass.config_entries.async_update_entry(
config_entry, data={**config_entry.data, "max_samples": 4.0}
)
assert config_entry.state is ConfigEntryState.LOADED
assert config_entry.data == {**config_entry.data, "max_samples": 4.0}