From 3fc043d99e5840d3c3df408e74059c1100abe088 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Sun, 20 Aug 2023 09:04:48 +0200 Subject: [PATCH] Deduplicate Tasmota sensor tests (#98628) --- tests/components/tasmota/test_sensor.py | 431 +++++++++++------------- 1 file changed, 206 insertions(+), 225 deletions(-) diff --git a/tests/components/tasmota/test_sensor.py b/tests/components/tasmota/test_sensor.py index 6a896615c73..22ee652aef3 100644 --- a/tests/components/tasmota/test_sensor.py +++ b/tests/components/tasmota/test_sensor.py @@ -38,7 +38,7 @@ from .test_common import ( from tests.common import async_fire_mqtt_message, async_fire_time_changed from tests.typing import MqttMockHAClient, MqttMockPahoClient -BAD_INDEXED_SENSOR_CONFIG_3 = { +BAD_LIST_SENSOR_CONFIG_3 = { "sn": { "Time": "2020-09-25T12:47:15", "ENERGY": { @@ -47,7 +47,9 @@ BAD_INDEXED_SENSOR_CONFIG_3 = { } } -INDEXED_SENSOR_CONFIG = { +# This configuration has some sensors where values are lists +# Home Assistant maps this to one sensor for each list item +LIST_SENSOR_CONFIG = { "sn": { "Time": "2020-09-25T12:47:15", "ENERGY": { @@ -74,7 +76,8 @@ INDEXED_SENSOR_CONFIG = { } } -INDEXED_SENSOR_CONFIG_2 = { +# Same as LIST_SENSOR_CONFIG, but Total is also a list +LIST_SENSOR_CONFIG_2 = { "sn": { "Time": "2020-09-25T12:47:15", "ENERGY": { @@ -101,8 +104,9 @@ INDEXED_SENSOR_CONFIG_2 = { } } - -NESTED_SENSOR_CONFIG_1 = { +# This configuration has some sensors where values are dicts +# Home Assistant maps this to one sensor for each dictionary item +DICT_SENSOR_CONFIG_1 = { "sn": { "Time": "2020-03-03T00:00:00+00:00", "TX23": { @@ -119,7 +123,22 @@ NESTED_SENSOR_CONFIG_1 = { } } -NESTED_SENSOR_CONFIG_2 = { +# Similar to LIST_SENSOR_CONFIG, but Total is a dict +DICT_SENSOR_CONFIG_2 = { + "sn": { + "Time": "2023-01-27T11:04:56", + "ENERGY": { + "Total": { + "Phase1": 0.017, + "Phase2": 0.017, + }, + "TotalStartTime": "2018-11-23T15:33:47", + }, + } +} + + +TEMPERATURE_SENSOR_CONFIG = { "sn": { "Time": "2023-01-27T11:04:56", "DS18B20": { @@ -131,65 +150,33 @@ NESTED_SENSOR_CONFIG_2 = { } -async def test_controlling_state_via_mqtt( - hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota -) -> None: - """Test state update via MQTT.""" - config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(DEFAULT_SENSOR_CONFIG) - mac = config["mac"] - - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/config", - json.dumps(config), - ) - await hass.async_block_till_done() - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/sensors", - json.dumps(sensor_config), - ) - await hass.async_block_till_done() - - state = hass.states.get("sensor.tasmota_dht11_temperature") - assert state.state == "unavailable" - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - entity_reg = er.async_get(hass) - entry = entity_reg.async_get("sensor.tasmota_dht11_temperature") - assert entry.disabled is False - assert entry.disabled_by is None - assert entry.entity_category is None - - async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_dht11_temperature") - assert state.state == STATE_UNKNOWN - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Test periodic state update - async_fire_mqtt_message( - hass, "tasmota_49A3BC/tele/SENSOR", '{"DHT11":{"Temperature":20.5}}' - ) - state = hass.states.get("sensor.tasmota_dht11_temperature") - assert state.state == "20.5" - - # Test polled state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"DHT11":{"Temperature":20.0}}}', - ) - state = hass.states.get("sensor.tasmota_dht11_temperature") - assert state.state == "20.0" - - @pytest.mark.parametrize( ("sensor_config", "entity_ids", "messages", "states"), [ ( - NESTED_SENSOR_CONFIG_1, + DEFAULT_SENSOR_CONFIG, + ["sensor.tasmota_dht11_temperature"], + ( + '{"DHT11":{"Temperature":20.5}}', + '{"StatusSNS":{"DHT11":{"Temperature":20.0}}}', + ), + ( + { + "sensor.tasmota_dht11_temperature": { + "state": "20.5", + "attributes": { + "device_class": "temperature", + "unit_of_measurement": "°C", + }, + }, + }, + { + "sensor.tasmota_dht11_temperature": {"state": "20.0"}, + }, + ), + ), + ( + DICT_SENSOR_CONFIG_1, ["sensor.tasmota_tx23_speed_act", "sensor.tasmota_tx23_dir_card"], ( '{"TX23":{"Speed":{"Act":"12.3"},"Dir": {"Card": "WSW"}}}', @@ -197,17 +184,50 @@ async def test_controlling_state_via_mqtt( ), ( { - "sensor.tasmota_tx23_speed_act": "12.3", - "sensor.tasmota_tx23_dir_card": "WSW", + "sensor.tasmota_tx23_speed_act": { + "state": "12.3", + "attributes": { + "device_class": None, + "unit_of_measurement": "km/h", + }, + }, + "sensor.tasmota_tx23_dir_card": {"state": "WSW"}, }, { - "sensor.tasmota_tx23_speed_act": "23.4", - "sensor.tasmota_tx23_dir_card": "ESE", + "sensor.tasmota_tx23_speed_act": {"state": "23.4"}, + "sensor.tasmota_tx23_dir_card": {"state": "ESE"}, }, ), ), ( - NESTED_SENSOR_CONFIG_2, + LIST_SENSOR_CONFIG, + [ + "sensor.tasmota_energy_totaltariff_0", + "sensor.tasmota_energy_totaltariff_1", + ], + ( + '{"ENERGY":{"TotalTariff":[1.2,3.4]}}', + '{"StatusSNS":{"ENERGY":{"TotalTariff":[5.6,7.8]}}}', + ), + ( + { + "sensor.tasmota_energy_totaltariff_0": { + "state": "1.2", + "attributes": { + "device_class": None, + "unit_of_measurement": None, + }, + }, + "sensor.tasmota_energy_totaltariff_1": {"state": "3.4"}, + }, + { + "sensor.tasmota_energy_totaltariff_0": {"state": "5.6"}, + "sensor.tasmota_energy_totaltariff_1": {"state": "7.8"}, + }, + ), + ), + ( + TEMPERATURE_SENSOR_CONFIG, ["sensor.tasmota_ds18b20_temperature", "sensor.tasmota_ds18b20_id"], ( '{"DS18B20":{"Id": "01191ED79190","Temperature": 12.3}}', @@ -215,18 +235,117 @@ async def test_controlling_state_via_mqtt( ), ( { - "sensor.tasmota_ds18b20_temperature": "12.3", - "sensor.tasmota_ds18b20_id": "01191ED79190", + "sensor.tasmota_ds18b20_temperature": { + "state": "12.3", + "attributes": { + "device_class": "temperature", + "unit_of_measurement": "°C", + }, + }, + "sensor.tasmota_ds18b20_id": {"state": "01191ED79190"}, }, { - "sensor.tasmota_ds18b20_temperature": "23.4", - "sensor.tasmota_ds18b20_id": "meep", + "sensor.tasmota_ds18b20_temperature": {"state": "23.4"}, + "sensor.tasmota_ds18b20_id": {"state": "meep"}, + }, + ), + ), + # Test simple Total sensor + ( + LIST_SENSOR_CONFIG, + ["sensor.tasmota_energy_total"], + ( + '{"ENERGY":{"Total":1.2,"TotalStartTime":"2018-11-23T15:33:47"}}', + '{"StatusSNS":{"ENERGY":{"Total":5.6,"TotalStartTime":"2018-11-23T16:33:47"}}}', + ), + ( + { + "sensor.tasmota_energy_total": { + "state": "1.2", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + }, + { + "sensor.tasmota_energy_total": {"state": "5.6"}, + }, + ), + ), + # Test list Total sensors + ( + LIST_SENSOR_CONFIG_2, + ["sensor.tasmota_energy_total_0", "sensor.tasmota_energy_total_1"], + ( + '{"ENERGY":{"Total":[1.2, 3.4],"TotalStartTime":"2018-11-23T15:33:47"}}', + '{"StatusSNS":{"ENERGY":{"Total":[5.6, 7.8],"TotalStartTime":"2018-11-23T16:33:47"}}}', + ), + ( + { + "sensor.tasmota_energy_total_0": { + "state": "1.2", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + "sensor.tasmota_energy_total_1": { + "state": "3.4", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + }, + { + "sensor.tasmota_energy_total_0": {"state": "5.6"}, + "sensor.tasmota_energy_total_1": {"state": "7.8"}, + }, + ), + ), + # Test dict Total sensors + ( + DICT_SENSOR_CONFIG_2, + [ + "sensor.tasmota_energy_total_phase1", + "sensor.tasmota_energy_total_phase2", + ], + ( + '{"ENERGY":{"Total":{"Phase1":1.2, "Phase2":3.4},"TotalStartTime":"2018-11-23T15:33:47"}}', + '{"StatusSNS":{"ENERGY":{"Total":{"Phase1":5.6, "Phase2":7.8},"TotalStartTime":"2018-11-23T15:33:47"}}}', + ), + ( + { + "sensor.tasmota_energy_total_phase1": { + "state": "1.2", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + "sensor.tasmota_energy_total_phase2": { + "state": "3.4", + "attributes": { + "device_class": "energy", + ATTR_STATE_CLASS: SensorStateClass.TOTAL, + "unit_of_measurement": "kWh", + }, + }, + }, + { + "sensor.tasmota_energy_total_phase1": {"state": "5.6"}, + "sensor.tasmota_energy_total_phase2": {"state": "7.8"}, }, ), ), ], ) -async def test_nested_sensor_state_via_mqtt( +async def test_controlling_state_via_mqtt( hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota, @@ -236,6 +355,7 @@ async def test_nested_sensor_state_via_mqtt( states, ) -> None: """Test state update via MQTT.""" + entity_reg = er.async_get(hass) config = copy.deepcopy(DEFAULT_CONFIG) sensor_config = copy.deepcopy(sensor_config) mac = config["mac"] @@ -258,6 +378,11 @@ async def test_nested_sensor_state_via_mqtt( assert state.state == "unavailable" assert not state.attributes.get(ATTR_ASSUMED_STATE) + entry = entity_reg.async_get(entity_id) + assert entry.disabled is False + assert entry.disabled_by is None + assert entry.entity_category is None + async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") await hass.async_block_till_done() for entity_id in entity_ids: @@ -269,163 +394,19 @@ async def test_nested_sensor_state_via_mqtt( async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/SENSOR", messages[0]) for entity_id in entity_ids: state = hass.states.get(entity_id) - assert state.state == states[0][entity_id] + expected_state = states[0][entity_id] + assert state.state == expected_state["state"] + for attribute, expected in expected_state.get("attributes", {}).items(): + assert state.attributes.get(attribute) == expected # Test polled state update async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/STATUS10", messages[1]) for entity_id in entity_ids: state = hass.states.get(entity_id) - assert state.state == states[1][entity_id] - - -async def test_indexed_sensor_state_via_mqtt( - hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota -) -> None: - """Test state update via MQTT.""" - config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(INDEXED_SENSOR_CONFIG) - mac = config["mac"] - - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/config", - json.dumps(config), - ) - await hass.async_block_till_done() - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/sensors", - json.dumps(sensor_config), - ) - await hass.async_block_till_done() - - state = hass.states.get("sensor.tasmota_energy_totaltariff_1") - assert state.state == "unavailable" - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_energy_totaltariff_1") - assert state.state == STATE_UNKNOWN - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Test periodic state update - async_fire_mqtt_message( - hass, "tasmota_49A3BC/tele/SENSOR", '{"ENERGY":{"TotalTariff":[1.2,3.4]}}' - ) - state = hass.states.get("sensor.tasmota_energy_totaltariff_1") - assert state.state == "3.4" - - # Test polled state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"ENERGY":{"TotalTariff":[5.6,7.8]}}}', - ) - state = hass.states.get("sensor.tasmota_energy_totaltariff_1") - assert state.state == "7.8" - - -async def test_indexed_sensor_state_via_mqtt2( - hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota -) -> None: - """Test state update via MQTT for sensor with last_reset property.""" - config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(INDEXED_SENSOR_CONFIG) - mac = config["mac"] - - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/config", - json.dumps(config), - ) - await hass.async_block_till_done() - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/sensors", - json.dumps(sensor_config), - ) - await hass.async_block_till_done() - - state = hass.states.get("sensor.tasmota_energy_total") - assert state.state == "unavailable" - assert not state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.TOTAL - - async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_energy_total") - assert state.state == STATE_UNKNOWN - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Test periodic state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/tele/SENSOR", - '{"ENERGY":{"Total":1.2,"TotalStartTime":"2018-11-23T15:33:47"}}', - ) - state = hass.states.get("sensor.tasmota_energy_total") - assert state.state == "1.2" - - # Test polled state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"ENERGY":{"Total":5.6,"TotalStartTime":"2018-11-23T16:33:47"}}}', - ) - state = hass.states.get("sensor.tasmota_energy_total") - assert state.state == "5.6" - - -async def test_indexed_sensor_state_via_mqtt3( - hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota -) -> None: - """Test state update via MQTT for indexed sensor with last_reset property.""" - config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(INDEXED_SENSOR_CONFIG_2) - mac = config["mac"] - - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/config", - json.dumps(config), - ) - await hass.async_block_till_done() - async_fire_mqtt_message( - hass, - f"{DEFAULT_PREFIX}/{mac}/sensors", - json.dumps(sensor_config), - ) - await hass.async_block_till_done() - - state = hass.states.get("sensor.tasmota_energy_total_1") - assert state.state == "unavailable" - assert not state.attributes.get(ATTR_ASSUMED_STATE) - assert state.attributes[ATTR_STATE_CLASS] is SensorStateClass.TOTAL - - async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online") - await hass.async_block_till_done() - state = hass.states.get("sensor.tasmota_energy_total_1") - assert state.state == STATE_UNKNOWN - assert not state.attributes.get(ATTR_ASSUMED_STATE) - - # Test periodic state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/tele/SENSOR", - '{"ENERGY":{"Total":[1.2, 3.4],"TotalStartTime":"2018-11-23T15:33:47"}}', - ) - state = hass.states.get("sensor.tasmota_energy_total_1") - assert state.state == "3.4" - - # Test polled state update - async_fire_mqtt_message( - hass, - "tasmota_49A3BC/stat/STATUS10", - '{"StatusSNS":{"ENERGY":{"Total":[5.6,7.8],"TotalStartTime":"2018-11-23T16:33:47"}}}', - ) - state = hass.states.get("sensor.tasmota_energy_total_1") - assert state.state == "7.8" + expected_state = states[1][entity_id] + assert state.state == expected_state["state"] + for attribute, expected in expected_state.get("attributes", {}).items(): + assert state.attributes.get(attribute) == expected async def test_bad_indexed_sensor_state_via_mqtt( @@ -433,7 +414,7 @@ async def test_bad_indexed_sensor_state_via_mqtt( ) -> None: """Test state update via MQTT where sensor is not matching configuration.""" config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(BAD_INDEXED_SENSOR_CONFIG_3) + sensor_config = copy.deepcopy(BAD_LIST_SENSOR_CONFIG_3) mac = config["mac"] async_fire_mqtt_message( @@ -784,7 +765,7 @@ async def test_nested_sensor_attributes( ) -> None: """Test correct attributes for sensors.""" config = copy.deepcopy(DEFAULT_CONFIG) - sensor_config = copy.deepcopy(NESTED_SENSOR_CONFIG_1) + sensor_config = copy.deepcopy(DICT_SENSOR_CONFIG_1) mac = config["mac"] async_fire_mqtt_message(