Skip to content

Commit

Permalink
Add validation to set_humidity action in humidifier (#125863)
Browse files Browse the repository at this point in the history
  • Loading branch information
gjohansson-ST committed Sep 19, 2024
1 parent dd10a83 commit 4d63bf4
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 5 deletions.
38 changes: 36 additions & 2 deletions homeassistant/components/humidifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
SERVICE_TURN_ON,
STATE_ON,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.deprecation import (
all_with_deprecated_constants,
Expand All @@ -45,7 +46,13 @@
DOMAIN,
MODE_AUTO,
MODE_AWAY,
MODE_BABY,
MODE_BOOST,
MODE_COMFORT,
MODE_ECO,
MODE_HOME,
MODE_NORMAL,
MODE_SLEEP,
SERVICE_SET_HUMIDITY,
SERVICE_SET_MODE,
HumidifierAction,
Expand Down Expand Up @@ -108,7 +115,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
vol.Coerce(int), vol.Range(min=0, max=100)
)
},
"async_set_humidity",
async_service_humidity_set,
)

return True
Expand Down Expand Up @@ -281,6 +288,33 @@ def supported_features_compat(self) -> HumidifierEntityFeature:
return features


async def async_service_humidity_set(
entity: HumidifierEntity, service_call: ServiceCall
) -> None:
"""Handle set humidity service."""
humidity = service_call.data[ATTR_HUMIDITY]
min_humidity = entity.min_humidity
max_humidity = entity.max_humidity
_LOGGER.debug(
"Check valid humidity %d in range %d - %d",
humidity,
min_humidity,
max_humidity,
)
if humidity < min_humidity or humidity > max_humidity:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="humidity_out_of_range",
translation_placeholders={
"humidity": str(humidity),
"min_humidity": str(min_humidity),
"max_humidity": str(max_humidity),
},
)

await entity.async_set_humidity(humidity)


# As we import deprecated constants from the const module, we need to add these two functions
# otherwise this module will be logged for using deprecated constants and not the custom component
# These can be removed if no deprecated constant are in this module anymore
Expand Down
5 changes: 5 additions & 0 deletions homeassistant/components/humidifier/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -115,5 +115,10 @@
"name": "[%key:common::action::toggle%]",
"description": "Toggles the humidifier on/off."
}
},
"exceptions": {
"humidity_out_of_range": {
"message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}."
}
}
}
69 changes: 69 additions & 0 deletions tests/components/humidifier/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
"""Fixtures for Humidifier platform tests."""

from collections.abc import Generator

import pytest

from homeassistant.components.humidifier import DOMAIN as HUMIDIFIER_DOMAIN
from homeassistant.config_entries import ConfigEntry, ConfigFlow
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant

from tests.common import (
MockConfigEntry,
MockModule,
mock_config_flow,
mock_integration,
mock_platform,
)


class MockFlow(ConfigFlow):
"""Test flow."""


@pytest.fixture
def config_flow_fixture(hass: HomeAssistant) -> Generator[None]:
"""Mock config flow."""
mock_platform(hass, "test.config_flow")

with mock_config_flow("test", MockFlow):
yield


@pytest.fixture
def register_test_integration(
hass: HomeAssistant, config_flow_fixture: None
) -> Generator:
"""Provide a mocked integration for tests."""

config_entry = MockConfigEntry(domain="test")
config_entry.add_to_hass(hass)

async def help_async_setup_entry_init(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Set up test config entry."""
await hass.config_entries.async_forward_entry_setups(
config_entry, [HUMIDIFIER_DOMAIN]
)
return True

async def help_async_unload_entry(
hass: HomeAssistant, config_entry: ConfigEntry
) -> bool:
"""Unload test config emntry."""
return await hass.config_entries.async_unload_platforms(
config_entry, [Platform.HUMIDIFIER]
)

mock_integration(
hass,
MockModule(
"test",
async_setup_entry=help_async_setup_entry_init,
async_unload_entry=help_async_unload_entry,
),
)

return config_entry
85 changes: 82 additions & 3 deletions tests/components/humidifier/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,28 @@

from homeassistant.components import humidifier
from homeassistant.components.humidifier import (
ATTR_HUMIDITY,
ATTR_MODE,
DOMAIN as HUMIDIFIER_DOMAIN,
MODE_ECO,
MODE_NORMAL,
SERVICE_SET_HUMIDITY,
HumidifierEntity,
HumidifierEntityFeature,
)
from homeassistant.core import HomeAssistant

from tests.common import help_test_all, import_and_test_deprecated_constant_enum
from homeassistant.exceptions import ServiceValidationError

from tests.common import (
MockConfigEntry,
MockEntity,
help_test_all,
import_and_test_deprecated_constant_enum,
setup_test_component_platform,
)


class MockHumidifierEntity(HumidifierEntity):
class MockHumidifierEntity(MockEntity, HumidifierEntity):
"""Mock Humidifier device to use in tests."""

@property
Expand Down Expand Up @@ -101,3 +113,70 @@ def supported_features(self) -> int:
assert "is using deprecated supported features values" not in caplog.text

assert entity.state_attributes[ATTR_MODE] == "mode1"


async def test_humidity_validation(
hass: HomeAssistant,
register_test_integration: MockConfigEntry,
caplog: pytest.LogCaptureFixture,
) -> None:
"""Test validation for humidity."""

class MockHumidifierEntityHumidity(MockEntity, HumidifierEntity):
"""Mock climate class with mocked aux heater."""

_attr_supported_features = HumidifierEntityFeature.MODES
_attr_available_modes = [MODE_NORMAL, MODE_ECO]
_attr_mode = MODE_NORMAL
_attr_target_humidity = 50
_attr_min_humidity = 50
_attr_max_humidity = 60

def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
self._attr_target_humidity = humidity

test_humidifier = MockHumidifierEntityHumidity(
name="Test",
unique_id="unique_humidifier_test",
)

setup_test_component_platform(
hass, HUMIDIFIER_DOMAIN, entities=[test_humidifier], from_config_entry=True
)
await hass.config_entries.async_setup(register_test_integration.entry_id)
await hass.async_block_till_done()

state = hass.states.get("humidifier.test")
assert state.attributes.get(ATTR_HUMIDITY) == 50

with pytest.raises(
ServiceValidationError,
match="Provided humidity 1 is not valid. Accepted range is 50 to 60",
) as exc:
await hass.services.async_call(
HUMIDIFIER_DOMAIN,
SERVICE_SET_HUMIDITY,
{
"entity_id": "humidifier.test",
ATTR_HUMIDITY: "1",
},
blocking=True,
)

assert exc.value.translation_key == "humidity_out_of_range"
assert "Check valid humidity 1 in range 50 - 60" in caplog.text

with pytest.raises(
ServiceValidationError,
match="Provided humidity 70 is not valid. Accepted range is 50 to 60",
) as exc:
await hass.services.async_call(
HUMIDIFIER_DOMAIN,
SERVICE_SET_HUMIDITY,
{
"entity_id": "humidifier.test",
ATTR_HUMIDITY: "70",
},
blocking=True,
)

0 comments on commit 4d63bf4

Please sign in to comment.