From 98adeb607008a17ecff81d31b143c47fb8fecfeb Mon Sep 17 00:00:00 2001
From: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Date: Mon, 7 Mar 2022 15:38:33 +0100
Subject: [PATCH] Fix false positive MQTT climate deprecation warnings for
 defaults (#67661)

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
---
 homeassistant/components/mqtt/climate.py |  32 +++++--
 tests/components/mqtt/test_climate.py    | 113 +++++++++++++++++++++++
 2 files changed, 136 insertions(+), 9 deletions(-)

diff --git a/homeassistant/components/mqtt/climate.py b/homeassistant/components/mqtt/climate.py
index e145edde7d7..94320cc5def 100644
--- a/homeassistant/components/mqtt/climate.py
+++ b/homeassistant/components/mqtt/climate.py
@@ -271,7 +271,7 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend(
         vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic,
         vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template,
         vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic,
-        vol.Optional(CONF_HOLD_LIST, default=list): cv.ensure_list,
+        vol.Optional(CONF_HOLD_LIST): cv.ensure_list,
         vol.Optional(CONF_MODE_COMMAND_TEMPLATE): cv.template,
         vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic,
         vol.Optional(
@@ -298,7 +298,7 @@ _PLATFORM_SCHEMA_BASE = SCHEMA_BASE.extend(
         ),
         vol.Optional(CONF_RETAIN, default=mqtt.DEFAULT_RETAIN): cv.boolean,
         # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
-        vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean,
+        vol.Optional(CONF_SEND_IF_OFF): cv.boolean,
         vol.Optional(CONF_ACTION_TEMPLATE): cv.template,
         vol.Optional(CONF_ACTION_TOPIC): mqtt.valid_subscribe_topic,
         # CONF_PRESET_MODE_COMMAND_TOPIC and CONF_PRESET_MODES_LIST must be used together
@@ -431,6 +431,12 @@ class MqttClimate(MqttEntity, ClimateEntity):
         self._feature_preset_mode = False
         self._optimistic_preset_mode = None
 
+        # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
+        self._send_if_off = True
+        # AWAY and HOLD mode topics and templates are deprecated,
+        # support will be removed with release 2022.9
+        self._hold_list = []
+
         MqttEntity.__init__(self, hass, config, config_entry, discovery_data)
 
     @staticmethod
@@ -499,6 +505,15 @@ class MqttClimate(MqttEntity, ClimateEntity):
 
         self._command_templates = command_templates
 
+        # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
+        if CONF_SEND_IF_OFF in config:
+            self._send_if_off = config[CONF_SEND_IF_OFF]
+
+        # AWAY and HOLD mode topics and templates are deprecated,
+        # support will be removed with release 2022.9
+        if CONF_HOLD_LIST in config:
+            self._hold_list = config[CONF_HOLD_LIST]
+
     def _prepare_subscribe_topics(self):  # noqa: C901
         """(Re)Subscribe to topics."""
         topics = {}
@@ -806,7 +821,9 @@ class MqttClimate(MqttEntity, ClimateEntity):
         ):
             presets.append(PRESET_AWAY)
 
-        presets.extend(self._config[CONF_HOLD_LIST])
+        # AWAY and HOLD mode topics and templates are deprecated,
+        # support will be removed with release 2022.9
+        presets.extend(self._hold_list)
 
         if presets:
             presets.insert(0, PRESET_NONE)
@@ -847,10 +864,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
                 setattr(self, attr, temp)
 
             # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
-            if (
-                self._config[CONF_SEND_IF_OFF]
-                or self._current_operation != HVAC_MODE_OFF
-            ):
+            if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
                 payload = self._command_templates[cmnd_template](temp)
                 await self._publish(cmnd_topic, payload)
 
@@ -890,7 +904,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
     async def async_set_swing_mode(self, swing_mode):
         """Set new swing mode."""
         # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
-        if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF:
+        if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
             payload = self._command_templates[CONF_SWING_MODE_COMMAND_TEMPLATE](
                 swing_mode
             )
@@ -903,7 +917,7 @@ class MqttClimate(MqttEntity, ClimateEntity):
     async def async_set_fan_mode(self, fan_mode):
         """Set new target temperature."""
         # CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
-        if self._config[CONF_SEND_IF_OFF] or self._current_operation != HVAC_MODE_OFF:
+        if self._send_if_off or self._current_operation != HVAC_MODE_OFF:
             payload = self._command_templates[CONF_FAN_MODE_COMMAND_TEMPLATE](fan_mode)
             await self._publish(CONF_FAN_MODE_COMMAND_TOPIC, payload)
 
diff --git a/tests/components/mqtt/test_climate.py b/tests/components/mqtt/test_climate.py
index c3501267e12..93249e76875 100644
--- a/tests/components/mqtt/test_climate.py
+++ b/tests/components/mqtt/test_climate.py
@@ -333,6 +333,43 @@ async def test_set_fan_mode(hass, mqtt_mock):
     assert state.attributes.get("fan_mode") == "high"
 
 
+# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
+@pytest.mark.parametrize(
+    "send_if_off,assert_async_publish",
+    [
+        ({}, [call("fan-mode-topic", "low", 0, False)]),
+        ({"send_if_off": True}, [call("fan-mode-topic", "low", 0, False)]),
+        ({"send_if_off": False}, []),
+    ],
+)
+async def test_set_fan_mode_send_if_off(
+    hass, mqtt_mock, send_if_off, assert_async_publish
+):
+    """Test setting of fan mode if the hvac is off."""
+    config = copy.deepcopy(DEFAULT_CONFIG)
+    config[CLIMATE_DOMAIN].update(send_if_off)
+    assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
+    await hass.async_block_till_done()
+    assert hass.states.get(ENTITY_CLIMATE) is not None
+
+    # Turn on HVAC
+    await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.reset_mock()
+    # Updates for fan_mode should be sent when the device is turned on
+    await common.async_set_fan_mode(hass, "high", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_called_once_with("fan-mode-topic", "high", 0, False)
+
+    # Turn off HVAC
+    await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
+    state = hass.states.get(ENTITY_CLIMATE)
+    assert state.state == "off"
+
+    # Updates for fan_mode should be sent if SEND_IF_OFF is not set or is True
+    mqtt_mock.async_publish.reset_mock()
+    await common.async_set_fan_mode(hass, "low", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
+
+
 async def test_set_swing_mode_bad_attr(hass, mqtt_mock, caplog):
     """Test setting swing mode without required attribute."""
     assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
@@ -385,6 +422,43 @@ async def test_set_swing(hass, mqtt_mock):
     assert state.attributes.get("swing_mode") == "on"
 
 
+# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
+@pytest.mark.parametrize(
+    "send_if_off,assert_async_publish",
+    [
+        ({}, [call("swing-mode-topic", "on", 0, False)]),
+        ({"send_if_off": True}, [call("swing-mode-topic", "on", 0, False)]),
+        ({"send_if_off": False}, []),
+    ],
+)
+async def test_set_swing_mode_send_if_off(
+    hass, mqtt_mock, send_if_off, assert_async_publish
+):
+    """Test setting of swing mode if the hvac is off."""
+    config = copy.deepcopy(DEFAULT_CONFIG)
+    config[CLIMATE_DOMAIN].update(send_if_off)
+    assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
+    await hass.async_block_till_done()
+    assert hass.states.get(ENTITY_CLIMATE) is not None
+
+    # Turn on HVAC
+    await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.reset_mock()
+    # Updates for swing_mode should be sent when the device is turned on
+    await common.async_set_swing_mode(hass, "off", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_called_once_with("swing-mode-topic", "off", 0, False)
+
+    # Turn off HVAC
+    await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
+    state = hass.states.get(ENTITY_CLIMATE)
+    assert state.state == "off"
+
+    # Updates for swing_mode should be sent if SEND_IF_OFF is not set or is True
+    mqtt_mock.async_publish.reset_mock()
+    await common.async_set_swing_mode(hass, "on", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
+
+
 async def test_set_target_temperature(hass, mqtt_mock):
     """Test setting the target temperature."""
     assert await async_setup_component(hass, CLIMATE_DOMAIN, DEFAULT_CONFIG)
@@ -421,6 +495,45 @@ async def test_set_target_temperature(hass, mqtt_mock):
     mqtt_mock.async_publish.reset_mock()
 
 
+# CONF_SEND_IF_OFF is deprecated, support will be removed with release 2022.9
+@pytest.mark.parametrize(
+    "send_if_off,assert_async_publish",
+    [
+        ({}, [call("temperature-topic", "21.0", 0, False)]),
+        ({"send_if_off": True}, [call("temperature-topic", "21.0", 0, False)]),
+        ({"send_if_off": False}, []),
+    ],
+)
+async def test_set_target_temperature_send_if_off(
+    hass, mqtt_mock, send_if_off, assert_async_publish
+):
+    """Test setting of target temperature if the hvac is off."""
+    config = copy.deepcopy(DEFAULT_CONFIG)
+    config[CLIMATE_DOMAIN].update(send_if_off)
+    assert await async_setup_component(hass, CLIMATE_DOMAIN, config)
+    await hass.async_block_till_done()
+    assert hass.states.get(ENTITY_CLIMATE) is not None
+
+    # Turn on HVAC
+    await common.async_set_hvac_mode(hass, "cool", ENTITY_CLIMATE)
+    mqtt_mock.async_publish.reset_mock()
+    # Updates for target temperature should be sent when the device is turned on
+    await common.async_set_temperature(hass, 16.0, ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_called_once_with(
+        "temperature-topic", "16.0", 0, False
+    )
+
+    # Turn off HVAC
+    await common.async_set_hvac_mode(hass, "off", ENTITY_CLIMATE)
+    state = hass.states.get(ENTITY_CLIMATE)
+    assert state.state == "off"
+
+    # Updates for target temperature sent should be if SEND_IF_OFF is not set or is True
+    mqtt_mock.async_publish.reset_mock()
+    await common.async_set_temperature(hass, 21.0, ENTITY_CLIMATE)
+    mqtt_mock.async_publish.assert_has_calls(assert_async_publish)
+
+
 async def test_set_target_temperature_pessimistic(hass, mqtt_mock):
     """Test setting the target temperature."""
     config = copy.deepcopy(DEFAULT_CONFIG)
-- 
GitLab