mirror of
https://github.com/home-assistant/core.git
synced 2026-05-23 09:15:45 +02:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| aad7b6c0e7 | |||
| 9634953f69 | |||
| 30176c6f68 | |||
| 7c6ff3f172 | |||
| fbda661516 | |||
| 2e1c4e0ef5 | |||
| ee9326dc60 | |||
| d8b23dfaf1 | |||
| f4bccbb348 | |||
| 80733a2c45 | |||
| 5079709d87 | |||
| e131b21c5e | |||
| 9fd0f99a44 | |||
| 00e8a6cae6 | |||
| 3f690141a5 | |||
| 379c0b1b13 | |||
| 46a05e401b | |||
| 787929fbd1 | |||
| 983df39785 | |||
| d037f33023 | |||
| 6d4f5a4a91 | |||
| 7f1846b34a | |||
| 44b071762d | |||
| f006283ee8 | |||
| d25f3c6d2e | |||
| f821952918 | |||
| 49ce8ed944 | |||
| d4fca3737d | |||
| 26d8dfb695 | |||
| 6e92ba2fc0 | |||
| 7288d19abf | |||
| 5bbfe69bbb | |||
| 564280cc65 |
@@ -47,7 +47,9 @@ from .const import ( # noqa: F401
|
||||
ATTR_OBJECT_ID,
|
||||
ATTR_ORDER,
|
||||
ATTR_REMOVE_ENTITIES,
|
||||
CONF_GROUP_TYPE,
|
||||
CONF_HIDE_MEMBERS,
|
||||
CONF_IGNORE_NON_NUMERIC,
|
||||
DATA_COMPONENT,
|
||||
DOMAIN,
|
||||
GROUP_ORDER,
|
||||
|
||||
@@ -24,7 +24,7 @@ from homeassistant.helpers.schema_config_entry_flow import (
|
||||
|
||||
from .binary_sensor import CONF_ALL, async_create_preview_binary_sensor
|
||||
from .button import async_create_preview_button
|
||||
from .const import CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN
|
||||
from .const import CONF_GROUP_TYPE, CONF_HIDE_MEMBERS, CONF_IGNORE_NON_NUMERIC, DOMAIN
|
||||
from .cover import async_create_preview_cover
|
||||
from .entity import GroupEntity
|
||||
from .event import async_create_preview_event
|
||||
@@ -180,7 +180,7 @@ GROUP_TYPES = [
|
||||
|
||||
async def choose_options_step(options: dict[str, Any]) -> str:
|
||||
"""Return next step_id for options flow according to group_type."""
|
||||
return cast(str, options["group_type"])
|
||||
return cast(str, options[CONF_GROUP_TYPE])
|
||||
|
||||
|
||||
def set_group_type(
|
||||
@@ -194,7 +194,7 @@ def set_group_type(
|
||||
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
|
||||
) -> dict[str, Any]:
|
||||
"""Add group type to user input."""
|
||||
return {"group_type": group_type, **user_input}
|
||||
return {CONF_GROUP_TYPE: group_type, **user_input}
|
||||
|
||||
return _set_group_type
|
||||
|
||||
@@ -430,7 +430,7 @@ def ws_start_preview(
|
||||
config_entry = hass.config_entries.async_get_entry(config_entry_id)
|
||||
if not config_entry:
|
||||
raise HomeAssistantError
|
||||
group_type = config_entry.options["group_type"]
|
||||
group_type = config_entry.options[CONF_GROUP_TYPE]
|
||||
name = config_entry.options["name"]
|
||||
validated = PREVIEW_OPTIONS_SCHEMA[group_type](msg["user_input"])
|
||||
entity_registry = er.async_get(hass)
|
||||
|
||||
@@ -14,6 +14,7 @@ if TYPE_CHECKING:
|
||||
|
||||
CONF_HIDE_MEMBERS = "hide_members"
|
||||
CONF_IGNORE_NON_NUMERIC = "ignore_non_numeric"
|
||||
CONF_GROUP_TYPE = "group_type"
|
||||
|
||||
DOMAIN = "group"
|
||||
DATA_COMPONENT: HassKey[EntityComponent[Group]] = HassKey(DOMAIN)
|
||||
|
||||
@@ -3,12 +3,30 @@
|
||||
from homeassistant.config_entries import ConfigEntry
|
||||
from homeassistant.const import Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.issue_registry import (
|
||||
IssueSeverity,
|
||||
async_create_issue,
|
||||
async_delete_issue,
|
||||
)
|
||||
|
||||
from .const import DOMAIN
|
||||
|
||||
PLATFORMS = [Platform.SENSOR]
|
||||
|
||||
|
||||
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Set up Min/Max from a config entry."""
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
f"migrate_to_group_sensor-{entry.entry_id}",
|
||||
breaks_in_ha_version="2026.12.0",
|
||||
is_fixable=True,
|
||||
is_persistent=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
translation_key="migrate_to_group_sensor",
|
||||
data={"entry_id": entry.entry_id},
|
||||
)
|
||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||
|
||||
return True
|
||||
@@ -17,3 +35,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||
"""Unload a config entry."""
|
||||
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
|
||||
|
||||
|
||||
async def async_remove_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
||||
"""Remove a config entry."""
|
||||
async_delete_issue(hass, DOMAIN, f"migrate_to_group_sensor-{entry.entry_id}")
|
||||
|
||||
@@ -11,8 +11,10 @@ from homeassistant.components.input_number import DOMAIN as INPUT_NUMBER_DOMAIN
|
||||
from homeassistant.components.number import DOMAIN as NUMBER_DOMAIN
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.const import CONF_TYPE
|
||||
from homeassistant.data_entry_flow import AbortFlow
|
||||
from homeassistant.helpers import selector
|
||||
from homeassistant.helpers.schema_config_entry_flow import (
|
||||
SchemaCommonFlowHandler,
|
||||
SchemaConfigFlowHandler,
|
||||
SchemaFlowFormStep,
|
||||
)
|
||||
@@ -51,14 +53,16 @@ OPTIONS_SCHEMA = vol.Schema(
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema(
|
||||
{
|
||||
vol.Required("name"): selector.TextSelector(),
|
||||
}
|
||||
).extend(OPTIONS_SCHEMA.schema)
|
||||
CONFIG_SCHEMA = vol.Schema({})
|
||||
|
||||
|
||||
async def migrate_to_groups(handler: SchemaCommonFlowHandler) -> vol.Schema:
|
||||
"""Abort flow as migrate to groups."""
|
||||
raise AbortFlow("migrated_to_groups")
|
||||
|
||||
|
||||
CONFIG_FLOW = {
|
||||
"user": SchemaFlowFormStep(CONFIG_SCHEMA),
|
||||
"user": SchemaFlowFormStep(migrate_to_groups),
|
||||
}
|
||||
|
||||
OPTIONS_FLOW = {
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
"""Repairs platform for the Min/Max integration."""
|
||||
|
||||
from types import MappingProxyType
|
||||
from typing import TYPE_CHECKING, Any, cast
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import data_entry_flow
|
||||
from homeassistant.components.group import (
|
||||
CONF_ENTITIES,
|
||||
CONF_GROUP_TYPE,
|
||||
CONF_HIDE_MEMBERS,
|
||||
CONF_IGNORE_NON_NUMERIC,
|
||||
DOMAIN as GROUP_DOMAIN,
|
||||
)
|
||||
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
|
||||
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
||||
from homeassistant.config_entries import SOURCE_USER, ConfigEntry, ConfigEntryDisabler
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
|
||||
|
||||
|
||||
class MigrateToGroupSensorFlow(RepairsFlow):
|
||||
"""Repair flow to migrate Min/Max helper to Group sensor."""
|
||||
|
||||
def __init__(self, entry: ConfigEntry) -> None:
|
||||
"""Create flow."""
|
||||
self.entry = entry
|
||||
super().__init__()
|
||||
|
||||
async def async_step_init(
|
||||
self, user_input: dict[str, str] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the first step of a fix flow."""
|
||||
return await self.async_step_migrate()
|
||||
|
||||
async def async_step_migrate(
|
||||
self, user_input: dict[str, Any] | None = None
|
||||
) -> data_entry_flow.FlowResult:
|
||||
"""Handle the migration step of a fix flow."""
|
||||
entity_reg = er.async_get(self.hass)
|
||||
old_entity = entity_reg.async_get_entity_id(
|
||||
SENSOR_DOMAIN, DOMAIN, self.entry.entry_id
|
||||
)
|
||||
if not old_entity:
|
||||
return self.async_abort(reason="entity_not_found")
|
||||
|
||||
if user_input is not None:
|
||||
config = dict(self.entry.options)
|
||||
config[CONF_ENTITIES] = config.pop(CONF_ENTITY_IDS)
|
||||
config.pop(CONF_ROUND_DIGITS)
|
||||
# Set group sensor defaults
|
||||
config[CONF_HIDE_MEMBERS] = False
|
||||
config[CONF_IGNORE_NON_NUMERIC] = False
|
||||
config[CONF_GROUP_TYPE] = SENSOR_DOMAIN
|
||||
|
||||
new_config_entry = ConfigEntry(
|
||||
data={},
|
||||
discovery_keys=MappingProxyType({}),
|
||||
domain=GROUP_DOMAIN,
|
||||
minor_version=1,
|
||||
options=config,
|
||||
source=SOURCE_USER,
|
||||
subentries_data=[],
|
||||
title=self.entry.title,
|
||||
unique_id=None,
|
||||
version=1,
|
||||
disabled_by=ConfigEntryDisabler.USER,
|
||||
)
|
||||
|
||||
if not await self.hass.config_entries.async_unload(self.entry.entry_id):
|
||||
return self.async_abort(reason="unload_failed")
|
||||
await self.hass.config_entries.async_add(new_config_entry)
|
||||
try:
|
||||
entity_reg.async_update_entity_platform(
|
||||
old_entity,
|
||||
GROUP_DOMAIN,
|
||||
new_config_entry_id=new_config_entry.entry_id,
|
||||
new_unique_id=new_config_entry.entry_id,
|
||||
)
|
||||
except ValueError:
|
||||
return self.async_abort(reason="entity_update_failed")
|
||||
await self.hass.config_entries.async_set_disabled_by(
|
||||
entry_id=new_config_entry.entry_id, disabled_by=None
|
||||
)
|
||||
await self.hass.config_entries.async_remove(self.entry.entry_id)
|
||||
|
||||
return self.async_create_entry(data={})
|
||||
|
||||
entity_info = entity_reg.async_get(old_entity)
|
||||
if TYPE_CHECKING:
|
||||
assert entity_info
|
||||
title = er.async_get_full_entity_name(self.hass, entity_info)
|
||||
|
||||
return self.async_show_form(
|
||||
step_id="migrate",
|
||||
data_schema=vol.Schema({}),
|
||||
description_placeholders={"title": title},
|
||||
)
|
||||
|
||||
|
||||
async def async_create_fix_flow(
|
||||
hass: HomeAssistant,
|
||||
issue_id: str,
|
||||
data: dict[str, Any] | None,
|
||||
) -> RepairsFlow:
|
||||
"""Create flow."""
|
||||
if data and (entry_id := data.get("entry_id")):
|
||||
entry_id = cast(str, entry_id)
|
||||
entry = hass.config_entries.async_get_entry(entry_id)
|
||||
if TYPE_CHECKING:
|
||||
assert entry
|
||||
return MigrateToGroupSensorFlow(entry)
|
||||
|
||||
return ConfirmRepairFlow()
|
||||
@@ -3,12 +3,15 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import logging
|
||||
import statistics
|
||||
from typing import Any
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.group import CONF_ENTITIES
|
||||
from homeassistant.components.sensor import (
|
||||
PLATFORM_SCHEMA as SENSOR_PLATFORM_SCHEMA,
|
||||
SensorDeviceClass,
|
||||
@@ -20,6 +23,7 @@ from homeassistant.const import (
|
||||
ATTR_ENTITY_ID,
|
||||
ATTR_UNIT_OF_MEASUREMENT,
|
||||
CONF_NAME,
|
||||
CONF_PLATFORM,
|
||||
CONF_TYPE,
|
||||
CONF_UNIQUE_ID,
|
||||
STATE_UNAVAILABLE,
|
||||
@@ -34,8 +38,10 @@ from homeassistant.helpers.entity_platform import (
|
||||
AddEntitiesCallback,
|
||||
)
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
|
||||
from homeassistant.helpers.reload import async_setup_reload_service
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType, StateType
|
||||
from homeassistant.util import yaml as yaml_util
|
||||
|
||||
from . import PLATFORMS
|
||||
from .const import CONF_ENTITY_IDS, CONF_ROUND_DIGITS, DOMAIN
|
||||
@@ -53,6 +59,7 @@ ATTR_LAST_ENTITY_ID = "last_entity_id"
|
||||
ATTR_RANGE = "range"
|
||||
ATTR_SUM = "sum"
|
||||
|
||||
|
||||
ICON = "mdi:calculator"
|
||||
|
||||
SENSOR_TYPES = {
|
||||
@@ -105,6 +112,36 @@ async def async_setup_entry(
|
||||
)
|
||||
|
||||
|
||||
async def yaml_deprecation_notice(hass: HomeAssistant, config: ConfigType) -> None:
|
||||
"""Raise repair issue for YAML configuration deprecation."""
|
||||
platform_config = config.copy()
|
||||
platform_config[CONF_ENTITIES] = platform_config.pop(CONF_ENTITY_IDS)
|
||||
platform_config.pop(CONF_ROUND_DIGITS)
|
||||
platform_config.pop(CONF_PLATFORM)
|
||||
if CONF_NAME not in platform_config:
|
||||
platform_config[CONF_NAME] = f"{platform_config[CONF_TYPE]} sensor".capitalize()
|
||||
yaml_config = yaml_util.dump(platform_config)
|
||||
yaml_config = yaml_config.replace("\n", "\n ")
|
||||
yaml_config = "```yaml\nsensor:\n - platform: group\n " + yaml_config + "\n```"
|
||||
|
||||
def make_hash(config: dict[str, Any]) -> str:
|
||||
d = hashlib.sha1(json.dumps(config, sort_keys=True).encode())
|
||||
return d.hexdigest()
|
||||
|
||||
issue_id = f"yaml_deprecated-{make_hash(platform_config)}"
|
||||
async_create_issue(
|
||||
hass,
|
||||
DOMAIN,
|
||||
issue_id,
|
||||
breaks_in_ha_version="2026.12.0",
|
||||
is_fixable=False,
|
||||
severity=IssueSeverity.WARNING,
|
||||
learn_more_url="https://www.home-assistant.io/integrations/group/",
|
||||
translation_key="yaml_deprecated",
|
||||
translation_placeholders={"yaml_config": yaml_config},
|
||||
)
|
||||
|
||||
|
||||
async def async_setup_platform(
|
||||
hass: HomeAssistant,
|
||||
config: ConfigType,
|
||||
@@ -119,6 +156,7 @@ async def async_setup_platform(
|
||||
unique_id = config.get(CONF_UNIQUE_ID)
|
||||
|
||||
await async_setup_reload_service(hass, DOMAIN, PLATFORMS)
|
||||
await yaml_deprecation_notice(hass, config)
|
||||
|
||||
async_add_entities(
|
||||
[MinMaxSensor(entity_ids, name, sensor_type, round_digits, unique_id)]
|
||||
|
||||
@@ -1,31 +1,47 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"migrated_to_groups": "The Min/Max helper has been migrated to use Group sensors. Please use the Group helper instead."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"entity_ids": "Input entities",
|
||||
"name": "[%key:common::config_flow::data::name%]",
|
||||
"round_digits": "Precision",
|
||||
"type": "Statistic characteristic"
|
||||
},
|
||||
"data_description": {
|
||||
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
|
||||
},
|
||||
"description": "Create a sensor that calculates a min, max, mean, median or sum from a list of input sensors.",
|
||||
"description": "Min/Max helper has been deprecated, please use the Group helper instead.",
|
||||
"title": "[%key:component::min_max::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"issues": {
|
||||
"migrate_to_group_sensor": {
|
||||
"fix_flow": {
|
||||
"abort": {
|
||||
"entity_not_found": "Entity could not be found as it has been removed, aborting the repair.",
|
||||
"entity_update_failed": "Failed to update the entity to a Group sensor, please manually remove the obsolete entity.",
|
||||
"unload_failed": "Failed to unload the Min/Max helper, please restart repairing the issue."
|
||||
},
|
||||
"step": {
|
||||
"migrate": {
|
||||
"description": "The Min/Max helper has been deprecated and {title} will be migrated to a Group sensor when you click submit to fix this repair.",
|
||||
"title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Min/Max helper has been deprecated"
|
||||
},
|
||||
"yaml_deprecated": {
|
||||
"description": "The Min/Max helper has been deprecated and you should use Group sensors instead.\n\nReplace your Min/Max YAML configuration with this converted configuration:\n{yaml_config}\n\n**Note:**\n- `precision` is not supported in Group sensor, you need to update your entity options after migrating.\n- Group sensor supports setting `ignore_non_numeric` which defaults to `False` after migrating, change this to `True` to ignore input entities with invalid values.\n\nOnce you have replaced your YAML configuration, restart Home Assistant to use the Group helper instead.\n\nThe Group helper has more configuration possibilities. Refer to the documentation by clicking Learn More.",
|
||||
"title": "[%key:component::min_max::issues::migrate_to_group_sensor::title%]"
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"entity_ids": "[%key:component::min_max::config::step::user::data::entity_ids%]",
|
||||
"round_digits": "[%key:component::min_max::config::step::user::data::round_digits%]",
|
||||
"type": "[%key:component::min_max::config::step::user::data::type%]"
|
||||
"entity_ids": "Input entities",
|
||||
"round_digits": "Precision",
|
||||
"type": "Statistic characteristic"
|
||||
},
|
||||
"data_description": {
|
||||
"round_digits": "[%key:component::min_max::config::step::user::data_description::round_digits%]"
|
||||
"round_digits": "Controls the number of decimal digits in the output when the statistics characteristic is mean, median or sum."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1932,7 +1932,7 @@ class EntityRegistry(BaseRegistry):
|
||||
"""
|
||||
if (
|
||||
state := self.hass.states.get(entity_id)
|
||||
) is not None and state.state != STATE_UNKNOWN:
|
||||
) is not None and state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE):
|
||||
raise ValueError("Only entities that haven't been loaded can be migrated")
|
||||
|
||||
old = self.entities[entity_id]
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
# serializer version: 1
|
||||
# name: test_setup_migrates_to_groups
|
||||
dict({
|
||||
'data': dict({
|
||||
}),
|
||||
'disabled_by': None,
|
||||
'discovery_keys': dict({
|
||||
}),
|
||||
'domain': 'group',
|
||||
'minor_version': 1,
|
||||
'options': dict({
|
||||
'entities': list([
|
||||
'sensor.input_one',
|
||||
'sensor.input_two',
|
||||
]),
|
||||
'group_type': 'sensor',
|
||||
'hide_members': False,
|
||||
'ignore_non_numeric': False,
|
||||
'name': 'My min_max',
|
||||
'type': 'max',
|
||||
}),
|
||||
'pref_disable_new_entities': False,
|
||||
'pref_disable_polling': False,
|
||||
'source': 'user',
|
||||
'subentries': list([
|
||||
]),
|
||||
'title': 'My min_max',
|
||||
'unique_id': None,
|
||||
'version': 1,
|
||||
})
|
||||
# ---
|
||||
@@ -1,7 +1,5 @@
|
||||
"""Test the Min/Max config flow."""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant import config_entries
|
||||
@@ -12,47 +10,14 @@ from homeassistant.data_entry_flow import FlowResultType
|
||||
from tests.common import MockConfigEntry, get_schema_suggested_value
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ["sensor"])
|
||||
async def test_config_flow(hass: HomeAssistant, platform: str) -> None:
|
||||
"""Test the config flow."""
|
||||
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||
async def test_config_flow_aborts(hass: HomeAssistant) -> None:
|
||||
"""Test the config flow aborts."""
|
||||
|
||||
result = await hass.config_entries.flow.async_init(
|
||||
DOMAIN, context={"source": config_entries.SOURCE_USER}
|
||||
)
|
||||
assert result["type"] is FlowResultType.FORM
|
||||
assert result["errors"] is None
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.min_max.async_setup_entry",
|
||||
return_value=True,
|
||||
) as mock_setup_entry:
|
||||
result = await hass.config_entries.flow.async_configure(
|
||||
result["flow_id"],
|
||||
{"name": "My min_max", "entity_ids": input_sensors, "type": "max"},
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert result["type"] is FlowResultType.CREATE_ENTRY
|
||||
assert result["title"] == "My min_max"
|
||||
assert result["data"] == {}
|
||||
assert result["options"] == {
|
||||
"entity_ids": input_sensors,
|
||||
"name": "My min_max",
|
||||
"round_digits": 2.0,
|
||||
"type": "max",
|
||||
}
|
||||
assert len(mock_setup_entry.mock_calls) == 1
|
||||
|
||||
config_entry = hass.config_entries.async_entries(DOMAIN)[0]
|
||||
assert config_entry.data == {}
|
||||
assert config_entry.options == {
|
||||
"entity_ids": input_sensors,
|
||||
"name": "My min_max",
|
||||
"round_digits": 2.0,
|
||||
"type": "max",
|
||||
}
|
||||
assert config_entry.title == "My min_max"
|
||||
assert result["type"] is FlowResultType.ABORT
|
||||
assert result["reason"] == "migrated_to_groups"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ["sensor"])
|
||||
|
||||
@@ -1,32 +1,44 @@
|
||||
"""Test the Min/Max integration."""
|
||||
|
||||
import pytest
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
from syrupy.filters import props
|
||||
|
||||
from homeassistant.components.group import DOMAIN as GROUP_DOMAIN
|
||||
from homeassistant.components.min_max.const import DOMAIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||
from tests.components.repairs import process_repair_fix_flow, start_repair_fix_flow
|
||||
from tests.typing import ClientSessionGenerator, WebSocketGenerator
|
||||
|
||||
|
||||
@pytest.mark.parametrize("platform", ["sensor"])
|
||||
async def test_setup_and_remove_config_entry(
|
||||
async def test_setup_migrates_to_groups(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
platform: str,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test setting up and removing a config entry."""
|
||||
"""Test migrating to group sensors."""
|
||||
assert await async_setup_component(hass, "repairs", {})
|
||||
hass.states.async_set("sensor.input_one", "10")
|
||||
hass.states.async_set("sensor.input_two", "20")
|
||||
|
||||
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||
|
||||
min_max_entity_id = f"{platform}.my_min_max"
|
||||
min_max_entity_id = "sensor.my_min_max"
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
entry_id="123",
|
||||
options={
|
||||
"entity_ids": input_sensors,
|
||||
"name": "My min_max",
|
||||
@@ -40,16 +52,108 @@ async def test_setup_and_remove_config_entry(
|
||||
await hass.async_block_till_done()
|
||||
|
||||
# Check the entity is registered in the entity registry
|
||||
assert entity_registry.async_get(min_max_entity_id) is not None
|
||||
entity = entity_registry.async_get(min_max_entity_id)
|
||||
assert entity is not None
|
||||
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"migrate_to_group_sensor-{config_entry.entry_id}"
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.is_fixable is True
|
||||
assert issue.breaks_in_ha_version == "2026.12.0"
|
||||
|
||||
ws_client = await hass_ws_client(hass)
|
||||
client = await hass_client()
|
||||
await ws_client.send_json({"id": 1, "type": "repairs/list_issues"})
|
||||
msg = await ws_client.receive_json()
|
||||
|
||||
assert msg["success"]
|
||||
|
||||
data = await start_repair_fix_flow(
|
||||
client, DOMAIN, f"migrate_to_group_sensor-{config_entry.entry_id}"
|
||||
)
|
||||
flow_id = data["flow_id"]
|
||||
assert data["description_placeholders"] == {"title": "My min_max"}
|
||||
assert data["step_id"] == "migrate"
|
||||
|
||||
data = await process_repair_fix_flow(client, flow_id, json={})
|
||||
|
||||
assert data["type"] == FlowResultType.CREATE_ENTRY
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = entity_registry.async_get(min_max_entity_id)
|
||||
assert entity.config_entry_id is not None
|
||||
assert entity.config_entry_id != config_entry.entry_id
|
||||
assert entity.unique_id != config_entry.entry_id
|
||||
assert entity.platform == GROUP_DOMAIN
|
||||
|
||||
# Check the platform is setup correctly
|
||||
assert len(hass.states.async_all()) == 3
|
||||
state = hass.states.get(min_max_entity_id)
|
||||
assert state.state == "20.0"
|
||||
|
||||
# Remove the config entry
|
||||
# Assert min/max config entry is removed
|
||||
min_max_config_entries = hass.config_entries.async_entries(DOMAIN)
|
||||
assert len(min_max_config_entries) == 0
|
||||
|
||||
config_entry = hass.config_entries.async_entries("group")[0]
|
||||
assert config_entry.as_dict() == snapshot(
|
||||
exclude=props("created_at", "entry_id", "modified_at")
|
||||
)
|
||||
|
||||
hass.states.async_set("sensor.input_two", "30")
|
||||
|
||||
freezer.tick(60 * 5)
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
state = hass.states.get(min_max_entity_id)
|
||||
assert state.state == "30.0"
|
||||
|
||||
|
||||
async def test_issue_is_deleted_on_removal(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
issue_registry: ir.IssueRegistry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
snapshot: SnapshotAssertion,
|
||||
hass_client: ClientSessionGenerator,
|
||||
hass_ws_client: WebSocketGenerator,
|
||||
) -> None:
|
||||
"""Test issue is removed on config entry removal."""
|
||||
assert await async_setup_component(hass, "repairs", {})
|
||||
hass.states.async_set("sensor.input_one", "10")
|
||||
hass.states.async_set("sensor.input_two", "20")
|
||||
|
||||
input_sensors = ["sensor.input_one", "sensor.input_two"]
|
||||
|
||||
# Setup the config entry
|
||||
config_entry = MockConfigEntry(
|
||||
data={},
|
||||
domain=DOMAIN,
|
||||
entry_id="123",
|
||||
options={
|
||||
"entity_ids": input_sensors,
|
||||
"name": "My min_max",
|
||||
"round_digits": 2.0,
|
||||
"type": "max",
|
||||
},
|
||||
title="My min_max",
|
||||
)
|
||||
config_entry.add_to_hass(hass)
|
||||
assert await hass.config_entries.async_setup(config_entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"migrate_to_group_sensor-{config_entry.entry_id}"
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.is_fixable is True
|
||||
assert issue.breaks_in_ha_version == "2026.12.0"
|
||||
|
||||
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(min_max_entity_id) is None
|
||||
assert entity_registry.async_get(min_max_entity_id) is None
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, f"migrate_to_group_sensor-{config_entry.entry_id}"
|
||||
)
|
||||
assert issue is None
|
||||
|
||||
@@ -23,7 +23,7 @@ from homeassistant.const import (
|
||||
UnitOfTemperature,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
from homeassistant.helpers import entity_registry as er, issue_registry as ir
|
||||
from homeassistant.setup import async_setup_component
|
||||
|
||||
from tests.common import get_fixture_path
|
||||
@@ -42,6 +42,44 @@ RANGE_4_DIGITS = round(max(VALUES) - min(VALUES), 4)
|
||||
SUM_VALUE = sum(VALUES)
|
||||
|
||||
|
||||
async def test_deprecation_warning(
|
||||
hass: HomeAssistant, issue_registry: ir.IssueRegistry
|
||||
) -> None:
|
||||
"""Test deprecation issue."""
|
||||
config = {
|
||||
"sensor": [
|
||||
{
|
||||
"platform": "min_max",
|
||||
"type": "min",
|
||||
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||
},
|
||||
{
|
||||
"platform": "min_max",
|
||||
"type": "min",
|
||||
"entity_ids": ["sensor.test_1", "sensor.test_2", "sensor.test_3"],
|
||||
"unique_id": "my_unique_id",
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
assert await async_setup_component(hass, "sensor", config)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
issue = issue_registry.async_get_issue(
|
||||
DOMAIN, "yaml_deprecated-ddc87b71acd58a195502396b87387d910c36ff7c"
|
||||
)
|
||||
issue2 = issue_registry.async_get_issue(
|
||||
DOMAIN, "yaml_deprecated-6e9186f09cfb0959d0fe420ef3b01e1b25899b2f"
|
||||
)
|
||||
assert issue is not None
|
||||
assert issue.severity == ir.IssueSeverity.WARNING
|
||||
assert issue.translation_key == "yaml_deprecated"
|
||||
|
||||
assert issue2 is not None
|
||||
assert issue2.severity == ir.IssueSeverity.WARNING
|
||||
assert issue2.translation_key == "yaml_deprecated"
|
||||
|
||||
|
||||
async def test_default_name_sensor(hass: HomeAssistant) -> None:
|
||||
"""Test the min sensor with a default name."""
|
||||
config = {
|
||||
|
||||
Reference in New Issue
Block a user