"""Tests for switch platform.""" from datetime import timedelta from unittest.mock import AsyncMock, patch import zoneinfo from aioautomower.exceptions import ApiError from aioautomower.model import MowerAttributes, MowerModes, Zone from aioautomower.utils import mower_list_to_dictionary_dataclass from freezegun.api import FrozenDateTimeFactory import pytest from syrupy import SnapshotAssertion from homeassistant.components.husqvarna_automower.const import ( DOMAIN, EXECUTION_TIME_DELAY, ) from homeassistant.components.husqvarna_automower.coordinator import SCAN_INTERVAL from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, Platform, ) from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import entity_registry as er from . import setup_integration from .const import TEST_MOWER_ID from tests.common import ( MockConfigEntry, async_fire_time_changed, load_json_value_fixture, snapshot_platform, ) TEST_AREA_ID = 0 TEST_VARIABLE_ZONE_ID = "203F6359-AB56-4D57-A6DC-703095BB695D" TEST_ZONE_ID = "AAAAAAAA-BBBB-CCCC-DDDD-123456789101" async def test_switch_states( hass: HomeAssistant, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, freezer: FrozenDateTimeFactory, values: dict[str, MowerAttributes], ) -> None: """Test switch state.""" await setup_integration(hass, mock_config_entry) for mode, expected_state in ( (MowerModes.HOME, "off"), (MowerModes.MAIN_AREA, "on"), ): values[TEST_MOWER_ID].mower.mode = mode mock_automower_client.get_status.return_value = values freezer.tick(SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() state = hass.states.get("switch.test_mower_1_enable_schedule") assert state.state == expected_state @pytest.mark.parametrize( ("service", "aioautomower_command"), [ (SERVICE_TURN_OFF, "park_until_further_notice"), (SERVICE_TURN_ON, "resume_schedule"), (SERVICE_TOGGLE, "park_until_further_notice"), ], ) async def test_switch_commands( hass: HomeAssistant, aioautomower_command: str, service: str, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, ) -> None: """Test switch commands.""" await setup_integration(hass, mock_config_entry) await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: "switch.test_mower_1_enable_schedule"}, blocking=True, ) mocked_method = getattr(mock_automower_client.commands, aioautomower_command) mocked_method.assert_called_once_with(TEST_MOWER_ID) mocked_method.side_effect = ApiError("Test error") with pytest.raises( HomeAssistantError, match="Failed to send command: Test error", ): await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: "switch.test_mower_1_enable_schedule"}, blocking=True, ) assert len(mocked_method.mock_calls) == 2 @pytest.mark.parametrize( ("service", "boolean", "excepted_state"), [ (SERVICE_TURN_OFF, False, "off"), (SERVICE_TURN_ON, True, "on"), (SERVICE_TOGGLE, True, "on"), ], ) async def test_stay_out_zone_switch_commands( hass: HomeAssistant, service: str, boolean: bool, excepted_state: str, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, freezer: FrozenDateTimeFactory, mower_time_zone: zoneinfo.ZoneInfo, ) -> None: """Test switch commands.""" entity_id = "switch.test_mower_1_avoid_danger_zone" await setup_integration(hass, mock_config_entry) values = mower_list_to_dictionary_dataclass( load_json_value_fixture("mower.json", DOMAIN), mower_time_zone, ) values[TEST_MOWER_ID].stay_out_zones.zones[TEST_ZONE_ID].enabled = boolean mock_automower_client.get_status.return_value = values mocked_method = AsyncMock() setattr(mock_automower_client.commands, "switch_stay_out_zone", mocked_method) await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: entity_id}, blocking=False, ) freezer.tick(timedelta(seconds=EXECUTION_TIME_DELAY)) async_fire_time_changed(hass) await hass.async_block_till_done() mocked_method.assert_called_once_with(TEST_MOWER_ID, TEST_ZONE_ID, switch=boolean) state = hass.states.get(entity_id) assert state is not None assert state.state == excepted_state mocked_method.side_effect = ApiError("Test error") with pytest.raises( HomeAssistantError, match="Failed to send command: Test error", ): await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: entity_id}, blocking=True, ) assert len(mocked_method.mock_calls) == 2 @pytest.mark.parametrize( ("service", "boolean", "excepted_state"), [ (SERVICE_TURN_OFF, False, "off"), (SERVICE_TURN_ON, True, "on"), (SERVICE_TOGGLE, True, "on"), ], ) async def test_work_area_switch_commands( hass: HomeAssistant, service: str, boolean: bool, excepted_state: str, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, freezer: FrozenDateTimeFactory, mower_time_zone: zoneinfo.ZoneInfo, values: dict[str, MowerAttributes], ) -> None: """Test switch commands.""" entity_id = "switch.test_mower_1_my_lawn" await setup_integration(hass, mock_config_entry) values = mower_list_to_dictionary_dataclass( load_json_value_fixture("mower.json", DOMAIN), mower_time_zone, ) values[TEST_MOWER_ID].work_areas[TEST_AREA_ID].enabled = boolean mock_automower_client.get_status.return_value = values mocked_method = AsyncMock() setattr(mock_automower_client.commands, "workarea_settings", mocked_method) await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: entity_id}, blocking=False, ) freezer.tick(timedelta(seconds=EXECUTION_TIME_DELAY)) async_fire_time_changed(hass) await hass.async_block_till_done() mocked_method.assert_called_once_with(TEST_MOWER_ID, TEST_AREA_ID, enabled=boolean) state = hass.states.get(entity_id) assert state is not None assert state.state == excepted_state mocked_method.side_effect = ApiError("Test error") with pytest.raises( HomeAssistantError, match="Failed to send command: Test error", ): await hass.services.async_call( domain=SWITCH_DOMAIN, service=service, service_data={ATTR_ENTITY_ID: entity_id}, blocking=True, ) assert len(mocked_method.mock_calls) == 2 async def test_add_stay_out_zone( hass: HomeAssistant, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, freezer: FrozenDateTimeFactory, entity_registry: er.EntityRegistry, values: dict[str, MowerAttributes], ) -> None: """Test adding a stay out zone in runtime.""" await setup_integration(hass, mock_config_entry) entry = hass.config_entries.async_entries(DOMAIN)[0] current_entites = len( er.async_entries_for_config_entry(entity_registry, entry.entry_id) ) values[TEST_MOWER_ID].stay_out_zones.zones.update( { TEST_VARIABLE_ZONE_ID: Zone( name="future_zone", enabled=True, ) } ) mock_automower_client.get_status.return_value = values freezer.tick(SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() current_entites_after_addition = len( er.async_entries_for_config_entry(entity_registry, entry.entry_id) ) assert current_entites_after_addition == current_entites + 1 values[TEST_MOWER_ID].stay_out_zones.zones.pop(TEST_VARIABLE_ZONE_ID) values[TEST_MOWER_ID].stay_out_zones.zones.pop(TEST_ZONE_ID) mock_automower_client.get_status.return_value = values freezer.tick(SCAN_INTERVAL) async_fire_time_changed(hass) await hass.async_block_till_done() current_entites_after_deletion = len( er.async_entries_for_config_entry(entity_registry, entry.entry_id) ) assert current_entites_after_deletion == current_entites - 1 async def test_switch_snapshot( hass: HomeAssistant, entity_registry: er.EntityRegistry, mock_automower_client: AsyncMock, mock_config_entry: MockConfigEntry, snapshot: SnapshotAssertion, ) -> None: """Snapshot tests of the switches.""" with patch( "homeassistant.components.husqvarna_automower.PLATFORMS", [Platform.SWITCH], ): await setup_integration(hass, mock_config_entry) await snapshot_platform( hass, entity_registry, snapshot, mock_config_entry.entry_id )