Compare commits

...

7 Commits

18 changed files with 298 additions and 162 deletions

View File

@@ -407,6 +407,7 @@ homeassistant.components.person.*
homeassistant.components.pi_hole.*
homeassistant.components.ping.*
homeassistant.components.plugwise.*
homeassistant.components.pooldose.*
homeassistant.components.portainer.*
homeassistant.components.powerfox.*
homeassistant.components.powerwall.*

View File

@@ -7,6 +7,5 @@
"integration_type": "service",
"iot_class": "local_push",
"loggers": ["datadog"],
"quality_scale": "legacy",
"requirements": ["datadog==0.52.0"]
}

View File

@@ -112,6 +112,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.CO,
native_unit_of_measurement_fn=lambda x: x.pollutants.co.concentration.units,
exists_fn=lambda x: "co" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.co.concentration.value,
),
AirQualitySensorEntityDescription(
@@ -143,6 +144,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
translation_key="nitrogen_dioxide",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.no2.concentration.units,
exists_fn=lambda x: "no2" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.no2.concentration.value,
),
AirQualitySensorEntityDescription(
@@ -150,6 +152,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
translation_key="ozone",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.o3.concentration.units,
exists_fn=lambda x: "o3" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.o3.concentration.value,
),
AirQualitySensorEntityDescription(
@@ -157,6 +160,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.PM10,
native_unit_of_measurement_fn=lambda x: x.pollutants.pm10.concentration.units,
exists_fn=lambda x: "pm10" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.pm10.concentration.value,
),
AirQualitySensorEntityDescription(
@@ -164,6 +168,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.PM25,
native_unit_of_measurement_fn=lambda x: x.pollutants.pm25.concentration.units,
exists_fn=lambda x: "pm25" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.pm25.concentration.value,
),
AirQualitySensorEntityDescription(
@@ -171,6 +176,7 @@ AIR_QUALITY_SENSOR_TYPES: tuple[AirQualitySensorEntityDescription, ...] = (
translation_key="sulphur_dioxide",
state_class=SensorStateClass.MEASUREMENT,
native_unit_of_measurement_fn=lambda x: x.pollutants.so2.concentration.units,
exists_fn=lambda x: "so2" in {p.code for p in x.pollutants},
value_fn=lambda x: x.pollutants.so2.concentration.value,
),
)

View File

@@ -7,7 +7,6 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["london_tube_status"],
"quality_scale": "legacy",
"requirements": ["london-tube-status==0.5"],
"single_config_entry": true
}

View File

@@ -528,7 +528,10 @@ DISCOVERY_SCHEMAS = [
),
),
entity_class=MatterBinarySensor,
required_attributes=(clusters.Thermostat.Attributes.RemoteSensing,),
required_attributes=(
clusters.Thermostat.Attributes.RemoteSensing,
clusters.Thermostat.Attributes.OutdoorTemperature,
),
allow_multi=True,
),
MatterDiscoverySchema(

View File

@@ -9,7 +9,7 @@ from aiohttp import ClientError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_DOMAIN, CONF_HOST, CONF_PASSWORD
from homeassistant.const import CONF_DOMAIN, CONF_HOST, CONF_NAME, CONF_PASSWORD
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
@@ -37,6 +37,16 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
}
)
STEP_RECONFIGURE_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
),
}
)
class NamecheapDnsConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Namecheap DynamicDNS."""
@@ -89,3 +99,41 @@ class NamecheapDnsConfigFlow(ConfigFlow, domain=DOMAIN):
deprecate_yaml_issue(self.hass, import_success=True)
return result
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfigure flow."""
errors: dict[str, str] = {}
entry = self._get_reconfigure_entry()
if user_input is not None:
session = async_get_clientsession(self.hass)
try:
if not await update_namecheapdns(
session,
entry.data[CONF_HOST],
entry.data[CONF_DOMAIN],
user_input[CONF_PASSWORD],
):
errors["base"] = "update_failed"
except ClientError:
_LOGGER.debug("Cannot connect", exc_info=True)
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
if not errors:
return self.async_update_reload_and_abort(
entry,
data_updates=user_input,
)
return self.async_show_form(
step_id="reconfigure",
data_schema=STEP_RECONFIGURE_DATA_SCHEMA,
errors=errors,
description_placeholders={CONF_NAME: entry.title},
)

View File

@@ -1,7 +1,8 @@
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -9,6 +10,15 @@
"update_failed": "Updating DNS failed"
},
"step": {
"reconfigure": {
"data": {
"password": "[%key:component::namecheapdns::config::step::user::data::password%]"
},
"data_description": {
"password": "[%key:component::namecheapdns::config::step::user::data_description::password%]"
},
"title": "Re-configure {name}"
},
"user": {
"data": {
"domain": "[%key:common::config_flow::data::username%]",

View File

@@ -6,6 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/nederlandse_spoorwegen",
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "legacy",
"requirements": ["nsapi==3.1.3"]
}

View File

@@ -7,6 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["pyrail"],
"quality_scale": "legacy",
"requirements": ["pyrail==0.4.1"]
}

View File

@@ -2,7 +2,8 @@
from __future__ import annotations
from typing import Literal
from collections.abc import Callable, Coroutine
from typing import Any, Literal
from pooldose.type_definitions import DeviceInfoDict, ValueDict
@@ -80,7 +81,10 @@ class PooldoseEntity(CoordinatorEntity[PooldoseCoordinator]):
return platform_data.get(self.entity_description.key)
async def _async_perform_write(
self, api_call, key: str, value: bool | str | float
self,
api_call: Callable[[str, Any], Coroutine[Any, Any, bool]],
key: str,
value: bool | str | float,
) -> None:
"""Perform a write call to the API with unified error handling.

View File

@@ -11,6 +11,6 @@
"documentation": "https://www.home-assistant.io/integrations/pooldose",
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "gold",
"quality_scale": "platinum",
"requirements": ["python-pooldose==0.8.2"]
}

View File

@@ -71,4 +71,4 @@ rules:
# Platinum
async-dependency: done
inject-websession: done
strict-typing: todo
strict-typing: done

View File

@@ -7,6 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_push",
"loggers": ["prowl"],
"quality_scale": "legacy",
"requirements": ["prowlpy==1.1.1"]
}

View File

@@ -7,6 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["wsdot"],
"quality_scale": "legacy",
"requirements": ["wsdot==0.0.1"]
}

10
mypy.ini generated
View File

@@ -3826,6 +3826,16 @@ disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.pooldose.*]
check_untyped_defs = true
disallow_incomplete_defs = true
disallow_subclassing_any = true
disallow_untyped_calls = true
disallow_untyped_decorators = true
disallow_untyped_defs = true
warn_return_any = true
warn_unreachable = true
[mypy-homeassistant.components.portainer.*]
check_untyped_defs = true
disallow_incomplete_defs = true

View File

@@ -41,7 +41,20 @@ from homeassistant.setup import async_setup_component
from tests.common import get_fixture_path
VALUES = [17, 20, 15.3]
VALUES_ERROR = [17, "string", 15.3]
STATES_ONE_ERROR = ["17", "string", "15.3"]
STATES_ONE_MISSING = ["17", None, "15.3"]
STATES_ONE_UNKNOWN = ["17", STATE_UNKNOWN, "15.3"]
STATES_ONE_UNAVAILABLE = ["17", STATE_UNAVAILABLE, "15.3"]
STATES_ALL_ERROR = ["string", "string", "string"]
STATES_ALL_MISSING = [None, None, None]
STATES_ALL_UNKNOWN = [STATE_UNKNOWN, STATE_UNKNOWN, STATE_UNKNOWN]
STATES_ALL_UNAVAILABLE = [STATE_UNAVAILABLE, STATE_UNAVAILABLE, STATE_UNAVAILABLE]
STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN = [None, STATE_UNAVAILABLE, STATE_UNKNOWN]
STATES_MIX_MISSING_UNAVAILABLE = [None, STATE_UNAVAILABLE, STATE_UNAVAILABLE]
STATES_MIX_MISSING_UNKNOWN = [None, STATE_UNKNOWN, STATE_UNKNOWN]
STATES_MIX_UNAVAILABLE_UNKNOWN = [STATE_UNAVAILABLE, STATE_UNKNOWN, STATE_UNKNOWN]
COUNT = len(VALUES)
MIN_VALUE = min(VALUES)
MAX_VALUE = max(VALUES)
@@ -53,6 +66,18 @@ SUM_VALUE = sum(VALUES)
PRODUCT_VALUE = prod(VALUES)
def set_or_remove_state(
hass: HomeAssistant,
entity_id: str,
state: str | None,
) -> None:
"""Set or remove the state of an entity."""
if state is None:
hass.states.async_remove(entity_id)
else:
hass.states.async_set(entity_id, state)
@pytest.mark.parametrize(
("sensor_type", "result", "attributes"),
[
@@ -90,7 +115,7 @@ async def test_sensors2(
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
@@ -140,7 +165,7 @@ async def test_sensors_attributes_defined(hass: HomeAssistant) -> None:
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.MEASUREMENT,
@@ -185,7 +210,7 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
assert state.attributes.get("min_entity_id") is None
assert state.attributes.get("max_entity_id") is None
hass.states.async_set(entity_ids[1], VALUES[1])
hass.states.async_set(entity_ids[1], str(VALUES[1]))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_max")
@@ -210,8 +235,8 @@ async def test_not_enough_sensor_value(hass: HomeAssistant) -> None:
async def test_reload(hass: HomeAssistant) -> None:
"""Verify we can reload sensors."""
hass.states.async_set("sensor.test_1", 12345)
hass.states.async_set("sensor.test_2", 45678)
hass.states.async_set("sensor.test_1", "12345")
hass.states.async_set("sensor.test_2", "45678")
await async_setup_component(
hass,
@@ -249,8 +274,28 @@ async def test_reload(hass: HomeAssistant) -> None:
assert hass.states.get("sensor.second_test")
@pytest.mark.parametrize(
("states_list", "expected_group_state"),
[
(STATES_ONE_ERROR, "17.0"),
(STATES_ONE_MISSING, "17.0"),
(STATES_ONE_UNKNOWN, "17.0"),
(STATES_ONE_UNAVAILABLE, "17.0"),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
],
)
async def test_sensor_incorrect_state_with_ignore_non_numeric(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
states_list: list[str | None],
expected_group_state: str,
) -> None:
"""Test that non numeric values are ignored in a group."""
config = {
@@ -271,27 +316,48 @@ async def test_sensor_incorrect_state_with_ignore_non_numeric(
entity_ids = config["sensor"]["entities"]
# Check that the final sensor value ignores the non numeric input
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_ignore_non_numeric")
assert state.state == "17.0"
assert state.state == expected_group_state
assert (
"Unable to use state. Only numerical states are supported," not in caplog.text
)
# Check that the final sensor value with all numeric inputs
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_ignore_non_numeric")
assert state.state == "20.0"
@pytest.mark.parametrize(
("states_list", "expected_group_state", "error_count"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN, 1),
(STATES_ONE_MISSING, "17.0", 0),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN, 1),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN, 1),
(STATES_ALL_ERROR, STATE_UNAVAILABLE, 3),
(STATES_ALL_MISSING, STATE_UNAVAILABLE, 0),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE, 3),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE, 3),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE, 2),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE, 3),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE, 2),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE, 2),
],
)
async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
hass: HomeAssistant, caplog: pytest.LogCaptureFixture
hass: HomeAssistant,
caplog: pytest.LogCaptureFixture,
states_list: list[str | None],
expected_group_state: str,
error_count: int,
) -> None:
"""Test that non numeric values cause a group to be unknown."""
config = {
@@ -312,24 +378,46 @@ async def test_sensor_incorrect_state_with_not_ignore_non_numeric(
entity_ids = config["sensor"]["entities"]
# Check that the final sensor value is unavailable if a non numeric input exists
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_failure")
assert state.state == "unknown"
assert "Unable to use state. Only numerical states are supported" in caplog.text
assert state.state == expected_group_state
assert (
caplog.text.count("Unable to use state. Only numerical states are supported")
== error_count
)
# Check that the final sensor value is correct with all numeric inputs
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_failure")
assert state.state == "20.0"
async def test_sensor_require_all_states(hass: HomeAssistant) -> None:
@pytest.mark.parametrize(
("states_list", "expected_group_state"),
[
(STATES_ONE_ERROR, STATE_UNKNOWN),
(STATES_ONE_MISSING, "32.3"),
(STATES_ONE_UNKNOWN, STATE_UNKNOWN),
(STATES_ONE_UNAVAILABLE, STATE_UNKNOWN),
(STATES_ALL_ERROR, STATE_UNAVAILABLE),
(STATES_ALL_MISSING, STATE_UNAVAILABLE),
(STATES_ALL_UNKNOWN, STATE_UNAVAILABLE),
(STATES_ALL_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
(STATES_MIX_MISSING_UNAVAILABLE_UNKNOWN, STATE_UNAVAILABLE),
],
)
async def test_sensor_require_all_states(
hass: HomeAssistant, states_list: list[str | None], expected_group_state: str
) -> None:
"""Test the sum sensor with missing state require all."""
config = {
SENSOR_DOMAIN: {
@@ -348,13 +436,13 @@ async def test_sensor_require_all_states(hass: HomeAssistant) -> None:
entity_ids = config["sensor"]["entities"]
for entity_id, value in dict(zip(entity_ids, VALUES_ERROR, strict=False)).items():
hass.states.async_set(entity_id, value)
for entity_id, value in dict(zip(entity_ids, states_list, strict=False)).items():
set_or_remove_state(hass, entity_id, value)
await hass.async_block_till_done()
state = hass.states.get("sensor.test_sum")
assert state.state == STATE_UNKNOWN
assert state.state == expected_group_state
async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
@@ -373,7 +461,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -382,7 +470,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -391,7 +479,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -413,7 +501,7 @@ async def test_sensor_calculated_properties(hass: HomeAssistant) -> None:
# is converted correctly by the group sensor
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -446,7 +534,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -455,7 +543,7 @@ async def test_sensor_with_uoms_but_no_device_class(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -464,7 +552,7 @@ async def test_sensor_with_uoms_but_no_device_class(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"unit_of_measurement": "W",
},
@@ -487,7 +575,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -508,7 +596,7 @@ async def test_sensor_with_uoms_but_no_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.POWER,
"state_class": SensorStateClass.MEASUREMENT,
@@ -541,7 +629,7 @@ async def test_sensor_calculated_properties_not_same(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -550,7 +638,7 @@ async def test_sensor_calculated_properties_not_same(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -559,7 +647,7 @@ async def test_sensor_calculated_properties_not_same(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.CURRENT,
"state_class": SensorStateClass.MEASUREMENT,
@@ -604,7 +692,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -613,7 +701,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -622,7 +710,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -642,7 +730,7 @@ async def test_sensor_calculated_result_fails_on_uom(hass: HomeAssistant) -> Non
hass.states.async_set(
entity_ids[2],
12,
"12",
{
"device_class": SensorDeviceClass.ENERGY,
"state_class": SensorStateClass.TOTAL,
@@ -677,7 +765,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
hass.states.async_set(
entity_ids[0],
VALUES[0],
str(VALUES[0]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -686,7 +774,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
)
hass.states.async_set(
entity_ids[1],
VALUES[1],
str(VALUES[1]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -695,7 +783,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
)
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -720,7 +808,7 @@ async def test_sensor_calculated_properties_not_convertible_device_class(
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"device_class": SensorDeviceClass.HUMIDITY,
"state_class": SensorStateClass.MEASUREMENT,
@@ -760,7 +848,7 @@ async def test_last_sensor(hass: HomeAssistant) -> None:
entity_ids = config["sensor"]["entities"]
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(entity_id, value)
hass.states.async_set(entity_id, str(value))
await hass.async_block_till_done()
state = hass.states.get("sensor.test_last")
assert str(float(value)) == state.state
@@ -797,7 +885,7 @@ async def test_sensors_attributes_added_when_entity_info_available(
for entity_id, value in dict(zip(entity_ids, VALUES, strict=False)).items():
hass.states.async_set(
entity_id,
value,
str(value),
{
ATTR_DEVICE_CLASS: SensorDeviceClass.VOLUME,
ATTR_STATE_CLASS: SensorStateClass.TOTAL,
@@ -843,9 +931,9 @@ async def test_sensor_state_class_no_uom_not_available(
"unit_of_measurement": PERCENTAGE,
}
hass.states.async_set(entity_ids[0], VALUES[0], input_attributes)
hass.states.async_set(entity_ids[1], VALUES[1], input_attributes)
hass.states.async_set(entity_ids[2], VALUES[2], input_attributes)
hass.states.async_set(entity_ids[0], str(VALUES[0]), input_attributes)
hass.states.async_set(entity_ids[1], str(VALUES[1]), input_attributes)
hass.states.async_set(entity_ids[2], str(VALUES[2]), input_attributes)
await hass.async_block_till_done()
assert await async_setup_component(hass, "sensor", config)
@@ -864,7 +952,7 @@ async def test_sensor_state_class_no_uom_not_available(
# sensor.test_3 drops the unit of measurement
hass.states.async_set(
entity_ids[2],
VALUES[2],
str(VALUES[2]),
{
"state_class": SensorStateClass.MEASUREMENT,
},
@@ -914,7 +1002,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
test_cases = [
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"unit_of_measurement": PERCENTAGE,
@@ -926,7 +1014,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[1],
"value": VALUES[1],
"value": str(VALUES[1]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -939,7 +1027,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[2],
"value": VALUES[2],
"value": str(VALUES[2]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.TEMPERATURE,
@@ -952,7 +1040,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[2],
"value": VALUES[2],
"value": str(VALUES[2]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -966,7 +1054,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
"device_class": SensorDeviceClass.HUMIDITY,
@@ -980,7 +1068,7 @@ async def test_sensor_different_attributes_ignore_non_numeric(
},
{
"entity": entity_ids[0],
"value": VALUES[0],
"value": str(VALUES[0]),
"attributes": {
"state_class": SensorStateClass.MEASUREMENT,
},

View File

@@ -439,54 +439,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-0000000000000021-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v4][binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20EBP1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ebp1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_local_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
@@ -535,54 +487,6 @@
'state': 'off',
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': None,
'config_entry_id': <ANY>,
'config_subentry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'binary_sensor',
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': None,
'original_icon': None,
'original_name': 'Outdoor temperature remote sensing',
'platform': 'matter',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': 0,
'translation_key': 'thermostat_remote_sensing_outdoor_temperature',
'unique_id': '00000000000004D2-000000000000000C-MatterNodeDevice-1-ThermostatRemoteSensing_OutdoorTemperature-513-26',
'unit_of_measurement': None,
})
# ---
# name: test_binary_sensors[eve_thermo_v5][binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'friendly_name': 'Eve Thermo 20ECD1701 Outdoor temperature remote sensing',
}),
'context': <ANY>,
'entity_id': 'binary_sensor.eve_thermo_20ecd1701_outdoor_temperature_remote_sensing',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'off',
})
# ---
# name: test_binary_sensors[heiman_motion_sensor_m1][binary_sensor.smart_motion_sensor_occupancy-entry]
EntityRegistryEntrySnapshot({
'aliases': set({

View File

@@ -7,6 +7,7 @@ import pytest
from homeassistant.components.namecheapdns.const import DOMAIN
from homeassistant.config_entries import SOURCE_IMPORT, SOURCE_USER
from homeassistant.const import CONF_PASSWORD
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.data_entry_flow import FlowResultType
from homeassistant.helpers import issue_registry as ir
@@ -14,6 +15,8 @@ from homeassistant.setup import async_setup_component
from .conftest import TEST_USER_INPUT
from tests.common import MockConfigEntry
@pytest.mark.usefixtures("mock_namecheap")
async def test_form(hass: HomeAssistant, mock_setup_entry: AsyncMock) -> None:
@@ -140,3 +143,68 @@ async def test_init_import_flow(
)
assert len(mock_setup_entry.mock_calls) == 1
assert len(hass.config_entries.async_entries(DOMAIN)) == 1
@pytest.mark.usefixtures("mock_namecheap")
async def test_reconfigure(
hass: HomeAssistant,
config_entry: MockConfigEntry,
) -> None:
"""Test reconfigure flow."""
config_entry.add_to_hass(hass)
result = await config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: "new-password"}
)
await hass.async_block_till_done()
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert config_entry.data[CONF_PASSWORD] == "new-password"
@pytest.mark.parametrize(
("side_effect", "text_error"),
[
(ValueError, "unknown"),
(False, "update_failed"),
(ClientError, "cannot_connect"),
],
)
async def test_reconfigure_errors(
hass: HomeAssistant,
config_entry: MockConfigEntry,
mock_namecheap: AsyncMock,
side_effect: Exception | bool,
text_error: str,
) -> None:
"""Test we handle errors."""
config_entry.add_to_hass(hass)
result = await config_entry.start_reconfigure_flow(hass)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "reconfigure"
mock_namecheap.side_effect = [side_effect]
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: "new-password"}
)
assert result["type"] is FlowResultType.FORM
assert result["errors"] == {"base": text_error}
mock_namecheap.side_effect = None
result = await hass.config_entries.flow.async_configure(
result["flow_id"], {CONF_PASSWORD: "new-password"}
)
assert result["type"] is FlowResultType.ABORT
assert result["reason"] == "reconfigure_successful"
assert config_entry.data[CONF_PASSWORD] == "new-password"