Add climate platform tests for nobo_hub (#169010)

This commit is contained in:
Øyvind Matheson Wergeland
2026-04-25 23:11:44 +02:00
committed by GitHub
parent f06cd25f4a
commit 77df31fa83
4 changed files with 303 additions and 3 deletions
@@ -134,8 +134,6 @@ class NoboZone(NoboBaseEntity, ClimateEntity):
if ATTR_TARGET_TEMP_LOW in kwargs:
low = round(kwargs[ATTR_TARGET_TEMP_LOW])
high = round(kwargs[ATTR_TARGET_TEMP_HIGH])
low = min(low, high)
high = max(low, high)
await self._nobo.async_update_zone(
self._id, temp_comfort_c=high, temp_eco_c=low
)
+13 -1
View File
@@ -1,6 +1,7 @@
"""Common fixtures for the Nobø Ecohub tests."""
from collections.abc import Generator
from typing import Any
from unittest.mock import AsyncMock, MagicMock, patch
from pynobo import nobo as pynobo_nobo
@@ -58,7 +59,17 @@ def connect_exc() -> BaseException | None:
@pytest.fixture
def mock_config_entry(ip_address: str, auto_discovered: bool) -> MockConfigEntry:
def config_entry_options() -> dict[str, Any]:
"""Return the options stored on the config entry."""
return {}
@pytest.fixture
def mock_config_entry(
ip_address: str,
auto_discovered: bool,
config_entry_options: dict[str, Any],
) -> MockConfigEntry:
"""Return a mock Nobø Ecohub config entry."""
return MockConfigEntry(
domain=DOMAIN,
@@ -69,6 +80,7 @@ def mock_config_entry(ip_address: str, auto_discovered: bool) -> MockConfigEntry
CONF_IP_ADDRESS: ip_address,
CONF_AUTO_DISCOVERED: auto_discovered,
},
options=config_entry_options,
)
@@ -0,0 +1,83 @@
# serializer version: 1
# name: test_climate_entities[climate.living_room-entry]
EntityRegistryEntrySnapshot({
'aliases': list([
None,
]),
'area_id': None,
'capabilities': dict({
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.AUTO: 'auto'>,
]),
'max_temp': 30,
'min_temp': 7,
'preset_modes': list([
'none',
'comfort',
'eco',
'away',
]),
'target_temp_step': 1,
}),
'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.living_room',
'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': 'nobo_hub',
'previous_unique_id': None,
'suggested_object_id': None,
'supported_features': <ClimateEntityFeature: 18>,
'translation_key': None,
'unique_id': '102000013098:1',
'unit_of_measurement': None,
})
# ---
# name: test_climate_entities[climate.living_room-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'current_temperature': 20.5,
'friendly_name': 'Living room',
'hvac_modes': list([
<HVACMode.HEAT: 'heat'>,
<HVACMode.AUTO: 'auto'>,
]),
'max_temp': 30,
'min_temp': 7,
'preset_mode': 'comfort',
'preset_modes': list([
'none',
'comfort',
'eco',
'away',
]),
'supported_features': <ClimateEntityFeature: 18>,
'target_temp_high': 21,
'target_temp_low': 17,
'target_temp_step': 1,
}),
'context': <ANY>,
'entity_id': 'climate.living_room',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': 'auto',
})
# ---
+207
View File
@@ -0,0 +1,207 @@
"""Tests for the Nobø Ecohub climate platform."""
from unittest.mock import MagicMock
from pynobo import nobo
import pytest
from syrupy.assertion import SnapshotAssertion
from homeassistant.components.climate import (
ATTR_CURRENT_TEMPERATURE,
ATTR_HVAC_MODE,
ATTR_PRESET_MODE,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
DOMAIN as CLIMATE_DOMAIN,
PRESET_AWAY,
PRESET_COMFORT,
PRESET_ECO,
PRESET_NONE,
SERVICE_SET_HVAC_MODE,
SERVICE_SET_PRESET_MODE,
SERVICE_SET_TEMPERATURE,
HVACMode,
)
from homeassistant.components.nobo_hub.const import (
CONF_OVERRIDE_TYPE,
OVERRIDE_TYPE_NOW,
)
from homeassistant.const import ATTR_ENTITY_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from . import fire_hub_update
from tests.common import MockConfigEntry, snapshot_platform
CLIMATE_ENTITY = "climate.living_room"
@pytest.fixture
def platforms() -> list[Platform]:
"""Only set up the climate platform for these tests."""
return [Platform.CLIMATE]
@pytest.mark.usefixtures("init_integration")
async def test_climate_entities(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
) -> None:
"""All climate entities match their snapshot."""
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.parametrize(
("zone_mode", "expected_state", "expected_preset"),
[
(nobo.API.NAME_OFF, HVACMode.OFF, PRESET_NONE),
(nobo.API.NAME_AWAY, HVACMode.AUTO, PRESET_AWAY),
(nobo.API.NAME_ECO, HVACMode.AUTO, PRESET_ECO),
(nobo.API.NAME_COMFORT, HVACMode.AUTO, PRESET_COMFORT),
],
)
@pytest.mark.usefixtures("init_integration")
async def test_state_maps_zone_mode(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
zone_mode: str,
expected_state: HVACMode,
expected_preset: str,
) -> None:
"""Zone modes map to the expected HVAC mode and preset."""
mock_nobo_hub.get_current_zone_mode.return_value = zone_mode
await fire_hub_update(hass, mock_nobo_hub)
state = hass.states.get(CLIMATE_ENTITY)
assert state.state == expected_state
assert state.attributes[ATTR_PRESET_MODE] == expected_preset
@pytest.mark.usefixtures("init_integration")
async def test_state_override_forces_heat(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
) -> None:
"""A non-normal zone override maps to HVACMode.HEAT."""
# Any non-NORMAL override value suffices; NAME_COMFORT is arbitrary.
mock_nobo_hub.get_zone_override_mode.return_value = nobo.API.NAME_COMFORT
await fire_hub_update(hass, mock_nobo_hub)
assert hass.states.get(CLIMATE_ENTITY).state == HVACMode.HEAT
@pytest.mark.usefixtures("init_integration")
async def test_current_temperature_unknown_when_missing(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
) -> None:
"""A missing current temperature surfaces as None."""
mock_nobo_hub.get_current_zone_temperature.return_value = None
await fire_hub_update(hass, mock_nobo_hub)
assert hass.states.get(CLIMATE_ENTITY).attributes[ATTR_CURRENT_TEMPERATURE] is None
@pytest.mark.parametrize(
("hvac_mode", "expected_override"),
[
(HVACMode.AUTO, nobo.API.OVERRIDE_MODE_NORMAL),
(HVACMode.HEAT, nobo.API.OVERRIDE_MODE_COMFORT),
],
)
@pytest.mark.usefixtures("init_integration")
async def test_set_hvac_mode(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
hvac_mode: HVACMode,
expected_override: str,
) -> None:
"""Each HVAC mode maps to the expected zone override."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_HVAC_MODE,
{ATTR_ENTITY_ID: CLIMATE_ENTITY, ATTR_HVAC_MODE: hvac_mode},
blocking=True,
)
mock_nobo_hub.async_create_override.assert_called_once_with(
expected_override,
nobo.API.OVERRIDE_TYPE_CONSTANT,
nobo.API.OVERRIDE_TARGET_ZONE,
"1",
)
@pytest.mark.parametrize(
("preset", "expected_mode"),
[
(PRESET_NONE, nobo.API.OVERRIDE_MODE_NORMAL),
(PRESET_COMFORT, nobo.API.OVERRIDE_MODE_COMFORT),
(PRESET_ECO, nobo.API.OVERRIDE_MODE_ECO),
(PRESET_AWAY, nobo.API.OVERRIDE_MODE_AWAY),
],
)
@pytest.mark.usefixtures("init_integration")
async def test_set_preset_mode(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
preset: str,
expected_mode: str,
) -> None:
"""Each preset maps to the expected override mode."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: CLIMATE_ENTITY, ATTR_PRESET_MODE: preset},
blocking=True,
)
mock_nobo_hub.async_create_override.assert_called_once_with(
expected_mode,
nobo.API.OVERRIDE_TYPE_CONSTANT,
nobo.API.OVERRIDE_TARGET_ZONE,
"1",
)
@pytest.mark.parametrize(
"config_entry_options",
[{CONF_OVERRIDE_TYPE: OVERRIDE_TYPE_NOW}],
)
@pytest.mark.usefixtures("init_integration")
async def test_set_preset_with_override_type_now(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
) -> None:
"""The override_type option flows into the zone override call."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_PRESET_MODE,
{ATTR_ENTITY_ID: CLIMATE_ENTITY, ATTR_PRESET_MODE: PRESET_COMFORT},
blocking=True,
)
mock_nobo_hub.async_create_override.assert_called_once_with(
nobo.API.OVERRIDE_MODE_COMFORT,
nobo.API.OVERRIDE_TYPE_NOW,
nobo.API.OVERRIDE_TARGET_ZONE,
"1",
)
@pytest.mark.usefixtures("init_integration")
async def test_set_temperature_updates_zone(
hass: HomeAssistant,
mock_nobo_hub: MagicMock,
) -> None:
"""Setting target temperatures updates the zone on the hub."""
await hass.services.async_call(
CLIMATE_DOMAIN,
SERVICE_SET_TEMPERATURE,
{
ATTR_ENTITY_ID: CLIMATE_ENTITY,
ATTR_TARGET_TEMP_LOW: 16.4,
ATTR_TARGET_TEMP_HIGH: 21.6,
},
blocking=True,
)
mock_nobo_hub.async_update_zone.assert_called_once_with(
"1", temp_comfort_c=22, temp_eco_c=16
)