hass-core/tests/components/tasmota/test_fan.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

390 lines
13 KiB
Python
Raw Normal View History

"""The tests for the Tasmota fan platform."""
import copy
import json
2021-01-01 22:31:56 +01:00
from unittest.mock import patch
from hatasmota.utils import (
get_topic_stat_result,
get_topic_tele_state,
get_topic_tele_will,
)
import pytest
from voluptuous import MultipleInvalid
from homeassistant.components import fan
from homeassistant.components.tasmota.const import DEFAULT_PREFIX
from homeassistant.const import ATTR_ASSUMED_STATE, STATE_OFF, STATE_ON, Platform
from homeassistant.core import HomeAssistant
from .test_common import (
DEFAULT_CONFIG,
help_test_availability,
help_test_availability_discovery_update,
help_test_availability_poll_state,
help_test_availability_when_connection_lost,
help_test_deep_sleep_availability,
help_test_deep_sleep_availability_when_connection_lost,
help_test_discovery_device_remove,
help_test_discovery_removal,
help_test_discovery_update_unchanged,
help_test_entity_id_update_discovery_update,
help_test_entity_id_update_subscriptions,
)
from tests.common import async_fire_mqtt_message
from tests.components.fan import common
from tests.typing import MqttMockHAClient, MqttMockPahoClient
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)
config["if"] = 1
mac = config["mac"]
async_fire_mqtt_message(
hass,
f"{DEFAULT_PREFIX}/{mac}/config",
json.dumps(config),
)
await hass.async_block_till_done()
state = hass.states.get("fan.tasmota")
assert state.state == "unavailable"
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/LWT", "Online")
2021-10-28 14:23:26 +02:00
await hass.async_block_till_done()
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
assert state.attributes["percentage"] is None
assert (
state.attributes["supported_features"]
== fan.FanEntityFeature.SET_SPEED
| fan.FanEntityFeature.TURN_OFF
| fan.FanEntityFeature.TURN_ON
)
assert not state.attributes.get(ATTR_ASSUMED_STATE)
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":1}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_ON
assert state.attributes["percentage"] == 33
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":2}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_ON
assert state.attributes["percentage"] == 66
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":3}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_ON
assert state.attributes["percentage"] == 100
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":0}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
assert state.attributes["percentage"] == 0
async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"FanSpeed":1}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_ON
assert state.attributes["percentage"] == 33
async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"FanSpeed":0}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
assert state.attributes["percentage"] == 0
async def test_sending_mqtt_commands(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test the sending MQTT commands."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
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, "tasmota_49A3BC/tele/LWT", "Online")
2021-10-28 14:23:26 +02:00
await hass.async_block_till_done()
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
await hass.async_block_till_done()
await hass.async_block_till_done()
mqtt_mock.async_publish.reset_mock()
# Turn the fan on and verify MQTT message is sent
await common.async_turn_on(hass, "fan.tasmota")
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "2", 0, False
)
mqtt_mock.async_publish.reset_mock()
# Tasmota is not optimistic, the state should still be off
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
# Turn the fan off and verify MQTT message is sent
await common.async_turn_off(hass, "fan.tasmota")
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "0", 0, False
)
mqtt_mock.async_publish.reset_mock()
# Set speed percentage and verify MQTT message is sent
await common.async_set_percentage(hass, "fan.tasmota", 0)
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "0", 0, False
)
mqtt_mock.async_publish.reset_mock()
# Set speed percentage and verify MQTT message is sent
await common.async_set_percentage(hass, "fan.tasmota", 15)
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "1", 0, False
)
mqtt_mock.async_publish.reset_mock()
# Set speed percentage and verify MQTT message is sent
await common.async_set_percentage(hass, "fan.tasmota", 50)
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "2", 0, False
)
mqtt_mock.async_publish.reset_mock()
# Set speed percentage and verify MQTT message is sent
await common.async_set_percentage(hass, "fan.tasmota", 90)
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "3", 0, False
)
# Test the last known fan speed is restored
# First, get a fan speed update
async_fire_mqtt_message(hass, "tasmota_49A3BC/tele/STATE", '{"FanSpeed":3}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_ON
assert state.attributes["percentage"] == 100
mqtt_mock.async_publish.reset_mock()
# Then turn the fan off and get a fan state update
await common.async_turn_off(hass, "fan.tasmota")
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "0", 0, False
)
mqtt_mock.async_publish.reset_mock()
async_fire_mqtt_message(hass, "tasmota_49A3BC/stat/RESULT", '{"FanSpeed":0}')
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
assert state.attributes["percentage"] == 0
mqtt_mock.async_publish.reset_mock()
# Finally, turn the fan on again and verify MQTT message is sent with last known speed
await common.async_turn_on(hass, "fan.tasmota")
mqtt_mock.async_publish.assert_called_once_with(
"tasmota_49A3BC/cmnd/FanSpeed", "3", 0, False
)
mqtt_mock.async_publish.reset_mock()
async def test_invalid_fan_speed_percentage(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test the sending MQTT commands."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
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, "tasmota_49A3BC/tele/LWT", "Online")
2021-10-28 14:23:26 +02:00
await hass.async_block_till_done()
state = hass.states.get("fan.tasmota")
assert state.state == STATE_OFF
await hass.async_block_till_done()
await hass.async_block_till_done()
mqtt_mock.async_publish.reset_mock()
# Set an unsupported speed and verify MQTT message is not sent
with pytest.raises(MultipleInvalid) as excinfo:
await common.async_set_percentage(hass, "fan.tasmota", 101)
assert "value must be at most 100" in str(excinfo.value)
mqtt_mock.async_publish.assert_not_called()
async def test_availability_when_connection_lost(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
mqtt_mock: MqttMockHAClient,
setup_tasmota,
) -> None:
"""Test availability after MQTT disconnection."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_availability_when_connection_lost(
hass, mqtt_client_mock, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)
async def test_deep_sleep_availability_when_connection_lost(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
mqtt_mock: MqttMockHAClient,
setup_tasmota,
) -> None:
"""Test availability after MQTT disconnection."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_deep_sleep_availability_when_connection_lost(
hass, mqtt_client_mock, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)
async def test_availability(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test availability."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_availability(
hass, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)
async def test_deep_sleep_availability(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test availability."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_deep_sleep_availability(
hass, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)
async def test_availability_discovery_update(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test availability discovery update."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_availability_discovery_update(
hass, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)
async def test_availability_poll_state(
hass: HomeAssistant,
mqtt_client_mock: MqttMockPahoClient,
mqtt_mock: MqttMockHAClient,
setup_tasmota,
) -> None:
"""Test polling after MQTT connection (re)established."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
poll_topic = "tasmota_49A3BC/cmnd/STATE"
await help_test_availability_poll_state(
hass, mqtt_client_mock, mqtt_mock, Platform.FAN, config, poll_topic, ""
)
async def test_discovery_removal_fan(
hass: HomeAssistant,
mqtt_mock: MqttMockHAClient,
caplog: pytest.LogCaptureFixture,
setup_tasmota,
) -> None:
"""Test removal of discovered fan."""
config1 = copy.deepcopy(DEFAULT_CONFIG)
config1["if"] = 1
config2 = copy.deepcopy(DEFAULT_CONFIG)
config2["if"] = 0
await help_test_discovery_removal(
hass,
mqtt_mock,
caplog,
Platform.FAN,
config1,
config2,
object_id="tasmota",
name="Tasmota",
)
async def test_discovery_update_unchanged_fan(
hass: HomeAssistant,
mqtt_mock: MqttMockHAClient,
caplog: pytest.LogCaptureFixture,
setup_tasmota,
) -> None:
"""Test update of discovered fan."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
with patch(
"homeassistant.components.tasmota.fan.TasmotaFan.discovery_update"
) as discovery_update:
await help_test_discovery_update_unchanged(
hass,
mqtt_mock,
caplog,
Platform.FAN,
config,
discovery_update,
object_id="tasmota",
name="Tasmota",
)
async def test_discovery_device_remove(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test device registry remove."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
unique_id = f"{DEFAULT_CONFIG['mac']}_fan_fan_ifan"
await help_test_discovery_device_remove(
hass, mqtt_mock, Platform.FAN, unique_id, config
)
async def test_entity_id_update_subscriptions(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test MQTT subscriptions are managed when entity_id is updated."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
topics = [
get_topic_stat_result(config),
get_topic_tele_state(config),
get_topic_tele_will(config),
]
await help_test_entity_id_update_subscriptions(
hass, mqtt_mock, Platform.FAN, config, topics, object_id="tasmota"
)
async def test_entity_id_update_discovery_update(
hass: HomeAssistant, mqtt_mock: MqttMockHAClient, setup_tasmota
) -> None:
"""Test MQTT discovery update when entity_id is updated."""
config = copy.deepcopy(DEFAULT_CONFIG)
config["if"] = 1
await help_test_entity_id_update_discovery_update(
hass, mqtt_mock, Platform.FAN, config, object_id="tasmota"
)