Skip to content

Commit a3dec46

Browse files
authored
Add derivative tests exhibiting unit issues (home-assistant#153051)
1 parent 7a3630e commit a3dec46

File tree

2 files changed

+149
-0
lines changed

2 files changed

+149
-0
lines changed

tests/components/derivative/test_config_flow.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
"""Test the Derivative config flow."""
22

3+
from datetime import timedelta
34
from unittest.mock import patch
45

6+
from freezegun import freeze_time
57
import pytest
68

79
from homeassistant import config_entries
810
from homeassistant.components.derivative.const import DOMAIN
11+
from homeassistant.const import STATE_UNAVAILABLE
912
from homeassistant.core import HomeAssistant
1013
from homeassistant.data_entry_flow import FlowResultType
1114
from homeassistant.helpers import selector
15+
from homeassistant.util import dt as dt_util
1216

1317
from tests.common import MockConfigEntry, get_schema_suggested_value
1418

@@ -154,3 +158,86 @@ async def test_options(
154158
await hass.async_block_till_done()
155159
state = hass.states.get(f"{platform}.my_derivative")
156160
assert state.attributes["unit_of_measurement"] == "cat/h"
161+
162+
163+
async def test_update_unit(hass: HomeAssistant) -> None:
164+
"""Test behavior of changing the unit_time option."""
165+
# Setup the config entry
166+
source_id = "sensor.source"
167+
config_entry = MockConfigEntry(
168+
data={},
169+
domain=DOMAIN,
170+
options={
171+
"name": "My derivative",
172+
"round": 1.0,
173+
"source": source_id,
174+
"unit_time": "min",
175+
"time_window": {"seconds": 0.0},
176+
},
177+
title="My derivative",
178+
)
179+
derivative_id = "sensor.my_derivative"
180+
config_entry.add_to_hass(hass)
181+
assert await hass.config_entries.async_setup(config_entry.entry_id)
182+
await hass.async_block_till_done()
183+
184+
state = hass.states.get(derivative_id)
185+
assert state.state == STATE_UNAVAILABLE
186+
assert state.attributes.get("unit_of_measurement") is None
187+
188+
time = dt_util.utcnow()
189+
with freeze_time(time) as freezer:
190+
# First state update of the source.
191+
# Derivative does not learn the unit yet.
192+
hass.states.async_set(source_id, 5, {"unit_of_measurement": "dogs"})
193+
await hass.async_block_till_done()
194+
state = hass.states.get(derivative_id)
195+
assert state.state == "0.0"
196+
assert state.attributes.get("unit_of_measurement") is None
197+
198+
# Second state update of the source.
199+
time += timedelta(minutes=1)
200+
freezer.move_to(time)
201+
hass.states.async_set(source_id, "7", {"unit_of_measurement": "dogs"})
202+
await hass.async_block_till_done()
203+
state = hass.states.get(derivative_id)
204+
assert state.state == "2.0"
205+
assert state.attributes.get("unit_of_measurement") == "dogs/min"
206+
207+
# Update the unit_time from minutes to seconds.
208+
result = await hass.config_entries.options.async_init(config_entry.entry_id)
209+
result = await hass.config_entries.options.async_configure(
210+
result["flow_id"],
211+
user_input={
212+
"source": source_id,
213+
"round": 1.0,
214+
"unit_time": "s",
215+
"time_window": {"seconds": 0.0},
216+
},
217+
)
218+
await hass.async_block_till_done()
219+
220+
# Check the state after reconfigure. Neither unit or state has changed.
221+
state = hass.states.get(derivative_id)
222+
assert state.state == "2.0"
223+
assert state.attributes.get("unit_of_measurement") == "dogs/min"
224+
225+
# Third state update of the source.
226+
time += timedelta(seconds=1)
227+
freezer.move_to(time)
228+
hass.states.async_set(source_id, "10", {"unit_of_measurement": "dogs"})
229+
await hass.async_block_till_done()
230+
state = hass.states.get(derivative_id)
231+
assert state.state == "3.0"
232+
# While the state is correctly reporting a state of 3 dogs per second, it incorrectly keeps
233+
# the unit as dogs/min
234+
assert state.attributes.get("unit_of_measurement") == "dogs/min"
235+
236+
# Fourth state update of the source.
237+
time += timedelta(seconds=1)
238+
freezer.move_to(time)
239+
hass.states.async_set(source_id, "20", {"unit_of_measurement": "dogs"})
240+
await hass.async_block_till_done()
241+
state = hass.states.get(derivative_id)
242+
assert state.state == "10.0"
243+
assert state.attributes.get("unit_of_measurement") == "dogs/min"

tests/components/derivative/test_sensor.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -934,3 +934,65 @@ async def test_unavailable_boot(
934934
assert state is not None
935935
# Now that the source sensor has two valid datapoints, we can calculate derivative
936936
assert state.state == "5.00"
937+
938+
939+
async def test_source_unit_change(
940+
hass: HomeAssistant,
941+
) -> None:
942+
"""Test how derivative responds when the source sensor changes unit."""
943+
source_id = "sensor.source"
944+
config = {
945+
"sensor": {
946+
"platform": "derivative",
947+
"name": "derivative",
948+
"source": source_id,
949+
"unit_time": "s",
950+
}
951+
}
952+
953+
assert await async_setup_component(hass, "sensor", config)
954+
await hass.async_block_till_done()
955+
entity_id = "sensor.derivative"
956+
957+
state = hass.states.get(entity_id)
958+
assert state.state == STATE_UNAVAILABLE
959+
assert state.attributes.get("unit_of_measurement") is None
960+
961+
time = dt_util.utcnow()
962+
with freeze_time(time) as freezer:
963+
# First state update of the source.
964+
# Derivative does not learn the UoM yet.
965+
hass.states.async_set(source_id, "5", {"unit_of_measurement": "cats"})
966+
await hass.async_block_till_done()
967+
state = hass.states.get(entity_id)
968+
assert state.state == "0.000"
969+
assert state.attributes.get("unit_of_measurement") is None
970+
971+
# Second state update of the source.
972+
time += timedelta(seconds=1)
973+
freezer.move_to(time)
974+
hass.states.async_set(source_id, "7", {"unit_of_measurement": "cats"})
975+
await hass.async_block_till_done()
976+
state = hass.states.get(entity_id)
977+
assert state.state == "2.000"
978+
assert state.attributes.get("unit_of_measurement") == "cats/s"
979+
980+
# Third state update of the source, source unit changes to dogs.
981+
# Ignored by derivative which continues reporting cats.
982+
time += timedelta(seconds=1)
983+
freezer.move_to(time)
984+
hass.states.async_set(source_id, "12", {"unit_of_measurement": "dogs"})
985+
await hass.async_block_till_done()
986+
state = hass.states.get(entity_id)
987+
assert state.state == "5.000"
988+
assert state.attributes.get("unit_of_measurement") == "cats/s"
989+
990+
# Fourth state update of the source, still dogs.
991+
# Ignored by derivative which continues reporting cats.
992+
time += timedelta(seconds=1)
993+
freezer.move_to(time)
994+
hass.states.async_set(source_id, "20", {"unit_of_measurement": "dogs"})
995+
await hass.async_block_till_done()
996+
state = hass.states.get(entity_id)
997+
assert state.state == "8.000"
998+
assert state.attributes.get("unit_of_measurement") == "cats/s"

0 commit comments

Comments
 (0)