diff --git a/.coveragerc b/.coveragerc index 33e0763a5dd..413294573a0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1111,7 +1111,6 @@ omit = homeassistant/components/seventeentrack/sensor.py homeassistant/components/shelly/climate.py homeassistant/components/shelly/coordinator.py - homeassistant/components/shelly/number.py homeassistant/components/shelly/utils.py homeassistant/components/shiftr/* homeassistant/components/shodan/sensor.py diff --git a/homeassistant/components/shelly/number.py b/homeassistant/components/shelly/number.py index bb7f17ea18d..7066f386355 100644 --- a/homeassistant/components/shelly/number.py +++ b/homeassistant/components/shelly/number.py @@ -25,7 +25,6 @@ from .entity import ( ShellySleepingBlockAttributeEntity, async_setup_entry_attribute_entities, ) -from .utils import get_device_entry_gen @dataclass @@ -77,9 +76,6 @@ async def async_setup_entry( async_add_entities: AddEntitiesCallback, ) -> None: """Set up numbers for device.""" - if get_device_entry_gen(config_entry) == 2: - return - if config_entry.data[CONF_SLEEP_PERIOD]: async_setup_entry_attribute_entities( hass, diff --git a/tests/components/shelly/__init__.py b/tests/components/shelly/__init__.py index 3adc1eaf49a..36a43984c4a 100644 --- a/tests/components/shelly/__init__.py +++ b/tests/components/shelly/__init__.py @@ -1,6 +1,7 @@ """Tests for the Shelly integration.""" from __future__ import annotations +from collections.abc import Mapping from copy import deepcopy from datetime import timedelta from typing import Any @@ -99,6 +100,7 @@ def register_entity( object_id: str, unique_id: str, config_entry: ConfigEntry | None = None, + capabilities: Mapping[str, Any] | None = None, ) -> str: """Register enabled entity, return entity_id.""" entity_registry = async_get(hass) @@ -109,6 +111,7 @@ def register_entity( suggested_object_id=object_id, disabled_by=None, config_entry=config_entry, + capabilities=capabilities, ) return f"{domain}.{object_id}" diff --git a/tests/components/shelly/conftest.py b/tests/components/shelly/conftest.py index 2f940530319..26931ea804e 100644 --- a/tests/components/shelly/conftest.py +++ b/tests/components/shelly/conftest.py @@ -30,6 +30,7 @@ MOCK_SETTINGS = { "relays": [{"btn_type": "momentary"}, {"btn_type": "toggle"}], "rollers": [{"positioning": True}], "external_power": 0, + "thermostats": [{"schedule_profile_names": {}}], } @@ -106,9 +107,11 @@ MOCK_BLOCKS = [ type="sensor", ), Mock( - sensor_ids={"battery": 98}, + sensor_ids={"battery": 98, "valvePos": 50}, + channel="0", battery=98, cfgChanged=0, + valvePos=50, description="device_0", type="device", ), diff --git a/tests/components/shelly/test_number.py b/tests/components/shelly/test_number.py new file mode 100644 index 00000000000..69b1105fef5 --- /dev/null +++ b/tests/components/shelly/test_number.py @@ -0,0 +1,152 @@ +"""Tests for Shelly number platform.""" +from unittest.mock import AsyncMock + +from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError +import pytest + +from homeassistant.components.number import ( + ATTR_VALUE, + DOMAIN as NUMBER_DOMAIN, + SERVICE_SET_VALUE, +) +from homeassistant.components.shelly.const import DOMAIN +from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState +from homeassistant.const import ATTR_ENTITY_ID +from homeassistant.core import State +from homeassistant.exceptions import HomeAssistantError + +from . import init_integration, register_device, register_entity + +from tests.common import mock_restore_cache + +DEVICE_BLOCK_ID = 4 + + +async def test_block_number_update(hass, mock_block_device, monkeypatch): + """Test block device number update.""" + await init_integration(hass, 1, sleep_period=1000) + + assert hass.states.get("number.test_name_valve_position") is None + + # Make device online + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get("number.test_name_valve_position").state == "50" + + monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valvePos", 30) + mock_block_device.mock_update() + + assert hass.states.get("number.test_name_valve_position").state == "30" + + +async def test_block_restored_number(hass, mock_block_device, device_reg, monkeypatch): + """Test block restored number.""" + entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True) + register_device(device_reg, entry) + capabilities = { + "min": 0, + "max": 100, + "step": 1, + "mode": "slider", + } + entity_id = register_entity( + hass, + NUMBER_DOMAIN, + "test_name_valve_position", + "device_0-valvePos", + entry, + capabilities, + ) + mock_restore_cache(hass, [State(entity_id, "40")]) + + monkeypatch.setattr(mock_block_device, "initialized", False) + await hass.config_entries.async_setup(entry.entry_id) + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "40" + + # Make device online + monkeypatch.setattr(mock_block_device, "initialized", True) + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert hass.states.get(entity_id).state == "50" + + +async def test_block_number_set_value(hass, mock_block_device): + """Test block device number set value.""" + await init_integration(hass, 1, sleep_period=1000) + + # Make device online + mock_block_device.mock_update() + await hass.async_block_till_done() + + mock_block_device.reset_mock() + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "number.test_name_valve_position", ATTR_VALUE: 30}, + blocking=True, + ) + mock_block_device.http_request.assert_called_once_with( + "get", "thermostat/0", {"pos": 30.0} + ) + + +async def test_block_set_value_connection_error(hass, mock_block_device, monkeypatch): + """Test block device set value connection error.""" + monkeypatch.setattr( + mock_block_device, + "http_request", + AsyncMock(side_effect=DeviceConnectionError), + ) + await init_integration(hass, 1, sleep_period=1000) + + # Make device online + mock_block_device.mock_update() + await hass.async_block_till_done() + + with pytest.raises(HomeAssistantError): + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "number.test_name_valve_position", ATTR_VALUE: 30}, + blocking=True, + ) + + +async def test_block_set_value_auth_error(hass, mock_block_device, monkeypatch): + """Test block device set value authentication error.""" + monkeypatch.setattr( + mock_block_device, + "http_request", + AsyncMock(side_effect=InvalidAuthError), + ) + entry = await init_integration(hass, 1, sleep_period=1000) + + # Make device online + mock_block_device.mock_update() + await hass.async_block_till_done() + + assert entry.state == ConfigEntryState.LOADED + + await hass.services.async_call( + NUMBER_DOMAIN, + SERVICE_SET_VALUE, + {ATTR_ENTITY_ID: "number.test_name_valve_position", ATTR_VALUE: 30}, + blocking=True, + ) + + assert entry.state == ConfigEntryState.LOADED + + flows = hass.config_entries.flow.async_progress() + assert len(flows) == 1 + + flow = flows[0] + assert flow.get("step_id") == "reauth_confirm" + assert flow.get("handler") == DOMAIN + + assert "context" in flow + assert flow["context"].get("source") == SOURCE_REAUTH + assert flow["context"].get("entry_id") == entry.entry_id diff --git a/tests/components/shelly/test_sensor.py b/tests/components/shelly/test_sensor.py index 89fa138349f..6dcd903219f 100644 --- a/tests/components/shelly/test_sensor.py +++ b/tests/components/shelly/test_sensor.py @@ -50,6 +50,9 @@ async def test_block_rest_sensor(hass, mock_block_device, monkeypatch): async def test_block_sleeping_sensor(hass, mock_block_device, monkeypatch): """Test block sleeping sensor.""" + monkeypatch.setattr( + mock_block_device.blocks[DEVICE_BLOCK_ID], "sensor_ids", {"battery": 98} + ) entity_id = f"{SENSOR_DOMAIN}.test_name_temperature" await init_integration(hass, 1, sleep_period=1000)