core/tests/components/shelly/test_number.py

393 lines
12 KiB
Python

"""Tests for Shelly number platform."""
from copy import deepcopy
from unittest.mock import AsyncMock, Mock
from aioshelly.exceptions import DeviceConnectionError, InvalidAuthError
import pytest
from homeassistant.components.number import (
ATTR_MAX,
ATTR_MIN,
ATTR_MODE,
ATTR_STEP,
ATTR_VALUE,
DOMAIN as NUMBER_DOMAIN,
SERVICE_SET_VALUE,
NumberMode,
)
from homeassistant.components.shelly.const import DOMAIN
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, STATE_UNKNOWN
from homeassistant.core import HomeAssistant, State
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceRegistry
from homeassistant.helpers.entity_registry import EntityRegistry
from . import init_integration, register_device, register_entity
from tests.common import mock_restore_cache_with_extra_data
DEVICE_BLOCK_ID = 4
async def test_block_number_update(
hass: HomeAssistant,
mock_block_device: Mock,
entity_registry: EntityRegistry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test block device number update."""
entity_id = "number.test_name_valve_position"
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
assert hass.states.get(entity_id) is None
# Make device online
mock_block_device.mock_online()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == "50"
monkeypatch.setattr(mock_block_device.blocks[DEVICE_BLOCK_ID], "valvePos", 30)
mock_block_device.mock_update()
assert hass.states.get(entity_id).state == "30"
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "123456789ABC-device_0-valvePos"
async def test_block_restored_number(
hass: HomeAssistant,
mock_block_device: Mock,
device_registry: DeviceRegistry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test block restored number."""
entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True)
device = register_device(device_registry, 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,
device_id=device.id,
)
extra_data = {
"native_max_value": 100,
"native_min_value": 0,
"native_step": 1,
"native_unit_of_measurement": "%",
"native_value": "40",
}
mock_restore_cache_with_extra_data(hass, ((State(entity_id, ""), extra_data),))
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_online()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == "50"
async def test_block_restored_number_no_last_state(
hass: HomeAssistant,
mock_block_device: Mock,
device_registry: DeviceRegistry,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test block restored number missing last state."""
entry = await init_integration(hass, 1, sleep_period=1000, skip_setup=True)
device = register_device(device_registry, 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,
device_id=device.id,
)
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 == STATE_UNKNOWN
# Make device online
monkeypatch.setattr(mock_block_device, "initialized", True)
mock_block_device.mock_online()
await hass.async_block_till_done(wait_background_tasks=True)
assert hass.states.get(entity_id).state == "50"
async def test_block_number_set_value(
hass: HomeAssistant,
mock_block_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test block device number set value."""
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
await init_integration(hass, 1, sleep_period=3600)
# Make device online
mock_block_device.mock_online()
await hass.async_block_till_done(wait_background_tasks=True)
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: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test block device set value connection error."""
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
monkeypatch.setattr(
mock_block_device,
"http_request",
AsyncMock(side_effect=DeviceConnectionError),
)
await init_integration(hass, 1, sleep_period=3600)
# Make device online
mock_block_device.mock_online()
await hass.async_block_till_done(wait_background_tasks=True)
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: HomeAssistant, mock_block_device: Mock, monkeypatch: pytest.MonkeyPatch
) -> None:
"""Test block device set value authentication error."""
monkeypatch.setitem(
mock_block_device.settings,
"sleep_mode",
{"period": 60, "unit": "m"},
)
monkeypatch.setattr(
mock_block_device,
"http_request",
AsyncMock(side_effect=InvalidAuthError),
)
entry = await init_integration(hass, 1, sleep_period=3600)
# Make device online
mock_block_device.mock_online()
await hass.async_block_till_done(wait_background_tasks=True)
assert entry.state is 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 is 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
@pytest.mark.parametrize(
("name", "entity_id", "original_unit", "expected_unit", "view", "mode"),
[
(
"Virtual number",
"number.test_name_virtual_number",
"%",
"%",
"field",
NumberMode.BOX,
),
(None, "number.test_name_number_203", "", None, "field", NumberMode.BOX),
(
"Virtual slider",
"number.test_name_virtual_slider",
"Hz",
"Hz",
"slider",
NumberMode.SLIDER,
),
],
)
async def test_rpc_device_virtual_number(
hass: HomeAssistant,
entity_registry: EntityRegistry,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
name: str | None,
entity_id: str,
original_unit: str,
expected_unit: str | None,
view: str,
mode: NumberMode,
) -> None:
"""Test a virtual number for RPC device."""
config = deepcopy(mock_rpc_device.config)
config["number:203"] = {
"name": name,
"min": 0,
"max": 100,
"meta": {"ui": {"step": 0.1, "unit": original_unit, "view": view}},
}
monkeypatch.setattr(mock_rpc_device, "config", config)
status = deepcopy(mock_rpc_device.status)
status["number:203"] = {"value": 12.3}
monkeypatch.setattr(mock_rpc_device, "status", status)
await init_integration(hass, 3)
state = hass.states.get(entity_id)
assert state
assert state.state == "12.3"
assert state.attributes.get(ATTR_MIN) == 0
assert state.attributes.get(ATTR_MAX) == 100
assert state.attributes.get(ATTR_STEP) == 0.1
assert state.attributes.get(ATTR_UNIT_OF_MEASUREMENT) == expected_unit
assert state.attributes.get(ATTR_MODE) is mode
entry = entity_registry.async_get(entity_id)
assert entry
assert entry.unique_id == "123456789ABC-number:203-number"
monkeypatch.setitem(mock_rpc_device.status["number:203"], "value", 78.9)
mock_rpc_device.mock_update()
assert hass.states.get(entity_id).state == "78.9"
monkeypatch.setitem(mock_rpc_device.status["number:203"], "value", 56.7)
await hass.services.async_call(
NUMBER_DOMAIN,
SERVICE_SET_VALUE,
{ATTR_ENTITY_ID: entity_id, ATTR_VALUE: 56.7},
blocking=True,
)
mock_rpc_device.mock_update()
assert hass.states.get(entity_id).state == "56.7"
async def test_rpc_remove_virtual_number_when_mode_label(
hass: HomeAssistant,
entity_registry: EntityRegistry,
device_registry: DeviceRegistry,
mock_rpc_device: Mock,
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""Test if the virtual number will be removed if the mode has been changed to a label."""
config = deepcopy(mock_rpc_device.config)
config["number:200"] = {
"name": None,
"min": -1000,
"max": 1000,
"meta": {"ui": {"step": 1, "unit": "", "view": "label"}},
}
monkeypatch.setattr(mock_rpc_device, "config", config)
status = deepcopy(mock_rpc_device.status)
status["number:200"] = {"value": 123}
monkeypatch.setattr(mock_rpc_device, "status", status)
config_entry = await init_integration(hass, 3, skip_setup=True)
device_entry = register_device(device_registry, config_entry)
entity_id = register_entity(
hass,
NUMBER_DOMAIN,
"test_name_number_200",
"number:200-number",
config_entry,
device_id=device_entry.id,
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
entry = entity_registry.async_get(entity_id)
assert not entry
async def test_rpc_remove_virtual_number_when_orphaned(
hass: HomeAssistant,
entity_registry: EntityRegistry,
device_registry: DeviceRegistry,
mock_rpc_device: Mock,
) -> None:
"""Check whether the virtual number will be removed if it has been removed from the device configuration."""
config_entry = await init_integration(hass, 3, skip_setup=True)
device_entry = register_device(device_registry, config_entry)
entity_id = register_entity(
hass,
NUMBER_DOMAIN,
"test_name_number_200",
"number:200-number",
config_entry,
device_id=device_entry.id,
)
await hass.config_entries.async_setup(config_entry.entry_id)
await hass.async_block_till_done()
entry = entity_registry.async_get(entity_id)
assert not entry