From 2def33b1687bfa8efeffb7181143b8b22e373c13 Mon Sep 17 00:00:00 2001 From: Robert Contreras <beastie29a@users.noreply.github.com> Date: Sun, 7 Jul 2024 09:30:59 -0700 Subject: [PATCH] Home Connect unit tests for switch platform (#115456) --- .../fixtures/programs-available.json | 2 +- .../home_connect/fixtures/settings.json | 16 ++ tests/components/home_connect/test_init.py | 67 +++--- tests/components/home_connect/test_switch.py | 217 ++++++++++++++++++ 4 files changed, 265 insertions(+), 37 deletions(-) create mode 100644 tests/components/home_connect/test_switch.py diff --git a/tests/components/home_connect/fixtures/programs-available.json b/tests/components/home_connect/fixtures/programs-available.json index b99ee5c6add..bba1a5d2721 100644 --- a/tests/components/home_connect/fixtures/programs-available.json +++ b/tests/components/home_connect/fixtures/programs-available.json @@ -26,7 +26,7 @@ ] } }, - "DishWasher": { + "Dishwasher": { "data": { "programs": [ { diff --git a/tests/components/home_connect/fixtures/settings.json b/tests/components/home_connect/fixtures/settings.json index 5dc0f0e0599..eb6a5f5ff98 100644 --- a/tests/components/home_connect/fixtures/settings.json +++ b/tests/components/home_connect/fixtures/settings.json @@ -95,5 +95,21 @@ } ] } + }, + "Washer": { + "data": { + "settings": [ + { + "key": "BSH.Common.Setting.PowerState", + "value": "BSH.Common.EnumType.PowerState.On", + "type": "BSH.Common.EnumType.PowerState" + }, + { + "key": "BSH.Common.Setting.ChildLock", + "value": false, + "type": "Boolean" + } + ] + } } } diff --git a/tests/components/home_connect/test_init.py b/tests/components/home_connect/test_init.py index f9b1d5c543e..02d9bcaa208 100644 --- a/tests/components/home_connect/test_init.py +++ b/tests/components/home_connect/test_init.py @@ -4,13 +4,13 @@ from collections.abc import Awaitable, Callable from typing import Any from unittest.mock import MagicMock, Mock +from freezegun.api import FrozenDateTimeFactory import pytest from requests import HTTPError import requests_mock from homeassistant.components.home_connect.const import DOMAIN, OAUTH2_TOKEN from homeassistant.config_entries import ConfigEntryState -from homeassistant.const import Platform from homeassistant.core import HomeAssistant from homeassistant.helpers import device_registry as dr @@ -137,6 +137,36 @@ async def test_api_setup( assert config_entry.state == ConfigEntryState.NOT_LOADED +async def test_update_throttle( + appliance: Mock, + freezer: FrozenDateTimeFactory, + hass: HomeAssistant, + config_entry: MockConfigEntry, + integration_setup: Callable[[], Awaitable[bool]], + setup_credentials: None, + get_appliances: MagicMock, +) -> None: + """Test to check Throttle functionality.""" + assert config_entry.state == ConfigEntryState.NOT_LOADED + assert await integration_setup() + assert config_entry.state == ConfigEntryState.LOADED + get_appliances_call_count = get_appliances.call_count + + # First re-load after 1 minute is not blocked. + assert await hass.config_entries.async_unload(config_entry.entry_id) + assert config_entry.state == ConfigEntryState.NOT_LOADED + freezer.tick(60) + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert get_appliances.call_count == get_appliances_call_count + 1 + + # Second re-load is blocked by Throttle. + assert await hass.config_entries.async_unload(config_entry.entry_id) + assert config_entry.state == ConfigEntryState.NOT_LOADED + freezer.tick(59) + assert await hass.config_entries.async_setup(config_entry.entry_id) + assert get_appliances.call_count == get_appliances_call_count + 1 + + @pytest.mark.usefixtures("bypass_throttle") async def test_exception_handling( integration_setup: Callable[[], Awaitable[bool]], @@ -191,41 +221,6 @@ async def test_token_refresh_success( ) -async def test_setup( - hass: HomeAssistant, - integration_setup: Callable[[], Awaitable[bool]], - config_entry: MockConfigEntry, - setup_credentials: None, -) -> None: - """Test setting up the integration.""" - assert config_entry.state == ConfigEntryState.NOT_LOADED - - assert await integration_setup() - assert config_entry.state == ConfigEntryState.LOADED - - assert await hass.config_entries.async_unload(config_entry.entry_id) - await hass.async_block_till_done() - - assert config_entry.state == ConfigEntryState.NOT_LOADED - - -async def test_update_throttle( - appliance: Mock, - hass: HomeAssistant, - config_entry: MockConfigEntry, - integration_setup: Callable[[], Awaitable[bool]], - setup_credentials: None, - platforms: list[Platform], - get_appliances: MagicMock, -) -> None: - """Test to check Throttle functionality.""" - assert config_entry.state == ConfigEntryState.NOT_LOADED - - assert await integration_setup() - assert config_entry.state == ConfigEntryState.LOADED - assert get_appliances.call_count == 0 - - @pytest.mark.usefixtures("bypass_throttle") async def test_http_error( config_entry: MockConfigEntry, diff --git a/tests/components/home_connect/test_switch.py b/tests/components/home_connect/test_switch.py new file mode 100644 index 00000000000..b23fbc13c21 --- /dev/null +++ b/tests/components/home_connect/test_switch.py @@ -0,0 +1,217 @@ +"""Tests for home_connect sensor entities.""" + +from collections.abc import Awaitable, Callable, Generator +from typing import Any +from unittest.mock import MagicMock, Mock + +from homeconnect.api import HomeConnectError +import pytest + +from homeassistant.components.home_connect.const import ( + BSH_ACTIVE_PROGRAM, + BSH_CHILD_LOCK_STATE, + BSH_OPERATION_STATE, + BSH_POWER_OFF, + BSH_POWER_ON, + BSH_POWER_STATE, +) +from homeassistant.components.switch import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + Platform, +) +from homeassistant.core import HomeAssistant + +from .conftest import get_all_appliances + +from tests.common import MockConfigEntry, load_json_object_fixture + +SETTINGS_STATUS = { + setting.pop("key"): setting + for setting in load_json_object_fixture("home_connect/settings.json") + .get("Washer") + .get("data") + .get("settings") +} + +PROGRAM = "LaundryCare.Dryer.Program.Mix" + + +@pytest.fixture +def platforms() -> list[str]: + """Fixture to specify platforms to test.""" + return [Platform.SWITCH] + + +async def test_switches( + bypass_throttle: Generator[None, Any, None], + hass: HomeAssistant, + config_entry: MockConfigEntry, + integration_setup: Callable[[], Awaitable[bool]], + setup_credentials: None, + get_appliances: Mock, +) -> None: + """Test switch entities.""" + get_appliances.side_effect = get_all_appliances + assert config_entry.state == ConfigEntryState.NOT_LOADED + assert await integration_setup() + assert config_entry.state == ConfigEntryState.LOADED + + +@pytest.mark.parametrize( + ("entity_id", "status", "service", "state"), + [ + ( + "switch.washer_program_mix", + {BSH_ACTIVE_PROGRAM: {"value": PROGRAM}}, + SERVICE_TURN_ON, + STATE_ON, + ), + ( + "switch.washer_program_mix", + {BSH_ACTIVE_PROGRAM: {"value": ""}}, + SERVICE_TURN_OFF, + STATE_OFF, + ), + ( + "switch.washer_power", + {BSH_POWER_STATE: {"value": BSH_POWER_ON}}, + SERVICE_TURN_ON, + STATE_ON, + ), + ( + "switch.washer_power", + {BSH_POWER_STATE: {"value": BSH_POWER_OFF}}, + SERVICE_TURN_OFF, + STATE_OFF, + ), + ( + "switch.washer_power", + { + BSH_POWER_STATE: {"value": ""}, + BSH_OPERATION_STATE: { + "value": "BSH.Common.EnumType.OperationState.Inactive" + }, + }, + SERVICE_TURN_OFF, + STATE_OFF, + ), + ( + "switch.washer_childlock", + {BSH_CHILD_LOCK_STATE: {"value": True}}, + SERVICE_TURN_ON, + STATE_ON, + ), + ( + "switch.washer_childlock", + {BSH_CHILD_LOCK_STATE: {"value": False}}, + SERVICE_TURN_OFF, + STATE_OFF, + ), + ], +) +async def test_switch_functionality( + entity_id: str, + status: dict, + service: str, + state: str, + bypass_throttle: Generator[None, Any, None], + hass: HomeAssistant, + config_entry: MockConfigEntry, + integration_setup: Callable[[], Awaitable[bool]], + setup_credentials: None, + appliance: Mock, + get_appliances: MagicMock, +) -> None: + """Test switch functionality.""" + appliance.status.update(SETTINGS_STATUS) + appliance.get_programs_available.return_value = [PROGRAM] + get_appliances.return_value = [appliance] + + assert config_entry.state == ConfigEntryState.NOT_LOADED + assert await integration_setup() + assert config_entry.state == ConfigEntryState.LOADED + + appliance.status.update(status) + await hass.services.async_call( + DOMAIN, service, {"entity_id": entity_id}, blocking=True + ) + assert hass.states.is_state(entity_id, state) + + +@pytest.mark.parametrize( + ("entity_id", "status", "service", "mock_attr"), + [ + ( + "switch.washer_program_mix", + {BSH_ACTIVE_PROGRAM: {"value": PROGRAM}}, + SERVICE_TURN_ON, + "start_program", + ), + ( + "switch.washer_program_mix", + {BSH_ACTIVE_PROGRAM: {"value": PROGRAM}}, + SERVICE_TURN_OFF, + "stop_program", + ), + ( + "switch.washer_power", + {BSH_POWER_STATE: {"value": ""}}, + SERVICE_TURN_ON, + "set_setting", + ), + ( + "switch.washer_power", + {BSH_POWER_STATE: {"value": ""}}, + SERVICE_TURN_OFF, + "set_setting", + ), + ( + "switch.washer_childlock", + {BSH_CHILD_LOCK_STATE: {"value": ""}}, + SERVICE_TURN_ON, + "set_setting", + ), + ( + "switch.washer_childlock", + {BSH_CHILD_LOCK_STATE: {"value": ""}}, + SERVICE_TURN_OFF, + "set_setting", + ), + ], +) +async def test_switch_exception_handling( + entity_id: str, + status: dict, + service: str, + mock_attr: str, + bypass_throttle: Generator[None, Any, None], + hass: HomeAssistant, + integration_setup: Callable[[], Awaitable[bool]], + config_entry: MockConfigEntry, + setup_credentials: None, + problematic_appliance: Mock, + get_appliances: MagicMock, +) -> None: + """Test exception handling.""" + problematic_appliance.get_programs_available.side_effect = None + problematic_appliance.get_programs_available.return_value = [PROGRAM] + get_appliances.return_value = [problematic_appliance] + + assert config_entry.state == ConfigEntryState.NOT_LOADED + assert await integration_setup() + assert config_entry.state == ConfigEntryState.LOADED + + # Assert that an exception is called. + with pytest.raises(HomeConnectError): + getattr(problematic_appliance, mock_attr)() + + problematic_appliance.status.update(status) + await hass.services.async_call( + DOMAIN, service, {"entity_id": entity_id}, blocking=True + ) + assert getattr(problematic_appliance, mock_attr).call_count == 2 -- GitLab