"""Tests for home_connect button entities.""" from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import AsyncMock, MagicMock from aiohomeconnect.model import ArrayOfCommands, CommandKey, EventMessage from aiohomeconnect.model.command import Command from aiohomeconnect.model.error import HomeConnectApiError from aiohomeconnect.model.event import ArrayOfEvents, EventType import pytest from homeassistant.components.button import DOMAIN as BUTTON_DOMAIN, SERVICE_PRESS from homeassistant.components.home_connect.const import DOMAIN from homeassistant.config_entries import ConfigEntryState from homeassistant.const import ATTR_ENTITY_ID, STATE_UNAVAILABLE, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import device_registry as dr, entity_registry as er from tests.common import MockConfigEntry @pytest.fixture def platforms() -> list[str]: """Fixture to specify platforms to test.""" return [Platform.BUTTON] async def test_buttons( hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client: MagicMock, ) -> None: """Test button entities.""" assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client) assert config_entry.state == ConfigEntryState.LOADED async def test_paired_depaired_devices_flow( appliance_ha_id: str, hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client: MagicMock, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test that removed devices are correctly removed from and added to hass on API events.""" assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client) assert config_entry.state == ConfigEntryState.LOADED device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)}) assert device entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) assert entity_entries await client.add_events( [ EventMessage( appliance_ha_id, EventType.DEPAIRED, data=ArrayOfEvents([]), ) ] ) await hass.async_block_till_done() device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)}) assert not device for entity_entry in entity_entries: assert not entity_registry.async_get(entity_entry.entity_id) # Now that all everything related to the device is removed, pair it again await client.add_events( [ EventMessage( appliance_ha_id, EventType.PAIRED, data=ArrayOfEvents([]), ) ] ) await hass.async_block_till_done() assert device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)}) for entity_entry in entity_entries: assert entity_registry.async_get(entity_entry.entity_id) async def test_connected_devices( appliance_ha_id: str, hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client: MagicMock, device_registry: dr.DeviceRegistry, entity_registry: er.EntityRegistry, ) -> None: """Test that devices reconnected. Specifically those devices whose settings, status, etc. could not be obtained while disconnected and once connected, the entities are added. """ get_available_commands_original_mock = client.get_available_commands get_available_programs_mock = client.get_available_programs async def get_available_commands_side_effect(ha_id: str): if ha_id == appliance_ha_id: raise HomeConnectApiError( "SDK.Error.HomeAppliance.Connection.Initialization.Failed" ) return await get_available_commands_original_mock.side_effect(ha_id) async def get_available_programs_side_effect(ha_id: str): if ha_id == appliance_ha_id: raise HomeConnectApiError( "SDK.Error.HomeAppliance.Connection.Initialization.Failed" ) return await get_available_programs_mock.side_effect(ha_id) client.get_available_commands = AsyncMock( side_effect=get_available_commands_side_effect ) client.get_available_programs = AsyncMock( side_effect=get_available_programs_side_effect ) assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client) assert config_entry.state == ConfigEntryState.LOADED client.get_available_commands = get_available_commands_original_mock client.get_available_programs = get_available_programs_mock device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)}) assert device entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) await client.add_events( [ EventMessage( appliance_ha_id, EventType.CONNECTED, data=ArrayOfEvents([]), ) ] ) await hass.async_block_till_done() device = device_registry.async_get_device(identifiers={(DOMAIN, appliance_ha_id)}) assert device new_entity_entries = entity_registry.entities.get_entries_for_device_id(device.id) assert len(new_entity_entries) > len(entity_entries) async def test_button_entity_availabilty( hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client: MagicMock, appliance_ha_id: str, ) -> None: """Test if button entities availability are based on the appliance connection state.""" entity_ids = [ "button.washer_pause_program", "button.washer_stop_program", ] assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client) assert config_entry.state == ConfigEntryState.LOADED for entity_id in entity_ids: state = hass.states.get(entity_id) assert state assert state.state != STATE_UNAVAILABLE await client.add_events( [ EventMessage( appliance_ha_id, EventType.DISCONNECTED, ArrayOfEvents([]), ) ] ) await hass.async_block_till_done() for entity_id in entity_ids: assert hass.states.is_state(entity_id, STATE_UNAVAILABLE) await client.add_events( [ EventMessage( appliance_ha_id, EventType.CONNECTED, ArrayOfEvents([]), ) ] ) await hass.async_block_till_done() for entity_id in entity_ids: state = hass.states.get(entity_id) assert state assert state.state != STATE_UNAVAILABLE @pytest.mark.parametrize( ("entity_id", "method_call", "expected_kwargs"), [ ( "button.washer_pause_program", "put_command", {"command_key": CommandKey.BSH_COMMON_PAUSE_PROGRAM, "value": True}, ), ("button.washer_stop_program", "stop_program", {}), ], ) async def test_button_functionality( hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client: MagicMock, entity_id: str, method_call: str, expected_kwargs: dict[str, Any], appliance_ha_id: str, ) -> None: """Test if button entities availability are based on the appliance connection state.""" assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client) assert config_entry.state == ConfigEntryState.LOADED entity = hass.states.get(entity_id) assert entity assert entity.state != STATE_UNAVAILABLE await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True, ) getattr(client, method_call).assert_called_with(appliance_ha_id, **expected_kwargs) async def test_command_button_exception( hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client_with_exception: MagicMock, ) -> None: """Test if button entities availability are based on the appliance connection state.""" entity_id = "button.washer_pause_program" client_with_exception.get_available_commands = AsyncMock( return_value=ArrayOfCommands( [ Command( CommandKey.BSH_COMMON_PAUSE_PROGRAM, "Pause Program", ) ] ) ) assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client_with_exception) assert config_entry.state == ConfigEntryState.LOADED entity = hass.states.get(entity_id) assert entity assert entity.state != STATE_UNAVAILABLE with pytest.raises(HomeAssistantError, match=r"Error.*executing.*command"): await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True, ) async def test_stop_program_button_exception( hass: HomeAssistant, config_entry: MockConfigEntry, integration_setup: Callable[[MagicMock], Awaitable[bool]], setup_credentials: None, client_with_exception: MagicMock, ) -> None: """Test if button entities availability are based on the appliance connection state.""" entity_id = "button.washer_stop_program" assert config_entry.state == ConfigEntryState.NOT_LOADED assert await integration_setup(client_with_exception) assert config_entry.state == ConfigEntryState.LOADED entity = hass.states.get(entity_id) assert entity assert entity.state != STATE_UNAVAILABLE with pytest.raises(HomeAssistantError, match=r"Error.*stop.*program"): await hass.services.async_call( BUTTON_DOMAIN, SERVICE_PRESS, {ATTR_ENTITY_ID: entity_id}, blocking=True, )