diff --git a/tests/components/derivative/test_config_flow.py b/tests/components/derivative/test_config_flow.py index 5e2d9446cdc..09e9fc9f07d 100644 --- a/tests/components/derivative/test_config_flow.py +++ b/tests/components/derivative/test_config_flow.py @@ -1,14 +1,18 @@ """Test the Derivative config flow.""" +from datetime import timedelta from unittest.mock import patch +from freezegun import freeze_time import pytest from homeassistant import config_entries from homeassistant.components.derivative.const import DOMAIN +from homeassistant.const import STATE_UNAVAILABLE from homeassistant.core import HomeAssistant from homeassistant.data_entry_flow import FlowResultType from homeassistant.helpers import selector +from homeassistant.util import dt as dt_util from tests.common import MockConfigEntry, get_schema_suggested_value @@ -154,3 +158,86 @@ async def test_options( await hass.async_block_till_done() state = hass.states.get(f"{platform}.my_derivative") assert state.attributes["unit_of_measurement"] == "cat/h" + + +async def test_update_unit(hass: HomeAssistant) -> None: + """Test behavior of changing the unit_time option.""" + # Setup the config entry + source_id = "sensor.source" + config_entry = MockConfigEntry( + data={}, + domain=DOMAIN, + options={ + "name": "My derivative", + "round": 1.0, + "source": source_id, + "unit_time": "min", + "time_window": {"seconds": 0.0}, + }, + title="My derivative", + ) + derivative_id = "sensor.my_derivative" + config_entry.add_to_hass(hass) + assert await hass.config_entries.async_setup(config_entry.entry_id) + await hass.async_block_till_done() + + state = hass.states.get(derivative_id) + assert state.state == STATE_UNAVAILABLE + assert state.attributes.get("unit_of_measurement") is None + + time = dt_util.utcnow() + with freeze_time(time) as freezer: + # First state update of the source. + # Derivative does not learn the unit yet. + hass.states.async_set(source_id, 5, {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(derivative_id) + assert state.state == "0.0" + assert state.attributes.get("unit_of_measurement") is None + + # Second state update of the source. + time += timedelta(minutes=1) + freezer.move_to(time) + hass.states.async_set(source_id, "7", {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(derivative_id) + assert state.state == "2.0" + assert state.attributes.get("unit_of_measurement") == "dogs/min" + + # Update the unit_time from minutes to seconds. + result = await hass.config_entries.options.async_init(config_entry.entry_id) + result = await hass.config_entries.options.async_configure( + result["flow_id"], + user_input={ + "source": source_id, + "round": 1.0, + "unit_time": "s", + "time_window": {"seconds": 0.0}, + }, + ) + await hass.async_block_till_done() + + # Check the state after reconfigure. Neither unit or state has changed. + state = hass.states.get(derivative_id) + assert state.state == "2.0" + assert state.attributes.get("unit_of_measurement") == "dogs/min" + + # Third state update of the source. + time += timedelta(seconds=1) + freezer.move_to(time) + hass.states.async_set(source_id, "10", {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(derivative_id) + assert state.state == "3.0" + # While the state is correctly reporting a state of 3 dogs per second, it incorrectly keeps + # the unit as dogs/min + assert state.attributes.get("unit_of_measurement") == "dogs/min" + + # Fourth state update of the source. + time += timedelta(seconds=1) + freezer.move_to(time) + hass.states.async_set(source_id, "20", {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(derivative_id) + assert state.state == "10.0" + assert state.attributes.get("unit_of_measurement") == "dogs/min" diff --git a/tests/components/derivative/test_sensor.py b/tests/components/derivative/test_sensor.py index 5a601ad26dd..52b7ae725ea 100644 --- a/tests/components/derivative/test_sensor.py +++ b/tests/components/derivative/test_sensor.py @@ -934,3 +934,65 @@ async def test_unavailable_boot( assert state is not None # Now that the source sensor has two valid datapoints, we can calculate derivative assert state.state == "5.00" + + +async def test_source_unit_change( + hass: HomeAssistant, +) -> None: + """Test how derivative responds when the source sensor changes unit.""" + source_id = "sensor.source" + config = { + "sensor": { + "platform": "derivative", + "name": "derivative", + "source": source_id, + "unit_time": "s", + } + } + + assert await async_setup_component(hass, "sensor", config) + await hass.async_block_till_done() + entity_id = "sensor.derivative" + + state = hass.states.get(entity_id) + assert state.state == STATE_UNAVAILABLE + assert state.attributes.get("unit_of_measurement") is None + + time = dt_util.utcnow() + with freeze_time(time) as freezer: + # First state update of the source. + # Derivative does not learn the UoM yet. + hass.states.async_set(source_id, "5", {"unit_of_measurement": "cats"}) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "0.000" + assert state.attributes.get("unit_of_measurement") is None + + # Second state update of the source. + time += timedelta(seconds=1) + freezer.move_to(time) + hass.states.async_set(source_id, "7", {"unit_of_measurement": "cats"}) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "2.000" + assert state.attributes.get("unit_of_measurement") == "cats/s" + + # Third state update of the source, source unit changes to dogs. + # Ignored by derivative which continues reporting cats. + time += timedelta(seconds=1) + freezer.move_to(time) + hass.states.async_set(source_id, "12", {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "5.000" + assert state.attributes.get("unit_of_measurement") == "cats/s" + + # Fourth state update of the source, still dogs. + # Ignored by derivative which continues reporting cats. + time += timedelta(seconds=1) + freezer.move_to(time) + hass.states.async_set(source_id, "20", {"unit_of_measurement": "dogs"}) + await hass.async_block_till_done() + state = hass.states.get(entity_id) + assert state.state == "8.000" + assert state.attributes.get("unit_of_measurement") == "cats/s"