From 96aba1c1a6604abbe4b72c520e28197e53258a40 Mon Sep 17 00:00:00 2001 From: David Knowles Date: Fri, 6 Oct 2023 08:42:08 -0400 Subject: [PATCH] Add tests to Hydrawise (#101110) * Add tests to Hydrawise * Update tests/components/hydrawise/test_binary_sensor.py Co-authored-by: Joost Lekkerkerker * Changes requested during review --------- Co-authored-by: Joost Lekkerkerker --- .coveragerc | 6 -- .../components/hydrawise/binary_sensor.py | 2 +- homeassistant/components/hydrawise/sensor.py | 2 +- homeassistant/components/hydrawise/switch.py | 2 +- tests/components/hydrawise/conftest.py | 3 + .../hydrawise/test_binary_sensor.py | 53 ++++++++++++ tests/components/hydrawise/test_init.py | 57 +++++++++++++ tests/components/hydrawise/test_sensor.py | 36 ++++++++ tests/components/hydrawise/test_switch.py | 85 +++++++++++++++++++ 9 files changed, 237 insertions(+), 9 deletions(-) create mode 100644 tests/components/hydrawise/test_binary_sensor.py create mode 100644 tests/components/hydrawise/test_init.py create mode 100644 tests/components/hydrawise/test_sensor.py create mode 100644 tests/components/hydrawise/test_switch.py diff --git a/.coveragerc b/.coveragerc index 7f474426fa2c..599556d5d570 100644 --- a/.coveragerc +++ b/.coveragerc @@ -541,12 +541,6 @@ omit = homeassistant/components/hvv_departures/__init__.py homeassistant/components/hvv_departures/binary_sensor.py homeassistant/components/hvv_departures/sensor.py - homeassistant/components/hydrawise/__init__.py - homeassistant/components/hydrawise/binary_sensor.py - homeassistant/components/hydrawise/const.py - homeassistant/components/hydrawise/coordinator.py - homeassistant/components/hydrawise/sensor.py - homeassistant/components/hydrawise/switch.py homeassistant/components/ialarm/alarm_control_panel.py homeassistant/components/iammeter/sensor.py homeassistant/components/iaqualink/binary_sensor.py diff --git a/homeassistant/components/hydrawise/binary_sensor.py b/homeassistant/components/hydrawise/binary_sensor.py index 1c40b16926d3..30096a9bf978 100644 --- a/homeassistant/components/hydrawise/binary_sensor.py +++ b/homeassistant/components/hydrawise/binary_sensor.py @@ -57,7 +57,7 @@ def setup_platform( ) -> None: """Set up a sensor for a Hydrawise device.""" # We don't need to trigger import flow from here as it's triggered from `__init__.py` - return + return # pragma: no cover async def async_setup_entry( diff --git a/homeassistant/components/hydrawise/sensor.py b/homeassistant/components/hydrawise/sensor.py index a5bd9251a338..ef98ce99bfb3 100644 --- a/homeassistant/components/hydrawise/sensor.py +++ b/homeassistant/components/hydrawise/sensor.py @@ -59,7 +59,7 @@ def setup_platform( ) -> None: """Set up a sensor for a Hydrawise device.""" # We don't need to trigger import flow from here as it's triggered from `__init__.py` - return + return # pragma: no cover async def async_setup_entry( diff --git a/homeassistant/components/hydrawise/switch.py b/homeassistant/components/hydrawise/switch.py index 8cdb5b675610..d1ea0233145f 100644 --- a/homeassistant/components/hydrawise/switch.py +++ b/homeassistant/components/hydrawise/switch.py @@ -65,7 +65,7 @@ def setup_platform( ) -> None: """Set up a sensor for a Hydrawise device.""" # We don't need to trigger import flow from here as it's triggered from `__init__.py` - return + return # pragma: no cover async def async_setup_entry( diff --git a/tests/components/hydrawise/conftest.py b/tests/components/hydrawise/conftest.py index 309890181526..4a6c8372e570 100644 --- a/tests/components/hydrawise/conftest.py +++ b/tests/components/hydrawise/conftest.py @@ -33,6 +33,9 @@ def mock_pydrawise( mock_pydrawise.return_value.current_controller = mock_controller mock_pydrawise.return_value.controller_status = {"relays": mock_zones} mock_pydrawise.return_value.relays = mock_zones + mock_pydrawise.return_value.relays_by_zone_number = { + r["relay"]: r for r in mock_zones + } yield mock_pydrawise.return_value diff --git a/tests/components/hydrawise/test_binary_sensor.py b/tests/components/hydrawise/test_binary_sensor.py new file mode 100644 index 000000000000..ab88c5fb750c --- /dev/null +++ b/tests/components/hydrawise/test_binary_sensor.py @@ -0,0 +1,53 @@ +"""Test Hydrawise binary_sensor.""" + +from datetime import timedelta +from unittest.mock import Mock + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.components.hydrawise.const import SCAN_INTERVAL +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_states( + hass: HomeAssistant, + mock_added_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test binary_sensor states.""" + # Make the coordinator refresh data. + freezer.tick(SCAN_INTERVAL + timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + connectivity = hass.states.get("binary_sensor.home_controller_connectivity") + assert connectivity is not None + assert connectivity.state == "on" + + watering1 = hass.states.get("binary_sensor.zone_one_watering") + assert watering1 is not None + assert watering1.state == "off" + + watering2 = hass.states.get("binary_sensor.zone_two_watering") + assert watering2 is not None + assert watering2.state == "on" + + +async def test_update_data_fails( + hass: HomeAssistant, + mock_added_config_entry: MockConfigEntry, + mock_pydrawise: Mock, + freezer: FrozenDateTimeFactory, +) -> None: + """Test that no data from the API sets the correct connectivity.""" + # Make the coordinator refresh data. + mock_pydrawise.update_controller_info.return_value = None + freezer.tick(SCAN_INTERVAL + timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + connectivity = hass.states.get("binary_sensor.home_controller_connectivity") + assert connectivity is not None + assert connectivity.state == "unavailable" diff --git a/tests/components/hydrawise/test_init.py b/tests/components/hydrawise/test_init.py new file mode 100644 index 000000000000..87c158ec0b99 --- /dev/null +++ b/tests/components/hydrawise/test_init.py @@ -0,0 +1,57 @@ +"""Tests for the Hydrawise integration.""" + +from unittest.mock import Mock, patch + +from requests.exceptions import HTTPError + +from homeassistant.config_entries import ConfigEntryState +from homeassistant.const import CONF_ACCESS_TOKEN +from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant +import homeassistant.helpers.issue_registry as ir +from homeassistant.setup import async_setup_component + +from tests.common import MockConfigEntry + + +async def test_setup_import_success(hass: HomeAssistant, mock_pydrawise: Mock) -> None: + """Test that setup with a YAML config triggers an import and warning.""" + mock_pydrawise.customer_id = 12345 + mock_pydrawise.status = "unknown" + config = {"hydrawise": {CONF_ACCESS_TOKEN: "_access-token_"}} + assert await async_setup_component(hass, "hydrawise", config) + await hass.async_block_till_done() + + issue_registry = ir.async_get(hass) + issue = issue_registry.async_get_issue( + HOMEASSISTANT_DOMAIN, "deprecated_yaml_hydrawise" + ) + assert issue.translation_key == "deprecated_yaml" + + +async def test_connect_retry( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test that a connection error triggers a retry.""" + with patch("pydrawise.legacy.LegacyHydrawise") as mock_api: + mock_api.side_effect = HTTPError + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_api.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY + + +async def test_setup_no_data( + hass: HomeAssistant, mock_config_entry: MockConfigEntry +) -> None: + """Test that no data from the API triggers a retry.""" + with patch("pydrawise.legacy.LegacyHydrawise") as mock_api: + mock_api.return_value.controller_info = {} + mock_api.return_value.controller_status = None + mock_config_entry.add_to_hass(hass) + await hass.config_entries.async_setup(mock_config_entry.entry_id) + await hass.async_block_till_done() + + mock_api.assert_called_once() + assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY diff --git a/tests/components/hydrawise/test_sensor.py b/tests/components/hydrawise/test_sensor.py new file mode 100644 index 000000000000..b7c60f333f4d --- /dev/null +++ b/tests/components/hydrawise/test_sensor.py @@ -0,0 +1,36 @@ +"""Test Hydrawise sensor.""" + +from datetime import timedelta + +from freezegun.api import FrozenDateTimeFactory +import pytest + +from homeassistant.components.hydrawise.const import SCAN_INTERVAL +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, async_fire_time_changed + + +@pytest.mark.freeze_time("2023-10-01 00:00:00+00:00") +async def test_states( + hass: HomeAssistant, + mock_added_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test sensor states.""" + # Make the coordinator refresh data. + freezer.tick(SCAN_INTERVAL + timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + watering_time1 = hass.states.get("sensor.zone_one_watering_time") + assert watering_time1 is not None + assert watering_time1.state == "0" + + watering_time2 = hass.states.get("sensor.zone_two_watering_time") + assert watering_time2 is not None + assert watering_time2.state == "29" + + next_cycle = hass.states.get("sensor.zone_one_next_cycle") + assert next_cycle is not None + assert next_cycle.state == "2023-10-04T19:52:27+00:00" diff --git a/tests/components/hydrawise/test_switch.py b/tests/components/hydrawise/test_switch.py new file mode 100644 index 000000000000..615a336ee5fa --- /dev/null +++ b/tests/components/hydrawise/test_switch.py @@ -0,0 +1,85 @@ +"""Test Hydrawise switch.""" + +from datetime import timedelta +from unittest.mock import Mock + +from freezegun.api import FrozenDateTimeFactory + +from homeassistant.components.hydrawise.const import SCAN_INTERVAL +from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON +from homeassistant.core import HomeAssistant + +from tests.common import MockConfigEntry, async_fire_time_changed + + +async def test_states( + hass: HomeAssistant, + mock_added_config_entry: MockConfigEntry, + freezer: FrozenDateTimeFactory, +) -> None: + """Test switch states.""" + # Make the coordinator refresh data. + freezer.tick(SCAN_INTERVAL + timedelta(seconds=30)) + async_fire_time_changed(hass) + await hass.async_block_till_done() + + watering1 = hass.states.get("switch.zone_one_manual_watering") + assert watering1 is not None + assert watering1.state == "off" + + watering2 = hass.states.get("switch.zone_two_manual_watering") + assert watering2 is not None + assert watering2.state == "on" + + auto_watering1 = hass.states.get("switch.zone_one_automatic_watering") + assert auto_watering1 is not None + assert auto_watering1.state == "on" + + auto_watering2 = hass.states.get("switch.zone_two_automatic_watering") + assert auto_watering2 is not None + assert auto_watering2.state == "off" + + +async def test_manual_watering_services( + hass: HomeAssistant, mock_added_config_entry: MockConfigEntry, mock_pydrawise: Mock +) -> None: + """Test Manual Watering services.""" + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + service_data={ATTR_ENTITY_ID: "switch.zone_one_manual_watering"}, + blocking=True, + ) + mock_pydrawise.run_zone.assert_called_once_with(15, 1) + mock_pydrawise.reset_mock() + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + service_data={ATTR_ENTITY_ID: "switch.zone_one_manual_watering"}, + blocking=True, + ) + mock_pydrawise.run_zone.assert_called_once_with(0, 1) + + +async def test_auto_watering_services( + hass: HomeAssistant, mock_added_config_entry: MockConfigEntry, mock_pydrawise: Mock +) -> None: + """Test Automatic Watering services.""" + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_OFF, + service_data={ATTR_ENTITY_ID: "switch.zone_one_automatic_watering"}, + blocking=True, + ) + mock_pydrawise.suspend_zone.assert_called_once_with(365, 1) + mock_pydrawise.reset_mock() + + await hass.services.async_call( + SWITCH_DOMAIN, + SERVICE_TURN_ON, + service_data={ATTR_ENTITY_ID: "switch.zone_one_automatic_watering"}, + blocking=True, + ) + mock_pydrawise.suspend_zone.assert_called_once_with(0, 1)