From 3ea694a0693a360539e88ec34c75d324a300a5ef Mon Sep 17 00:00:00 2001 From: Robert Contreras Date: Mon, 8 Jul 2024 23:31:41 -0700 Subject: [PATCH] Home Connect unit tests for light platform (#121576) --- tests/components/home_connect/conftest.py | 9 +- tests/components/home_connect/test_light.py | 299 ++++++++++++++++++++ 2 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 tests/components/home_connect/test_light.py diff --git a/tests/components/home_connect/conftest.py b/tests/components/home_connect/conftest.py index 4a92545ff2f..c8137a044a1 100644 --- a/tests/components/home_connect/conftest.py +++ b/tests/components/home_connect/conftest.py @@ -152,15 +152,18 @@ def mock_appliance(request: pytest.FixtureRequest) -> MagicMock: @pytest.fixture(name="problematic_appliance") -def mock_problematic_appliance() -> Mock: +def mock_problematic_appliance(request: pytest.FixtureRequest) -> Mock: """Fixture to mock a problematic Appliance.""" app = "Washer" + if hasattr(request, "param") and request.param: + app = request.param + mock = Mock( - spec=HomeConnectAppliance, + autospec=HomeConnectAppliance, **MOCK_APPLIANCES_PROPERTIES.get(app), ) mock.name = app - setattr(mock, "status", {}) + type(mock).status = PropertyMock(return_value={}) mock.get_programs_active.side_effect = HomeConnectError mock.get_programs_available.side_effect = HomeConnectError mock.start_program.side_effect = HomeConnectError diff --git a/tests/components/home_connect/test_light.py b/tests/components/home_connect/test_light.py new file mode 100644 index 00000000000..a90eeee74da --- /dev/null +++ b/tests/components/home_connect/test_light.py @@ -0,0 +1,299 @@ +"""Tests for home_connect light 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_AMBIENT_LIGHT_BRIGHTNESS, + BSH_AMBIENT_LIGHT_CUSTOM_COLOR, + BSH_AMBIENT_LIGHT_ENABLED, + COOKING_LIGHTING, + COOKING_LIGHTING_BRIGHTNESS, +) +from homeassistant.components.light import DOMAIN +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import ( + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, + Platform, +) +from homeassistant.core import HomeAssistant + +from .conftest import get_all_appliances + +from tests.common import MockConfigEntry, load_json_object_fixture + +TEST_HC_APP = "Hood" + +SETTINGS_STATUS = { + setting.pop("key"): setting + for setting in load_json_object_fixture("home_connect/settings.json") + .get(TEST_HC_APP) + .get("data") + .get("settings") +} + + +@pytest.fixture +def platforms() -> list[str]: + """Fixture to specify platforms to test.""" + return [Platform.LIGHT] + + +async def test_light( + 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", "service_data", "state", "appliance"), + [ + ( + "light.hood_light", + { + COOKING_LIGHTING: { + "value": True, + }, + }, + SERVICE_TURN_ON, + {}, + STATE_ON, + "Hood", + ), + ( + "light.hood_light", + { + COOKING_LIGHTING: { + "value": True, + }, + COOKING_LIGHTING_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_ON, + {"brightness": 200}, + STATE_ON, + "Hood", + ), + ( + "light.hood_light", + { + COOKING_LIGHTING: {"value": False}, + COOKING_LIGHTING_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_OFF, + {}, + STATE_OFF, + "Hood", + ), + ( + "light.hood_light", + { + COOKING_LIGHTING: { + "value": None, + }, + COOKING_LIGHTING_BRIGHTNESS: None, + }, + SERVICE_TURN_ON, + {}, + STATE_UNKNOWN, + "Hood", + ), + ( + "light.hood_ambientlight", + { + BSH_AMBIENT_LIGHT_ENABLED: { + "value": True, + }, + BSH_AMBIENT_LIGHT_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_ON, + {"brightness": 200}, + STATE_ON, + "Hood", + ), + ( + "light.hood_ambientlight", + { + BSH_AMBIENT_LIGHT_ENABLED: {"value": False}, + BSH_AMBIENT_LIGHT_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_OFF, + {}, + STATE_OFF, + "Hood", + ), + ( + "light.hood_ambientlight", + { + BSH_AMBIENT_LIGHT_ENABLED: {"value": True}, + BSH_AMBIENT_LIGHT_CUSTOM_COLOR: {}, + }, + SERVICE_TURN_ON, + {}, + STATE_ON, + "Hood", + ), + ], + indirect=["appliance"], +) +async def test_light_functionality( + entity_id: str, + status: dict, + service: str, + service_data: dict, + state: str, + appliance: Mock, + bypass_throttle: Generator[None, Any, None], + hass: HomeAssistant, + config_entry: MockConfigEntry, + integration_setup: Callable[[], Awaitable[bool]], + setup_credentials: None, + get_appliances: MagicMock, +) -> None: + """Test light functionality.""" + appliance.status.update(SETTINGS_STATUS) + 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) + service_data["entity_id"] = entity_id + await hass.services.async_call( + DOMAIN, + service, + service_data, + blocking=True, + ) + assert hass.states.is_state(entity_id, state) + + +@pytest.mark.parametrize( + ( + "entity_id", + "status", + "service", + "service_data", + "mock_attr", + "attr_side_effect", + "problematic_appliance", + ), + [ + ( + "light.hood_light", + { + COOKING_LIGHTING: { + "value": False, + }, + }, + SERVICE_TURN_ON, + {}, + "set_setting", + [HomeConnectError, HomeConnectError], + "Hood", + ), + ( + "light.hood_light", + { + COOKING_LIGHTING: { + "value": True, + }, + COOKING_LIGHTING_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_ON, + {"brightness": 200}, + "set_setting", + [HomeConnectError, HomeConnectError], + "Hood", + ), + ( + "light.hood_light", + { + COOKING_LIGHTING: {"value": False}, + }, + SERVICE_TURN_OFF, + {}, + "set_setting", + [HomeConnectError, HomeConnectError], + "Hood", + ), + ( + "light.hood_ambientlight", + { + BSH_AMBIENT_LIGHT_ENABLED: { + "value": True, + }, + BSH_AMBIENT_LIGHT_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_ON, + {}, + "set_setting", + [HomeConnectError, HomeConnectError], + "Hood", + ), + ( + "light.hood_ambientlight", + { + BSH_AMBIENT_LIGHT_ENABLED: { + "value": True, + }, + BSH_AMBIENT_LIGHT_BRIGHTNESS: {"value": 70}, + }, + SERVICE_TURN_ON, + {"brightness": 200}, + "set_setting", + [HomeConnectError, None, HomeConnectError, HomeConnectError], + "Hood", + ), + ], + indirect=["problematic_appliance"], +) +async def test_switch_exception_handling( + entity_id: str, + status: dict, + service: str, + service_data: dict, + mock_attr: str, + attr_side_effect: list, + problematic_appliance: Mock, + bypass_throttle: Generator[None, Any, None], + hass: HomeAssistant, + integration_setup: Callable[[], Awaitable[bool]], + config_entry: MockConfigEntry, + setup_credentials: None, + get_appliances: MagicMock, +) -> None: + """Test light exception handling.""" + problematic_appliance.status.update(SETTINGS_STATUS) + problematic_appliance.set_setting.side_effect = attr_side_effect + 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) + service_data["entity_id"] = entity_id + await hass.services.async_call(DOMAIN, service, service_data, blocking=True) + assert getattr(problematic_appliance, mock_attr).call_count == len(attr_side_effect)