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