mirror of
https://github.com/home-assistant/core.git
synced 2026-04-19 16:09:06 +02:00
Improve eurotronic_cometblue tests (#168046)
This commit is contained in:
@@ -1 +1,67 @@
|
||||
"""Tests for the Eurotronic Comet Blue integration."""
|
||||
|
||||
from eurotronic_cometblue_ha import const as cometblue_const
|
||||
|
||||
from homeassistant.const import CONF_PIN
|
||||
|
||||
FIXTURE_DEVICE_NAME = "Comet Blue"
|
||||
FIXTURE_MAC = "aa:bb:cc:dd:ee:ff"
|
||||
FIXTURE_RSSI = -60
|
||||
FIXTURE_SERVICE_UUID = "47e9ee00-47e9-11e4-8939-164230d1df67"
|
||||
|
||||
WRITEABLE_CHARACTERISTICS = [
|
||||
cometblue_const.CHARACTERISTIC_DATETIME,
|
||||
cometblue_const.CHARACTERISTIC_MONDAY,
|
||||
cometblue_const.CHARACTERISTIC_TUESDAY,
|
||||
cometblue_const.CHARACTERISTIC_WEDNESDAY,
|
||||
cometblue_const.CHARACTERISTIC_THURSDAY,
|
||||
cometblue_const.CHARACTERISTIC_FRIDAY,
|
||||
cometblue_const.CHARACTERISTIC_SATURDAY,
|
||||
cometblue_const.CHARACTERISTIC_SUNDAY,
|
||||
cometblue_const.CHARACTERISTIC_HOLIDAY_1,
|
||||
cometblue_const.CHARACTERISTIC_SETTINGS,
|
||||
cometblue_const.CHARACTERISTIC_TEMPERATURE,
|
||||
cometblue_const.CHARACTERISTIC_PIN,
|
||||
]
|
||||
WRITEABLE_CHARACTERISTICS_ALLOW_UNCHANGED = [
|
||||
cometblue_const.CHARACTERISTIC_SETTINGS,
|
||||
cometblue_const.CHARACTERISTIC_TEMPERATURE,
|
||||
]
|
||||
|
||||
FIXTURE_DEFAULT_CHARACTERISTICS = {
|
||||
cometblue_const.CHARACTERISTIC_MODEL: b"Comet Blue",
|
||||
cometblue_const.CHARACTERISTIC_VERSION: b"0.0.10",
|
||||
cometblue_const.CHARACTERISTIC_MANUFACTURER: b"Eurotronic GmbH",
|
||||
cometblue_const.CHARACTERISTIC_HOLIDAY_1: [
|
||||
128,
|
||||
27,
|
||||
11,
|
||||
22,
|
||||
128,
|
||||
27,
|
||||
11,
|
||||
22,
|
||||
34,
|
||||
],
|
||||
cometblue_const.CHARACTERISTIC_TEMPERATURE: [
|
||||
41,
|
||||
40,
|
||||
34,
|
||||
42,
|
||||
0,
|
||||
4,
|
||||
10,
|
||||
],
|
||||
cometblue_const.CHARACTERISTIC_BATTERY: b"48",
|
||||
cometblue_const.CHARACTERISTIC_MONDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
cometblue_const.CHARACTERISTIC_TUESDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
cometblue_const.CHARACTERISTIC_WEDNESDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
cometblue_const.CHARACTERISTIC_THURSDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
cometblue_const.CHARACTERISTIC_FRIDAY: [0, 1, 10, 20, 21, 130, 140, 143],
|
||||
cometblue_const.CHARACTERISTIC_SATURDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
cometblue_const.CHARACTERISTIC_SUNDAY: [37, 137, 0, 0, 0, 0, 0, 0],
|
||||
}
|
||||
|
||||
FIXTURE_USER_INPUT = {
|
||||
CONF_PIN: "000000",
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"""Session fixtures."""
|
||||
|
||||
from collections.abc import Generator
|
||||
from collections.abc import Buffer, Generator
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
import uuid
|
||||
@@ -12,17 +12,21 @@ from eurotronic_cometblue_ha import CometBlueBleakClient
|
||||
import pytest
|
||||
|
||||
from homeassistant.components.bluetooth import BluetoothServiceInfoBleak
|
||||
from homeassistant.components.eurotronic_cometblue import PLATFORMS
|
||||
from homeassistant.components.eurotronic_cometblue.const import DOMAIN
|
||||
from homeassistant.const import CONF_ADDRESS
|
||||
from homeassistant.const import CONF_ADDRESS, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.helpers.device_registry import format_mac
|
||||
|
||||
from .const import (
|
||||
from . import (
|
||||
FIXTURE_DEFAULT_CHARACTERISTICS,
|
||||
FIXTURE_DEVICE_NAME,
|
||||
FIXTURE_GATT_CHARACTERISTICS,
|
||||
FIXTURE_MAC,
|
||||
FIXTURE_RSSI,
|
||||
FIXTURE_SERVICE_UUID,
|
||||
FIXTURE_USER_INPUT,
|
||||
WRITEABLE_CHARACTERISTICS,
|
||||
WRITEABLE_CHARACTERISTICS_ALLOW_UNCHANGED,
|
||||
)
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
@@ -58,9 +62,24 @@ FAKE_SERVICE_INFO = BluetoothServiceInfoBleak(
|
||||
)
|
||||
|
||||
|
||||
def _normalize_characteristic(
|
||||
char_specifier: BleakGATTCharacteristic | int | str | uuid.UUID,
|
||||
) -> uuid.UUID:
|
||||
"""Normalize a characteristic specifier to UUID."""
|
||||
if not isinstance(char_specifier, (BleakGATTCharacteristic, str, uuid.UUID)):
|
||||
raise BleakCharacteristicNotFoundError(char_specifier)
|
||||
if isinstance(char_specifier, BleakGATTCharacteristic):
|
||||
char_specifier = char_specifier.uuid
|
||||
if not isinstance(char_specifier, uuid.UUID):
|
||||
char_specifier = uuid.UUID(char_specifier)
|
||||
return char_specifier
|
||||
|
||||
|
||||
class MockCometBlueBleakClient(CometBlueBleakClient):
|
||||
"""Mock BleakClient."""
|
||||
|
||||
characteristics: dict[uuid.UUID, bytearray] = {}
|
||||
|
||||
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
||||
"""Mock init."""
|
||||
super().__init__(*args, **kwargs)
|
||||
@@ -94,17 +113,40 @@ class MockCometBlueBleakClient(CometBlueBleakClient):
|
||||
**kwargs: Any,
|
||||
) -> bytearray:
|
||||
"""Mock read_gatt_char."""
|
||||
if not isinstance(char_specifier, (BleakGATTCharacteristic, str, uuid.UUID)):
|
||||
raise BleakCharacteristicNotFoundError(char_specifier)
|
||||
if isinstance(char_specifier, BleakGATTCharacteristic):
|
||||
char_specifier = char_specifier.uuid
|
||||
if not isinstance(char_specifier, uuid.UUID):
|
||||
char_specifier = uuid.UUID(char_specifier)
|
||||
char_specifier = _normalize_characteristic(char_specifier)
|
||||
try:
|
||||
return FIXTURE_GATT_CHARACTERISTICS[char_specifier]
|
||||
return bytearray(self.characteristics[char_specifier])
|
||||
except KeyError:
|
||||
raise BleakCharacteristicNotFoundError(char_specifier)
|
||||
|
||||
async def write_gatt_char(
|
||||
self,
|
||||
char_specifier: BleakGATTCharacteristic | int | str | uuid.UUID,
|
||||
data: Buffer,
|
||||
response: bool | None = None,
|
||||
) -> None:
|
||||
"""Mock write_gatt_char."""
|
||||
char_specifier = _normalize_characteristic(char_specifier)
|
||||
if char_specifier not in WRITEABLE_CHARACTERISTICS:
|
||||
raise BleakCharacteristicNotFoundError(char_specifier)
|
||||
data = bytearray(data)
|
||||
# when writing temperature it is possible that 128 will be sent, meaning "no change"
|
||||
# we have to restore the original value in this case to keep tests working
|
||||
if char_specifier in WRITEABLE_CHARACTERISTICS_ALLOW_UNCHANGED:
|
||||
for i, byte in enumerate(data):
|
||||
if byte == 128:
|
||||
data[i] = self.characteristics[char_specifier][i]
|
||||
self.characteristics[char_specifier] = data
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_gatt_characteristics() -> dict[uuid.UUID, bytearray]:
|
||||
"""Provide a mutable per-test GATT characteristic store."""
|
||||
return {
|
||||
characteristic: bytearray(value)
|
||||
for characteristic, value in FIXTURE_DEFAULT_CHARACTERISTICS.items()
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def mock_service_info() -> Generator[None]:
|
||||
@@ -133,13 +175,26 @@ def mock_ble_device() -> Generator[None]:
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def mock_bluetooth(enable_bluetooth: None) -> Generator[None]:
|
||||
def mock_bluetooth(
|
||||
enable_bluetooth: None,
|
||||
mock_gatt_characteristics: dict[uuid.UUID, bytearray],
|
||||
) -> Generator[None]:
|
||||
"""Auto mock bluetooth."""
|
||||
|
||||
with patch(
|
||||
"eurotronic_cometblue_ha.CometBlueBleakClient", MockCometBlueBleakClient
|
||||
MockCometBlueBleakClient.characteristics = mock_gatt_characteristics
|
||||
with (
|
||||
patch(
|
||||
"homeassistant.components.eurotronic_cometblue.entity.bluetooth.async_address_present",
|
||||
return_value=True,
|
||||
),
|
||||
patch(
|
||||
"homeassistant.components.eurotronic_cometblue.coordinator.COMMAND_RETRY_INTERVAL",
|
||||
0,
|
||||
),
|
||||
patch("eurotronic_cometblue_ha.CometBlueBleakClient", MockCometBlueBleakClient),
|
||||
):
|
||||
yield
|
||||
MockCometBlueBleakClient.characteristics = {}
|
||||
|
||||
|
||||
# Home Assistant related fixtures
|
||||
@@ -164,3 +219,16 @@ def mock_setup_entry() -> Generator[AsyncMock]:
|
||||
return_value=True,
|
||||
) as mock_setup:
|
||||
yield mock_setup
|
||||
|
||||
|
||||
async def setup_with_selected_platforms(
|
||||
hass: HomeAssistant, entry: MockConfigEntry, platforms: list[Platform] | None = None
|
||||
) -> None:
|
||||
"""Set up the Eurotronic Comet Blue integration with the selected platforms."""
|
||||
entry.add_to_hass(hass)
|
||||
with patch(
|
||||
"homeassistant.components.eurotronic_cometblue.PLATFORMS",
|
||||
platforms or PLATFORMS,
|
||||
):
|
||||
assert await hass.config_entries.async_setup(entry.entry_id)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
"""Constants for Eurotronic CometBlue tests."""
|
||||
|
||||
from uuid import UUID
|
||||
|
||||
from homeassistant.const import CONF_PIN
|
||||
|
||||
FIXTURE_DEVICE_NAME = "Comet Blue"
|
||||
FIXTURE_MAC = "aa:bb:cc:dd:ee:ff"
|
||||
FIXTURE_RSSI = -60
|
||||
FIXTURE_SERVICE_UUID = "47e9ee00-47e9-11e4-8939-164230d1df67"
|
||||
|
||||
FIXTURE_GATT_CHARACTERISTICS = {
|
||||
UUID("00002a24-0000-1000-8000-00805f9b34fb"): bytearray(b"Comet Blue"), # model
|
||||
UUID("00002a26-0000-1000-8000-00805f9b34fb"): bytearray(b"0.0.10"), # version
|
||||
UUID("00002a29-0000-1000-8000-00805f9b34fb"): bytearray(
|
||||
b"Eurotronic GmbH"
|
||||
), # manufacturer
|
||||
UUID("47e9ee20-47e9-11e4-8939-164230d1df67"): bytearray(
|
||||
b'\x80\x1b\x0b\x16\x80\x1b\x0b\x16"'
|
||||
), # holiday 1
|
||||
UUID("47e9ee2b-47e9-11e4-8939-164230d1df67"): bytearray(
|
||||
b"/999\x00\x04\n"
|
||||
), # temperature
|
||||
UUID("47e9ee2c-47e9-11e4-8939-164230d1df67"): bytearray(b"48"), # battery
|
||||
}
|
||||
|
||||
FIXTURE_USER_INPUT = {
|
||||
CONF_PIN: "000000",
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
# serializer version: 1
|
||||
# name: test_climate_state[climate.comet_blue_aa_bb_cc_dd_ee_ff-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
'hvac_modes': list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 28.5,
|
||||
'min_temp': 7.5,
|
||||
'preset_modes': list([
|
||||
'comfort',
|
||||
'eco',
|
||||
'boost',
|
||||
'away',
|
||||
'none',
|
||||
]),
|
||||
'target_temp_step': 0.5,
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'climate',
|
||||
'entity_category': None,
|
||||
'entity_id': 'climate.comet_blue_aa_bb_cc_dd_ee_ff',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': None,
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': None,
|
||||
'original_icon': None,
|
||||
'original_name': None,
|
||||
'platform': 'eurotronic_cometblue',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': <ClimateEntityFeature: 403>,
|
||||
'translation_key': None,
|
||||
'unique_id': 'aa:bb:cc:dd:ee:ff',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_climate_state[climate.comet_blue_aa_bb_cc_dd_ee_ff-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
'current_temperature': 20.5,
|
||||
'friendly_name': 'Comet Blue aa:bb:cc:dd:ee:ff',
|
||||
'hvac_modes': list([
|
||||
<HVACMode.AUTO: 'auto'>,
|
||||
<HVACMode.HEAT: 'heat'>,
|
||||
<HVACMode.OFF: 'off'>,
|
||||
]),
|
||||
'max_temp': 28.5,
|
||||
'min_temp': 7.5,
|
||||
'preset_mode': 'none',
|
||||
'preset_modes': list([
|
||||
'comfort',
|
||||
'eco',
|
||||
'boost',
|
||||
'away',
|
||||
'none',
|
||||
]),
|
||||
'supported_features': <ClimateEntityFeature: 403>,
|
||||
'target_temp_high': 21.0,
|
||||
'target_temp_low': 17.0,
|
||||
'target_temp_step': 0.5,
|
||||
'temperature': 20.0,
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'climate.comet_blue_aa_bb_cc_dd_ee_ff',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'auto',
|
||||
})
|
||||
# ---
|
||||
331
tests/components/eurotronic_cometblue/test_climate.py
Normal file
331
tests/components/eurotronic_cometblue/test_climate.py
Normal file
@@ -0,0 +1,331 @@
|
||||
"""Test the eurotronic_cometblue climate platform."""
|
||||
|
||||
from unittest.mock import patch
|
||||
import uuid
|
||||
|
||||
from eurotronic_cometblue_ha import const as cometblue_const
|
||||
import pytest
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_TEMPERATURE,
|
||||
ATTR_HVAC_MODE,
|
||||
ATTR_PRESET_MODE,
|
||||
DOMAIN as CLIMATE_DOMAIN,
|
||||
PRESET_AWAY,
|
||||
PRESET_BOOST,
|
||||
PRESET_COMFORT,
|
||||
PRESET_ECO,
|
||||
PRESET_NONE,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
SERVICE_TURN_OFF,
|
||||
SERVICE_TURN_ON,
|
||||
HVACMode,
|
||||
)
|
||||
from homeassistant.components.eurotronic_cometblue.climate import MAX_TEMP, MIN_TEMP
|
||||
from homeassistant.const import ATTR_ENTITY_ID, ATTR_TEMPERATURE, Platform
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
|
||||
from homeassistant.helpers import entity_registry as er
|
||||
|
||||
from .conftest import setup_with_selected_platforms
|
||||
|
||||
from tests.common import MockConfigEntry, snapshot_platform
|
||||
|
||||
ENTITY_ID = "climate.comet_blue_aa_bb_cc_dd_ee_ff"
|
||||
|
||||
|
||||
async def test_climate_state(
|
||||
hass: HomeAssistant,
|
||||
entity_registry: er.EntityRegistry,
|
||||
snapshot: SnapshotAssertion,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test climate entity state and registry data."""
|
||||
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("temperature_values", "expected_hvac_mode", "expected_preset"),
|
||||
[
|
||||
([47, 15, 34, 42, 0, 4, 10], HVACMode.OFF, PRESET_NONE),
|
||||
([47, 40, 34, 42, 0, 4, 10], HVACMode.AUTO, PRESET_NONE),
|
||||
([47, 42, 34, 42, 0, 4, 10], HVACMode.AUTO, PRESET_COMFORT),
|
||||
([47, 34, 34, 42, 0, 4, 10], HVACMode.AUTO, PRESET_ECO),
|
||||
([47, 57, 57, 57, 0, 4, 10], HVACMode.HEAT, PRESET_BOOST),
|
||||
],
|
||||
)
|
||||
async def test_climate_hvac_and_preset_states(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_gatt_characteristics: dict[uuid.UUID, bytearray],
|
||||
temperature_values: list[int],
|
||||
expected_hvac_mode: HVACMode,
|
||||
expected_preset: str,
|
||||
) -> None:
|
||||
"""Test climate state mapping from device temperatures."""
|
||||
mock_gatt_characteristics[cometblue_const.CHARACTERISTIC_TEMPERATURE] = bytearray(
|
||||
temperature_values
|
||||
)
|
||||
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.state == expected_hvac_mode
|
||||
assert state.attributes[ATTR_PRESET_MODE] == expected_preset
|
||||
|
||||
|
||||
async def test_set_temperature(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test setting target temperature."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.5
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 21.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 21.0
|
||||
assert state.attributes[ATTR_CURRENT_TEMPERATURE] == 20.5
|
||||
|
||||
|
||||
async def test_climate_preset_away_active(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
mock_gatt_characteristics: dict[uuid.UUID, bytearray],
|
||||
) -> None:
|
||||
"""Test away preset detection from holiday data."""
|
||||
# Holiday active if start hour >= 128 and end hour < 128
|
||||
mock_gatt_characteristics[cometblue_const.CHARACTERISTIC_HOLIDAY_1] = bytearray(
|
||||
[128, 1, 1, 26, 10, 2, 1, 26, 34]
|
||||
)
|
||||
# Current target temperature must match holiday temperature for away preset to be active
|
||||
mock_gatt_characteristics[cometblue_const.CHARACTERISTIC_TEMPERATURE] = bytearray(
|
||||
[47, 34, 34, 42, 0, 4, 10]
|
||||
)
|
||||
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_PRESET_MODE] == PRESET_AWAY
|
||||
|
||||
with pytest.raises(
|
||||
ServiceValidationError,
|
||||
match="Cannot adjust TRV remotely",
|
||||
):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 21.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("preset_mode", "expected_temperature", "expected_state"),
|
||||
[
|
||||
(PRESET_ECO, 17.0, HVACMode.AUTO),
|
||||
(PRESET_COMFORT, 21.0, HVACMode.AUTO),
|
||||
(PRESET_BOOST, MAX_TEMP, HVACMode.HEAT),
|
||||
],
|
||||
)
|
||||
async def test_set_preset_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
preset_mode: str,
|
||||
expected_temperature: float,
|
||||
expected_state: HVACMode,
|
||||
) -> None:
|
||||
"""Test setting preset modes."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: preset_mode},
|
||||
blocking=True,
|
||||
)
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == expected_temperature
|
||||
assert state.attributes[ATTR_PRESET_MODE] == preset_mode
|
||||
assert state.state == expected_state
|
||||
|
||||
|
||||
@pytest.mark.parametrize("preset_mode", [PRESET_NONE, PRESET_AWAY])
|
||||
async def test_set_preset_mode_display_only_raises(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
preset_mode: str,
|
||||
) -> None:
|
||||
"""Test display-only presets cannot be set."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
with pytest.raises(ServiceValidationError, match="Unable to set preset"):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_PRESET_MODE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_PRESET_MODE: preset_mode},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("hvac_mode", "expected_temperature", "expected_preset"),
|
||||
[
|
||||
(HVACMode.OFF, MIN_TEMP, PRESET_NONE),
|
||||
(HVACMode.HEAT, MAX_TEMP, PRESET_BOOST),
|
||||
(HVACMode.AUTO, 17.0, PRESET_ECO),
|
||||
],
|
||||
)
|
||||
async def test_set_hvac_mode(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
hvac_mode: HVACMode,
|
||||
expected_temperature: float,
|
||||
expected_preset: str,
|
||||
) -> None:
|
||||
"""Test setting HVAC modes."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_HVAC_MODE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_HVAC_MODE: hvac_mode},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == expected_temperature
|
||||
assert state.attributes[ATTR_PRESET_MODE] == expected_preset
|
||||
assert state.state == hvac_mode
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("service", "expected_temperature"),
|
||||
[
|
||||
(SERVICE_TURN_OFF, MIN_TEMP),
|
||||
(SERVICE_TURN_ON, 17.0),
|
||||
],
|
||||
)
|
||||
async def test_turn_on_turn_off(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
service: str,
|
||||
expected_temperature: float,
|
||||
) -> None:
|
||||
"""Test turn_on and turn_off services."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
service,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == expected_temperature
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("raise_exception", "raised_exception"),
|
||||
[
|
||||
(TimeoutError, HomeAssistantError),
|
||||
(ValueError, ServiceValidationError),
|
||||
],
|
||||
)
|
||||
async def test_set_temperature_errors(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
raise_exception: type[Exception],
|
||||
raised_exception: type[Exception],
|
||||
) -> None:
|
||||
"""Test setting target temperature."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
# raise exceptions to test error handling
|
||||
with (
|
||||
pytest.raises(raised_exception),
|
||||
patch(
|
||||
"homeassistant.components.eurotronic_cometblue.coordinator.AsyncCometBlue.set_temperature_async",
|
||||
side_effect=raise_exception(),
|
||||
),
|
||||
):
|
||||
await hass.services.async_call(
|
||||
CLIMATE_DOMAIN,
|
||||
SERVICE_SET_TEMPERATURE,
|
||||
{ATTR_ENTITY_ID: ENTITY_ID, ATTR_TEMPERATURE: 21.0},
|
||||
blocking=True,
|
||||
)
|
||||
|
||||
|
||||
async def test_update_data_error_handling(
|
||||
hass: HomeAssistant,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
) -> None:
|
||||
"""Test that update data errors are handled and retried."""
|
||||
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
|
||||
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||
|
||||
# Fail with TimeoutError (expected) and raise UpdateFailed after 3 retries
|
||||
with patch.object(
|
||||
mock_config_entry.runtime_data.device,
|
||||
"get_temperature_async",
|
||||
side_effect=TimeoutError(),
|
||||
) as mock_get_temperature:
|
||||
await mock_config_entry.runtime_data.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_get_temperature.call_count == 3
|
||||
assert mock_config_entry.runtime_data.last_update_success is False
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||
|
||||
# Fail with OSError (unexpected) and raise UpdateFailed directly
|
||||
with patch.object(
|
||||
mock_config_entry.runtime_data.device,
|
||||
"get_temperature_async",
|
||||
side_effect=OSError(),
|
||||
) as mock_get_temperature:
|
||||
await mock_config_entry.runtime_data.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_get_temperature.call_count == 1
|
||||
assert mock_config_entry.runtime_data.last_update_success is False
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 20.0
|
||||
|
||||
# Fail once with TimeoutError and then succeed, verify that data is updated
|
||||
updated_temperatures = dict(mock_config_entry.runtime_data.data.temperatures)
|
||||
updated_temperatures["manualTemp"] = 27.0
|
||||
|
||||
with patch.object(
|
||||
mock_config_entry.runtime_data.device,
|
||||
"get_temperature_async",
|
||||
side_effect=[TimeoutError(), updated_temperatures],
|
||||
) as mock_get_temperature:
|
||||
await mock_config_entry.runtime_data.async_refresh()
|
||||
await hass.async_block_till_done()
|
||||
|
||||
assert mock_get_temperature.call_count == 2
|
||||
assert mock_config_entry.runtime_data.last_update_success is True
|
||||
assert (state := hass.states.get(ENTITY_ID))
|
||||
assert state.attributes[ATTR_TEMPERATURE] == 27.0
|
||||
@@ -16,8 +16,8 @@ from homeassistant.const import CONF_ADDRESS, CONF_PIN
|
||||
from homeassistant.core import HomeAssistant
|
||||
from homeassistant.data_entry_flow import FlowResultType
|
||||
|
||||
from . import FIXTURE_DEVICE_NAME, FIXTURE_MAC, FIXTURE_USER_INPUT
|
||||
from .conftest import FAKE_SERVICE_INFO
|
||||
from .const import FIXTURE_DEVICE_NAME, FIXTURE_MAC, FIXTURE_USER_INPUT
|
||||
|
||||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
Reference in New Issue
Block a user