Tweak energy validator (#58018)
* Tweak energy validator * Update code and tests * Tweak implementation * Update tests * Update after rebase
This commit is contained in:
parent
d67c1118dc
commit
547e36ae94
3 changed files with 311 additions and 74 deletions
|
@ -23,7 +23,13 @@ from homeassistant.const import (
|
|||
ENERGY_WATT_HOUR,
|
||||
VOLUME_CUBIC_METERS,
|
||||
)
|
||||
from homeassistant.core import HomeAssistant, State, callback, split_entity_id
|
||||
from homeassistant.core import (
|
||||
HomeAssistant,
|
||||
State,
|
||||
callback,
|
||||
split_entity_id,
|
||||
valid_entity_id,
|
||||
)
|
||||
from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
||||
from homeassistant.helpers.event import async_track_state_change_event
|
||||
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
|
||||
|
@ -177,9 +183,13 @@ class SensorManager:
|
|||
|
||||
# Make sure the right data is there
|
||||
# If the entity existed, we don't pop it from to_remove so it's removed
|
||||
if config.get(adapter.entity_energy_key) is None or (
|
||||
if (
|
||||
config.get(adapter.entity_energy_key) is None
|
||||
or not valid_entity_id(config[adapter.entity_energy_key])
|
||||
or (
|
||||
config.get("entity_energy_price") is None
|
||||
and config.get("number_energy_price") is None
|
||||
)
|
||||
):
|
||||
return
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
|
||||
from collections.abc import Mapping, Sequence
|
||||
import dataclasses
|
||||
import functools
|
||||
from typing import Any
|
||||
|
||||
from homeassistant.components import recorder, sensor
|
||||
|
@ -66,56 +67,68 @@ class EnergyPreferencesValidation:
|
|||
return dataclasses.asdict(self)
|
||||
|
||||
|
||||
@callback
|
||||
def _async_validate_usage_stat(
|
||||
async def _async_validate_usage_stat(
|
||||
hass: HomeAssistant,
|
||||
stat_value: str,
|
||||
stat_id: str,
|
||||
allowed_device_classes: Sequence[str],
|
||||
allowed_units: Mapping[str, Sequence[str]],
|
||||
unit_error: str,
|
||||
result: list[ValidationIssue],
|
||||
) -> None:
|
||||
"""Validate a statistic."""
|
||||
has_entity_source = valid_entity_id(stat_value)
|
||||
metadata = await hass.async_add_executor_job(
|
||||
functools.partial(
|
||||
recorder.statistics.get_metadata,
|
||||
hass,
|
||||
statistic_ids=(stat_id,),
|
||||
)
|
||||
)
|
||||
|
||||
if stat_id not in metadata:
|
||||
result.append(ValidationIssue("statistics_not_defined", stat_id))
|
||||
|
||||
has_entity_source = valid_entity_id(stat_id)
|
||||
|
||||
if not has_entity_source:
|
||||
return
|
||||
|
||||
if not recorder.is_entity_recorded(hass, stat_value):
|
||||
entity_id = stat_id
|
||||
|
||||
if not recorder.is_entity_recorded(hass, entity_id):
|
||||
result.append(
|
||||
ValidationIssue(
|
||||
"recorder_untracked",
|
||||
stat_value,
|
||||
entity_id,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
state = hass.states.get(stat_value)
|
||||
state = hass.states.get(entity_id)
|
||||
|
||||
if state is None:
|
||||
result.append(
|
||||
ValidationIssue(
|
||||
"entity_not_defined",
|
||||
stat_value,
|
||||
entity_id,
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
if state.state in (STATE_UNAVAILABLE, STATE_UNKNOWN):
|
||||
result.append(ValidationIssue("entity_unavailable", stat_value, state.state))
|
||||
result.append(ValidationIssue("entity_unavailable", entity_id, state.state))
|
||||
return
|
||||
|
||||
try:
|
||||
current_value: float | None = float(state.state)
|
||||
except ValueError:
|
||||
result.append(
|
||||
ValidationIssue("entity_state_non_numeric", stat_value, state.state)
|
||||
ValidationIssue("entity_state_non_numeric", entity_id, state.state)
|
||||
)
|
||||
return
|
||||
|
||||
if current_value is not None and current_value < 0:
|
||||
result.append(
|
||||
ValidationIssue("entity_negative_state", stat_value, current_value)
|
||||
ValidationIssue("entity_negative_state", entity_id, current_value)
|
||||
)
|
||||
|
||||
device_class = state.attributes.get(ATTR_DEVICE_CLASS)
|
||||
|
@ -123,7 +136,7 @@ def _async_validate_usage_stat(
|
|||
result.append(
|
||||
ValidationIssue(
|
||||
"entity_unexpected_device_class",
|
||||
stat_value,
|
||||
entity_id,
|
||||
device_class,
|
||||
)
|
||||
)
|
||||
|
@ -131,7 +144,7 @@ def _async_validate_usage_stat(
|
|||
unit = state.attributes.get("unit_of_measurement")
|
||||
|
||||
if device_class and unit not in allowed_units.get(device_class, []):
|
||||
result.append(ValidationIssue(unit_error, stat_value, unit))
|
||||
result.append(ValidationIssue(unit_error, entity_id, unit))
|
||||
|
||||
state_class = state.attributes.get(sensor.ATTR_STATE_CLASS)
|
||||
|
||||
|
@ -144,7 +157,7 @@ def _async_validate_usage_stat(
|
|||
result.append(
|
||||
ValidationIssue(
|
||||
"entity_unexpected_state_class",
|
||||
stat_value,
|
||||
entity_id,
|
||||
state_class,
|
||||
)
|
||||
)
|
||||
|
@ -154,7 +167,7 @@ def _async_validate_usage_stat(
|
|||
and sensor.ATTR_LAST_RESET not in state.attributes
|
||||
):
|
||||
result.append(
|
||||
ValidationIssue("entity_state_class_measurement_no_last_reset", stat_value)
|
||||
ValidationIssue("entity_state_class_measurement_no_last_reset", entity_id)
|
||||
)
|
||||
|
||||
|
||||
|
@ -192,33 +205,33 @@ def _async_validate_price_entity(
|
|||
result.append(ValidationIssue(unit_error, entity_id, unit))
|
||||
|
||||
|
||||
@callback
|
||||
def _async_validate_cost_stat(
|
||||
async def _async_validate_cost_stat(
|
||||
hass: HomeAssistant, stat_id: str, result: list[ValidationIssue]
|
||||
) -> None:
|
||||
"""Validate that the cost stat is correct."""
|
||||
metadata = await hass.async_add_executor_job(
|
||||
functools.partial(
|
||||
recorder.statistics.get_metadata,
|
||||
hass,
|
||||
statistic_ids=(stat_id,),
|
||||
)
|
||||
)
|
||||
|
||||
if stat_id not in metadata:
|
||||
result.append(ValidationIssue("statistics_not_defined", stat_id))
|
||||
|
||||
has_entity = valid_entity_id(stat_id)
|
||||
|
||||
if not has_entity:
|
||||
return
|
||||
|
||||
if not recorder.is_entity_recorded(hass, stat_id):
|
||||
result.append(
|
||||
ValidationIssue(
|
||||
"recorder_untracked",
|
||||
stat_id,
|
||||
)
|
||||
)
|
||||
result.append(ValidationIssue("recorder_untracked", stat_id))
|
||||
|
||||
state = hass.states.get(stat_id)
|
||||
|
||||
if state is None:
|
||||
result.append(
|
||||
ValidationIssue(
|
||||
"entity_not_defined",
|
||||
stat_id,
|
||||
)
|
||||
)
|
||||
result.append(ValidationIssue("entity_not_defined", stat_id))
|
||||
return
|
||||
|
||||
state_class = state.attributes.get("state_class")
|
||||
|
@ -244,16 +257,16 @@ def _async_validate_cost_stat(
|
|||
|
||||
@callback
|
||||
def _async_validate_auto_generated_cost_entity(
|
||||
hass: HomeAssistant, entity_id: str, result: list[ValidationIssue]
|
||||
hass: HomeAssistant, energy_entity_id: str, result: list[ValidationIssue]
|
||||
) -> None:
|
||||
"""Validate that the auto generated cost entity is correct."""
|
||||
if not recorder.is_entity_recorded(hass, entity_id):
|
||||
result.append(
|
||||
ValidationIssue(
|
||||
"recorder_untracked",
|
||||
entity_id,
|
||||
)
|
||||
)
|
||||
if energy_entity_id not in hass.data[DOMAIN]["cost_sensors"]:
|
||||
# The cost entity has not been setup
|
||||
return
|
||||
|
||||
cost_entity_id = hass.data[DOMAIN]["cost_sensors"][energy_entity_id]
|
||||
if not recorder.is_entity_recorded(hass, cost_entity_id):
|
||||
result.append(ValidationIssue("recorder_untracked", cost_entity_id))
|
||||
|
||||
|
||||
async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
||||
|
@ -271,7 +284,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
|
||||
if source["type"] == "grid":
|
||||
for flow in source["flow_from"]:
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
flow["stat_energy_from"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
@ -281,7 +294,9 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
)
|
||||
|
||||
if flow.get("stat_cost") is not None:
|
||||
_async_validate_cost_stat(hass, flow["stat_cost"], source_result)
|
||||
await _async_validate_cost_stat(
|
||||
hass, flow["stat_cost"], source_result
|
||||
)
|
||||
elif flow.get("entity_energy_price") is not None:
|
||||
_async_validate_price_entity(
|
||||
hass,
|
||||
|
@ -291,18 +306,18 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
ENERGY_PRICE_UNIT_ERROR,
|
||||
)
|
||||
|
||||
if (
|
||||
if flow.get("entity_energy_from") is not None and (
|
||||
flow.get("entity_energy_price") is not None
|
||||
or flow.get("number_energy_price") is not None
|
||||
):
|
||||
_async_validate_auto_generated_cost_entity(
|
||||
hass,
|
||||
hass.data[DOMAIN]["cost_sensors"][flow["stat_energy_from"]],
|
||||
flow["entity_energy_from"],
|
||||
source_result,
|
||||
)
|
||||
|
||||
for flow in source["flow_to"]:
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
flow["stat_energy_to"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
@ -312,7 +327,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
)
|
||||
|
||||
if flow.get("stat_compensation") is not None:
|
||||
_async_validate_cost_stat(
|
||||
await _async_validate_cost_stat(
|
||||
hass, flow["stat_compensation"], source_result
|
||||
)
|
||||
elif flow.get("entity_energy_price") is not None:
|
||||
|
@ -324,18 +339,18 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
ENERGY_PRICE_UNIT_ERROR,
|
||||
)
|
||||
|
||||
if (
|
||||
if flow.get("entity_energy_to") is not None and (
|
||||
flow.get("entity_energy_price") is not None
|
||||
or flow.get("number_energy_price") is not None
|
||||
):
|
||||
_async_validate_auto_generated_cost_entity(
|
||||
hass,
|
||||
hass.data[DOMAIN]["cost_sensors"][flow["stat_energy_to"]],
|
||||
flow["entity_energy_to"],
|
||||
source_result,
|
||||
)
|
||||
|
||||
elif source["type"] == "gas":
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
source["stat_energy_from"],
|
||||
GAS_USAGE_DEVICE_CLASSES,
|
||||
|
@ -345,7 +360,9 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
)
|
||||
|
||||
if source.get("stat_cost") is not None:
|
||||
_async_validate_cost_stat(hass, source["stat_cost"], source_result)
|
||||
await _async_validate_cost_stat(
|
||||
hass, source["stat_cost"], source_result
|
||||
)
|
||||
elif source.get("entity_energy_price") is not None:
|
||||
_async_validate_price_entity(
|
||||
hass,
|
||||
|
@ -355,18 +372,18 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
GAS_PRICE_UNIT_ERROR,
|
||||
)
|
||||
|
||||
if (
|
||||
if source.get("entity_energy_from") is not None and (
|
||||
source.get("entity_energy_price") is not None
|
||||
or source.get("number_energy_price") is not None
|
||||
):
|
||||
_async_validate_auto_generated_cost_entity(
|
||||
hass,
|
||||
hass.data[DOMAIN]["cost_sensors"][source["stat_energy_from"]],
|
||||
source["entity_energy_from"],
|
||||
source_result,
|
||||
)
|
||||
|
||||
elif source["type"] == "solar":
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
source["stat_energy_from"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
@ -376,7 +393,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
)
|
||||
|
||||
elif source["type"] == "battery":
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
source["stat_energy_from"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
@ -384,7 +401,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
ENERGY_UNIT_ERROR,
|
||||
source_result,
|
||||
)
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
source["stat_energy_to"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
@ -396,7 +413,7 @@ async def async_validate(hass: HomeAssistant) -> EnergyPreferencesValidation:
|
|||
for device in manager.data["device_consumption"]:
|
||||
device_result: list[ValidationIssue] = []
|
||||
result.device_consumption.append(device_result)
|
||||
_async_validate_usage_stat(
|
||||
await _async_validate_usage_stat(
|
||||
hass,
|
||||
device["stat_consumption"],
|
||||
ENERGY_USAGE_DEVICE_CLASSES,
|
||||
|
|
|
@ -21,6 +21,20 @@ def mock_is_entity_recorded():
|
|||
yield mocks
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_get_metadata():
|
||||
"""Mock recorder.statistics.get_metadata."""
|
||||
mocks = {}
|
||||
|
||||
with patch(
|
||||
"homeassistant.components.recorder.statistics.get_metadata",
|
||||
side_effect=lambda hass, statistic_ids: mocks.get(
|
||||
statistic_ids[0], {statistic_ids[0]: (1, {})}
|
||||
),
|
||||
):
|
||||
yield mocks
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
async def mock_energy_manager(hass):
|
||||
"""Set up energy."""
|
||||
|
@ -48,7 +62,9 @@ async def test_validation_empty_config(hass):
|
|||
("measurement", {"last_reset": "abc"}),
|
||||
],
|
||||
)
|
||||
async def test_validation(hass, mock_energy_manager, state_class, extra):
|
||||
async def test_validation(
|
||||
hass, mock_energy_manager, mock_get_metadata, state_class, extra
|
||||
):
|
||||
"""Test validating success."""
|
||||
for key in ("device_cons", "battery_import", "battery_export", "solar_production"):
|
||||
hass.states.async_set(
|
||||
|
@ -82,7 +98,7 @@ async def test_validation(hass, mock_energy_manager, state_class, extra):
|
|||
|
||||
|
||||
async def test_validation_device_consumption_entity_missing(hass, mock_energy_manager):
|
||||
"""Test validating missing stat for device."""
|
||||
"""Test validating missing entity for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
{"device_consumption": [{"stat_consumption": "sensor.not_exist"}]}
|
||||
)
|
||||
|
@ -90,10 +106,34 @@ async def test_validation_device_consumption_entity_missing(hass, mock_energy_ma
|
|||
"energy_sources": [],
|
||||
"device_consumption": [
|
||||
[
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "sensor.not_exist",
|
||||
"value": None,
|
||||
},
|
||||
{
|
||||
"type": "entity_not_defined",
|
||||
"identifier": "sensor.not_exist",
|
||||
"value": None,
|
||||
},
|
||||
]
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
async def test_validation_device_consumption_stat_missing(hass, mock_energy_manager):
|
||||
"""Test validating missing statistic for device with non entity stats."""
|
||||
await mock_energy_manager.async_update(
|
||||
{"device_consumption": [{"stat_consumption": "external:not_exist"}]}
|
||||
)
|
||||
assert (await validate.async_validate(hass)).as_dict() == {
|
||||
"energy_sources": [],
|
||||
"device_consumption": [
|
||||
[
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "external:not_exist",
|
||||
"value": None,
|
||||
}
|
||||
]
|
||||
],
|
||||
|
@ -101,7 +141,7 @@ async def test_validation_device_consumption_entity_missing(hass, mock_energy_ma
|
|||
|
||||
|
||||
async def test_validation_device_consumption_entity_unavailable(
|
||||
hass, mock_energy_manager
|
||||
hass, mock_energy_manager, mock_get_metadata
|
||||
):
|
||||
"""Test validating missing stat for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
|
@ -124,7 +164,7 @@ async def test_validation_device_consumption_entity_unavailable(
|
|||
|
||||
|
||||
async def test_validation_device_consumption_entity_non_numeric(
|
||||
hass, mock_energy_manager
|
||||
hass, mock_energy_manager, mock_get_metadata
|
||||
):
|
||||
"""Test validating missing stat for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
|
@ -147,7 +187,7 @@ async def test_validation_device_consumption_entity_non_numeric(
|
|||
|
||||
|
||||
async def test_validation_device_consumption_entity_unexpected_unit(
|
||||
hass, mock_energy_manager
|
||||
hass, mock_energy_manager, mock_get_metadata
|
||||
):
|
||||
"""Test validating missing stat for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
|
@ -178,7 +218,7 @@ async def test_validation_device_consumption_entity_unexpected_unit(
|
|||
|
||||
|
||||
async def test_validation_device_consumption_recorder_not_tracked(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating device based on untracked entity."""
|
||||
mock_is_entity_recorded["sensor.not_recorded"] = False
|
||||
|
@ -200,7 +240,9 @@ async def test_validation_device_consumption_recorder_not_tracked(
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_device_consumption_no_last_reset(hass, mock_energy_manager):
|
||||
async def test_validation_device_consumption_no_last_reset(
|
||||
hass, mock_energy_manager, mock_get_metadata
|
||||
):
|
||||
"""Test validating device based on untracked entity."""
|
||||
await mock_energy_manager.async_update(
|
||||
{"device_consumption": [{"stat_consumption": "sensor.no_last_reset"}]}
|
||||
|
@ -229,7 +271,7 @@ async def test_validation_device_consumption_no_last_reset(hass, mock_energy_man
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_solar(hass, mock_energy_manager):
|
||||
async def test_validation_solar(hass, mock_energy_manager, mock_get_metadata):
|
||||
"""Test validating missing stat for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
{
|
||||
|
@ -262,7 +304,7 @@ async def test_validation_solar(hass, mock_energy_manager):
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_battery(hass, mock_energy_manager):
|
||||
async def test_validation_battery(hass, mock_energy_manager, mock_get_metadata):
|
||||
"""Test validating missing stat for device."""
|
||||
await mock_energy_manager.async_update(
|
||||
{
|
||||
|
@ -313,10 +355,14 @@ async def test_validation_battery(hass, mock_energy_manager):
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_grid(hass, mock_energy_manager, mock_is_entity_recorded):
|
||||
async def test_validation_grid(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating grid with sensors for energy and cost/compensation."""
|
||||
mock_is_entity_recorded["sensor.grid_cost_1"] = False
|
||||
mock_is_entity_recorded["sensor.grid_compensation_1"] = False
|
||||
mock_get_metadata["sensor.grid_cost_1"] = {}
|
||||
mock_get_metadata["sensor.grid_compensation_1"] = {}
|
||||
await mock_energy_manager.async_update(
|
||||
{
|
||||
"energy_sources": [
|
||||
|
@ -365,6 +411,11 @@ async def test_validation_grid(hass, mock_energy_manager, mock_is_entity_recorde
|
|||
"identifier": "sensor.grid_consumption_1",
|
||||
"value": "beers",
|
||||
},
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "sensor.grid_cost_1",
|
||||
"value": None,
|
||||
},
|
||||
{
|
||||
"type": "recorder_untracked",
|
||||
"identifier": "sensor.grid_cost_1",
|
||||
|
@ -380,6 +431,11 @@ async def test_validation_grid(hass, mock_energy_manager, mock_is_entity_recorde
|
|||
"identifier": "sensor.grid_production_1",
|
||||
"value": "beers",
|
||||
},
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "sensor.grid_compensation_1",
|
||||
"value": None,
|
||||
},
|
||||
{
|
||||
"type": "recorder_untracked",
|
||||
"identifier": "sensor.grid_compensation_1",
|
||||
|
@ -396,8 +452,91 @@ async def test_validation_grid(hass, mock_energy_manager, mock_is_entity_recorde
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_grid_price_not_exist(hass, mock_energy_manager):
|
||||
"""Test validating grid with price entity that does not exist."""
|
||||
async def test_validation_grid_external_cost_compensation(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating grid with non entity stats for energy and cost/compensation."""
|
||||
mock_get_metadata["external:grid_cost_1"] = {}
|
||||
mock_get_metadata["external:grid_compensation_1"] = {}
|
||||
await mock_energy_manager.async_update(
|
||||
{
|
||||
"energy_sources": [
|
||||
{
|
||||
"type": "grid",
|
||||
"flow_from": [
|
||||
{
|
||||
"stat_energy_from": "sensor.grid_consumption_1",
|
||||
"stat_cost": "external:grid_cost_1",
|
||||
}
|
||||
],
|
||||
"flow_to": [
|
||||
{
|
||||
"stat_energy_to": "sensor.grid_production_1",
|
||||
"stat_compensation": "external:grid_compensation_1",
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.grid_consumption_1",
|
||||
"10.10",
|
||||
{
|
||||
"device_class": "energy",
|
||||
"unit_of_measurement": "beers",
|
||||
"state_class": "total_increasing",
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.grid_production_1",
|
||||
"10.10",
|
||||
{
|
||||
"device_class": "energy",
|
||||
"unit_of_measurement": "beers",
|
||||
"state_class": "total_increasing",
|
||||
},
|
||||
)
|
||||
|
||||
assert (await validate.async_validate(hass)).as_dict() == {
|
||||
"energy_sources": [
|
||||
[
|
||||
{
|
||||
"type": "entity_unexpected_unit_energy",
|
||||
"identifier": "sensor.grid_consumption_1",
|
||||
"value": "beers",
|
||||
},
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "external:grid_cost_1",
|
||||
"value": None,
|
||||
},
|
||||
{
|
||||
"type": "entity_unexpected_unit_energy",
|
||||
"identifier": "sensor.grid_production_1",
|
||||
"value": "beers",
|
||||
},
|
||||
{
|
||||
"type": "statistics_not_defined",
|
||||
"identifier": "external:grid_compensation_1",
|
||||
"value": None,
|
||||
},
|
||||
]
|
||||
],
|
||||
"device_consumption": [],
|
||||
}
|
||||
|
||||
|
||||
async def test_validation_grid_price_not_exist(
|
||||
hass, mock_energy_manager, mock_get_metadata, mock_is_entity_recorded
|
||||
):
|
||||
"""Test validating grid with errors.
|
||||
|
||||
- The price entity for the auto generated cost entity does not exist.
|
||||
- The auto generated cost entities are not recorded.
|
||||
"""
|
||||
mock_is_entity_recorded["sensor.grid_consumption_1_cost"] = False
|
||||
mock_is_entity_recorded["sensor.grid_production_1_compensation"] = False
|
||||
hass.states.async_set(
|
||||
"sensor.grid_consumption_1",
|
||||
"10.10",
|
||||
|
@ -450,13 +589,82 @@ async def test_validation_grid_price_not_exist(hass, mock_energy_manager):
|
|||
"type": "entity_not_defined",
|
||||
"identifier": "sensor.grid_price_1",
|
||||
"value": None,
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "recorder_untracked",
|
||||
"identifier": "sensor.grid_consumption_1_cost",
|
||||
"value": None,
|
||||
},
|
||||
{
|
||||
"type": "recorder_untracked",
|
||||
"identifier": "sensor.grid_production_1_compensation",
|
||||
"value": None,
|
||||
},
|
||||
]
|
||||
],
|
||||
"device_consumption": [],
|
||||
}
|
||||
|
||||
|
||||
async def test_validation_grid_auto_cost_entity_errors(
|
||||
hass, mock_energy_manager, mock_get_metadata, mock_is_entity_recorded, caplog
|
||||
):
|
||||
"""Test validating grid when the auto generated cost entity config is incorrect.
|
||||
|
||||
The intention of the test is to make sure the validation does not throw due to the
|
||||
bad config.
|
||||
"""
|
||||
hass.states.async_set(
|
||||
"sensor.grid_consumption_1",
|
||||
"10.10",
|
||||
{
|
||||
"device_class": "energy",
|
||||
"unit_of_measurement": "kWh",
|
||||
"state_class": "total_increasing",
|
||||
},
|
||||
)
|
||||
hass.states.async_set(
|
||||
"sensor.grid_production_1",
|
||||
"10.10",
|
||||
{
|
||||
"device_class": "energy",
|
||||
"unit_of_measurement": "kWh",
|
||||
"state_class": "total_increasing",
|
||||
},
|
||||
)
|
||||
await mock_energy_manager.async_update(
|
||||
{
|
||||
"energy_sources": [
|
||||
{
|
||||
"type": "grid",
|
||||
"flow_from": [
|
||||
{
|
||||
"stat_energy_from": "sensor.grid_consumption_1",
|
||||
"entity_energy_from": None,
|
||||
"entity_energy_price": None,
|
||||
"number_energy_price": 0.20,
|
||||
}
|
||||
],
|
||||
"flow_to": [
|
||||
{
|
||||
"stat_energy_to": "sensor.grid_production_1",
|
||||
"entity_energy_to": "invalid",
|
||||
"entity_energy_price": None,
|
||||
"number_energy_price": 0.10,
|
||||
}
|
||||
],
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert (await validate.async_validate(hass)).as_dict() == {
|
||||
"energy_sources": [[]],
|
||||
"device_consumption": [],
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"state, unit, expected",
|
||||
(
|
||||
|
@ -481,7 +689,7 @@ async def test_validation_grid_price_not_exist(hass, mock_energy_manager):
|
|||
),
|
||||
)
|
||||
async def test_validation_grid_price_errors(
|
||||
hass, mock_energy_manager, state, unit, expected
|
||||
hass, mock_energy_manager, mock_get_metadata, state, unit, expected
|
||||
):
|
||||
"""Test validating grid with price data that gives errors."""
|
||||
hass.states.async_set(
|
||||
|
@ -526,7 +734,9 @@ async def test_validation_grid_price_errors(
|
|||
}
|
||||
|
||||
|
||||
async def test_validation_gas(hass, mock_energy_manager, mock_is_entity_recorded):
|
||||
async def test_validation_gas(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating gas with sensors for energy and cost/compensation."""
|
||||
mock_is_entity_recorded["sensor.gas_cost_1"] = False
|
||||
mock_is_entity_recorded["sensor.gas_compensation_1"] = False
|
||||
|
@ -653,7 +863,7 @@ async def test_validation_gas(hass, mock_energy_manager, mock_is_entity_recorded
|
|||
|
||||
|
||||
async def test_validation_gas_no_costs_tracking(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating gas with sensors without cost tracking."""
|
||||
await mock_energy_manager.async_update(
|
||||
|
@ -687,7 +897,7 @@ async def test_validation_gas_no_costs_tracking(
|
|||
|
||||
|
||||
async def test_validation_grid_no_costs_tracking(
|
||||
hass, mock_energy_manager, mock_is_entity_recorded
|
||||
hass, mock_energy_manager, mock_is_entity_recorded, mock_get_metadata
|
||||
):
|
||||
"""Test validating grid with sensors for energy without cost tracking."""
|
||||
await mock_energy_manager.async_update(
|
||||
|
|
Loading…
Add table
Reference in a new issue