"""The tests for the Netatmo climate platform.""" from datetime import timedelta from typing import Any from unittest.mock import AsyncMock, patch import pytest from syrupy.assertion import SnapshotAssertion from voluptuous.error import MultipleInvalid from homeassistant.components.climate import ( ATTR_HVAC_MODE, ATTR_PRESET_MODE, DOMAIN as CLIMATE_DOMAIN, PRESET_AWAY, PRESET_BOOST, SERVICE_SET_HVAC_MODE, SERVICE_SET_PRESET_MODE, SERVICE_SET_TEMPERATURE, SERVICE_TURN_OFF, SERVICE_TURN_ON, HVACMode, ) from homeassistant.components.netatmo.climate import PRESET_FROST_GUARD, PRESET_SCHEDULE from homeassistant.components.netatmo.const import ( ATTR_END_DATETIME, ATTR_SCHEDULE_NAME, ATTR_TARGET_TEMPERATURE, ATTR_TIME_PERIOD, DOMAIN, SERVICE_CLEAR_TEMPERATURE_SETTING, SERVICE_SET_PRESET_MODE_WITH_END_DATETIME, SERVICE_SET_SCHEDULE, SERVICE_SET_TEMPERATURE_WITH_END_DATETIME, SERVICE_SET_TEMPERATURE_WITH_TIME_PERIOD, ) from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_WEBHOOK_ID, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import ServiceValidationError from homeassistant.helpers import entity_registry as er from homeassistant.util import dt as dt_util from .common import ( fake_get_image, fake_post_request, selected_platforms, simulate_webhook, snapshot_platform_entities, ) from tests.common import MockConfigEntry async def test_entity( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock, snapshot: SnapshotAssertion, entity_registry: er.EntityRegistry, ) -> None: """Test entities.""" await snapshot_platform_entities( hass, config_entry, Platform.CLIMATE, entity_registry, snapshot, ) async def test_schedule_update_webhook_event( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test schedule update webhook event without schedule_id.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" # Save initial state initial_state = hass.states.get(climate_entity_livingroom) # Create a schedule update event without a schedule_id (the # event is sent when temperature sets of a schedule are changed) response = { "home_id": "91763b24c43d3e344f424e8b", "event_type": "schedule", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) # State should be unchanged assert hass.states.get(climate_entity_livingroom) == initial_state async def test_webhook_event_handling_thermostats( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service and webhook event handling with thermostats.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 12 # Test service setting the temperature await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_TEMPERATURE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TEMPERATURE: 21}, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat manual set point response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "manual", "therm_setpoint_temperature": 21, "therm_setpoint_end_time": 1612734552, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "manual", "event_type": "set_point", "temperature": 21, "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 21 # Test service setting the HVAC mode to "heat" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVACMode.HEAT}, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat mode change to "Max" response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "max", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 # Test service setting the HVAC mode to "off" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVACMode.OFF}, blocking=True, ) await hass.async_block_till_done() # Fake webhook turn thermostat off response = { "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "off", } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "off", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "off" # Test service setting the HVAC mode to "auto" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_HVAC_MODE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_HVAC_MODE: HVACMode.AUTO}, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat mode cancel set point response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "home", } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) async def test_service_preset_mode_frost_guard_thermostat( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service with frost guard preset for thermostats.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) # Test service setting the preset mode to "frost guard" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_FROST_GUARD, }, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat mode change to "Frost Guard" response = { "event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "hg"}, "mode": "hg", "previous_mode": "schedule", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "frost_guard" ) # Test service setting the preset mode to "frost guard" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_SCHEDULE, }, blocking=True, ) await hass.async_block_till_done() # Test webhook thermostat mode change to "Schedule" response = { "event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "schedule"}, "mode": "schedule", "previous_mode": "hg", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) async def test_service_preset_modes_thermostat( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service with preset modes for thermostats.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) # Test service setting the preset mode to "away" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY}, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat mode change to "Away" response = { "event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "away"}, "mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) # Test service setting the preset mode to "boost" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_BOOST}, blocking=True, ) await hass.async_block_till_done() # Test webhook thermostat mode change to "Max" response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "max", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 30 async def test_service_set_temperature_with_end_datetime( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service setting temperature with an end datetime.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" # Test service setting the temperature without an end datetime await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE_WITH_END_DATETIME, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TARGET_TEMPERATURE: 25, ATTR_END_DATETIME: "2023-11-17 12:23:00", }, blocking=True, ) await hass.async_block_till_done() # Test webhook room mode change to "manual" response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "manual", "therm_setpoint_temperature": 25, "therm_setpoint_end_time": 1612749189, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "manual", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 25 async def test_service_set_temperature_with_time_period( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service setting temperature with an end datetime.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" # Test service setting the temperature without an end datetime await hass.services.async_call( DOMAIN, SERVICE_SET_TEMPERATURE_WITH_TIME_PERIOD, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_TARGET_TEMPERATURE: 25, ATTR_TIME_PERIOD: "02:24:00", }, blocking=True, ) await hass.async_block_till_done() # Test webhook room mode change to "manual" response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "manual", "therm_setpoint_temperature": 25, "therm_setpoint_end_time": 1612749189, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "manual", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 25 async def test_service_clear_temperature_setting( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service clearing temperature setting.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert hass.states.get(climate_entity_livingroom).state == "auto" # Simulate a room thermostat change to manual boost response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "manual", "therm_setpoint_temperature": 25, "therm_setpoint_end_time": 1612749189, } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "manual", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "heat" assert hass.states.get(climate_entity_livingroom).attributes["temperature"] == 25 # Test service setting the temperature without an end datetime await hass.services.async_call( DOMAIN, SERVICE_CLEAR_TEMPERATURE_SETTING, {ATTR_ENTITY_ID: climate_entity_livingroom}, blocking=True, ) await hass.async_block_till_done() # Test webhook room mode change to "home" response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2746182631", "name": "Livingroom", "type": "livingroom", "therm_setpoint_mode": "home", } ], "modules": [ {"id": "12:34:56:00:01:ae", "name": "Livingroom", "type": "NATherm1"} ], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" async def test_webhook_event_handling_no_data( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service and webhook event handling with erroneous data.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() # Test webhook without home entry webhook_id = config_entry.data[CONF_WEBHOOK_ID] response = { "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) # Test webhook with different home id response = { "home_id": "3d3e344f491763b24c424e8b", "room_id": "2746182631", "home": { "id": "3d3e344f491763b24c424e8b", "name": "MYHOME", "country": "DE", "rooms": [], "modules": [], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) # Test webhook without room entries response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [], "modules": [], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) async def test_service_schedule_thermostats( hass: HomeAssistant, config_entry, caplog: pytest.LogCaptureFixture, netatmo_auth ) -> None: """Test service for selecting Netatmo schedule with thermostats.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" assert ( hass.states.get(climate_entity_livingroom).attributes.get( "selected_schedule_id" ) == "591b54a2764ff4d50d8b5795" ) # Test setting a valid schedule with patch("pyatmo.home.Home.async_switch_schedule") as mock_switch_schedule: await hass.services.async_call( "netatmo", SERVICE_SET_SCHEDULE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "Winter"}, blocking=True, ) await hass.async_block_till_done() mock_switch_schedule.assert_called_once_with( schedule_id="b1b54a2f45795764f59d50d8" ) # Fake backend response for valve being turned on response = { "event_type": "schedule", "schedule_id": "b1b54a2f45795764f59d50d8", "previous_schedule_id": "59d32176d183948b05ab4dce", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) assert ( hass.states.get(climate_entity_livingroom).attributes["selected_schedule"] == "Winter" ) assert ( hass.states.get(climate_entity_livingroom).attributes.get( "selected_schedule_id" ) == "b1b54a2f45795764f59d50d8" ) # Test setting an invalid schedule with patch("pyatmo.home.Home.async_switch_schedule") as mock_switch_home_schedule: await hass.services.async_call( "netatmo", SERVICE_SET_SCHEDULE, {ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_SCHEDULE_NAME: "summer"}, blocking=True, ) await hass.async_block_till_done() mock_switch_home_schedule.assert_not_called() assert "summer is not a valid schedule" in caplog.text async def test_service_preset_mode_with_end_time_thermostats( hass: HomeAssistant, config_entry, caplog: pytest.LogCaptureFixture, netatmo_auth ) -> None: """Test service for set preset mode with end datetime for Netatmo thermostats.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_livingroom = "climate.livingroom_livingroom" # Test setting a valid preset mode (that allow an end datetime # in Netatmo == THERM_MODES) and a valid end datetime await hass.services.async_call( "netatmo", SERVICE_SET_PRESET_MODE_WITH_END_DATETIME, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY, ATTR_END_DATETIME: (dt_util.now() + timedelta(days=10)).strftime( "%Y-%m-%d %H:%M:%S" ), }, blocking=True, ) await hass.async_block_till_done() # Fake webhook thermostat mode change to "Away" response = { "event_type": "therm_mode", "home": {"id": "91763b24c43d3e344f424e8b", "therm_mode": "away"}, "mode": "away", "previous_mode": "schedule", "push_type": "home_event_changed", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_livingroom).state == "auto" assert ( hass.states.get(climate_entity_livingroom).attributes["preset_mode"] == "away" ) # Test setting an invalid preset mode (not in THERM_MODES) and a valid end datetime with pytest.raises(MultipleInvalid): await hass.services.async_call( "netatmo", SERVICE_SET_PRESET_MODE_WITH_END_DATETIME, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_BOOST, ATTR_END_DATETIME: (dt_util.now() + timedelta(days=10)).strftime( "%Y-%m-%d %H:%M:%S" ), }, blocking=True, ) # Test setting a valid preset mode (that allow an end datetime # in Netatmo == THERM_MODES) without an end datetime with pytest.raises(MultipleInvalid): await hass.services.async_call( "netatmo", SERVICE_SET_PRESET_MODE_WITH_END_DATETIME, { ATTR_ENTITY_ID: climate_entity_livingroom, ATTR_PRESET_MODE: PRESET_AWAY, }, blocking=True, ) async def test_service_preset_mode_already_boost_valves( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service with boost preset for valves when already in boost mode.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" assert hass.states.get(climate_entity_entrada).state == "auto" assert ( hass.states.get(climate_entity_entrada).attributes["preset_mode"] == "frost_guard" ) assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 # Test webhook valve mode change to "Max" response = { "room_id": "2833524037", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189, } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "max", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) # Test service setting the preset mode to "boost" await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, blocking=True, ) await hass.async_block_till_done() # Test webhook valve mode change to "Max" response = { "room_id": "2833524037", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189, } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "max", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 async def test_service_preset_mode_boost_valves( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service with boost preset for valves.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" # Test service setting the preset mode to "boost" assert hass.states.get(climate_entity_entrada).state == "auto" assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 7 await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: climate_entity_entrada, ATTR_PRESET_MODE: PRESET_BOOST}, blocking=True, ) await hass.async_block_till_done() # Fake backend response response = { "room_id": "2833524037", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "max", "therm_setpoint_end_time": 1612749189, } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "max", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat" assert hass.states.get(climate_entity_entrada).attributes["temperature"] == 30 async def test_service_preset_mode_invalid( hass: HomeAssistant, config_entry, caplog: pytest.LogCaptureFixture, netatmo_auth ) -> None: """Test service with invalid preset.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() with pytest.raises(ServiceValidationError): await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_SET_PRESET_MODE, {ATTR_ENTITY_ID: "climate.cocina_cocina", ATTR_PRESET_MODE: "invalid"}, blocking=True, ) async def test_valves_service_turn_off( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service turn off for valves.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" assert hass.states.get(climate_entity_entrada).attributes["hvac_modes"] == [ "auto", "heat", ] # Test turning valve off await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: climate_entity_entrada}, blocking=True, ) await hass.async_block_till_done() # Fake backend response for valve being turned off response = { "room_id": "2833524037", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "off", } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "off", "event_type": "set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "off" async def test_valves_service_turn_on( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service turn on for valves.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" # Test turning valve on await hass.services.async_call( CLIMATE_DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: climate_entity_entrada}, blocking=True, ) await hass.async_block_till_done() # Fake backend response for valve being turned on response = { "room_id": "2833524037", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "home", } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "auto" async def test_webhook_home_id_mismatch( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service turn on for valves.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" assert hass.states.get(climate_entity_entrada).state == "auto" # Fake backend response for valve being turned on response = { "room_id": "2833524037", "home": { "id": "123", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "home", } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "home", "event_type": "cancel_set_point", "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "auto" async def test_thermostat_update_with_none_therm_setpoint_mode( hass: HomeAssistant, config_entry: MockConfigEntry ) -> None: """Test thermostat setup when Netatmo returns no therm setpoint mode.""" def set_none_therm_setpoint_mode(payload: dict[str, Any]) -> None: """Set the thermostat setpoint mode to None in the backend response.""" home = payload.get("body", {}).get("home") if home is None: return for room in home.get("rooms", []): if room["id"] == "2746182631": room["therm_setpoint_mode"] = None async def fake_post(*args: Any, **kwargs: Any): """Return backend data with a missing thermostat setpoint mode.""" kwargs["msg_callback"] = set_none_therm_setpoint_mode return await fake_post_request(hass, *args, **kwargs) with ( selected_platforms([Platform.CLIMATE]), patch( "homeassistant.components.netatmo.api.AsyncConfigEntryNetatmoAuth" ) as mock_auth, ): mock_auth.return_value.async_post_request.side_effect = fake_post mock_auth.return_value.async_post_api_request.side_effect = fake_post mock_auth.return_value.async_get_image.side_effect = fake_get_image mock_auth.return_value.async_addwebhook.side_effect = AsyncMock() mock_auth.return_value.async_dropwebhook.side_effect = AsyncMock() assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() state = hass.states.get("climate.livingroom_livingroom") assert state is not None assert state.state == HVACMode.AUTO assert state.attributes["preset_mode"] == PRESET_SCHEDULE async def test_webhook_set_point( hass: HomeAssistant, config_entry: MockConfigEntry, netatmo_auth: AsyncMock ) -> None: """Test service turn on for valves.""" with selected_platforms([Platform.CLIMATE]): assert await hass.config_entries.async_setup(config_entry.entry_id) await hass.async_block_till_done() webhook_id = config_entry.data[CONF_WEBHOOK_ID] climate_entity_entrada = "climate.entrada_entrada" # Fake backend response for valve being turned on response = { "room_id": "2746182631", "home": { "id": "91763b24c43d3e344f424e8b", "name": "MYHOME", "country": "DE", "rooms": [ { "id": "2833524037", "name": "Entrada", "type": "lobby", "therm_setpoint_mode": "home", "therm_setpoint_temperature": 30, } ], "modules": [{"id": "12:34:56:00:01:ae", "name": "Entrada", "type": "NRV"}], }, "mode": "home", "event_type": "set_point", "temperature": 21, "push_type": "display_change", } await simulate_webhook(hass, webhook_id, response) assert hass.states.get(climate_entity_entrada).state == "heat"