From 1b35a9036f5608f2e87924aac8a5fce5f4bb7a22 Mon Sep 17 00:00:00 2001 From: Willem-Jan van Rootselaar Date: Sat, 11 Apr 2026 10:58:32 +0200 Subject: [PATCH] fix: skip temperature range validation when device has no min/max params Previously _validate_target_temperature raised BSBLANError when the device did not provide min/max temperature parameters. Now it validates the value is a valid float and skips range checking when min/max are unavailable, allowing the temperature to pass through to the device. --- src/bsblan/bsblan.py | 26 ++++++++++++++------------ tests/test_circuit.py | 14 ++++++++------ tests/test_temperature_validation.py | 11 +++++------ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/bsblan/bsblan.py b/src/bsblan/bsblan.py index 249b8ac5..728c84a4 100644 --- a/src/bsblan/bsblan.py +++ b/src/bsblan/bsblan.py @@ -1201,24 +1201,29 @@ async def _validate_target_temperature( """Validate the target temperature. This method lazy-loads the temperature range if not already initialized. + If the device does not provide min/max temperature parameters, + only validates that the value is a valid float. Args: target_temperature (str): The target temperature to validate. circuit: The heating circuit number (1 or 2). Raises: - BSBLANError: If the temperature range cannot be initialized. BSBLANInvalidParameterError: If the target temperature is invalid. """ + # Validate it's a valid float first + try: + temp = float(target_temperature) + except ValueError as err: + raise BSBLANInvalidParameterError(target_temperature) from err + + # Try to load temperature range for bounds checking if circuit == 1: # HC1 uses legacy fields for backwards compatibility if self._min_temp is None or self._max_temp is None: await self._initialize_temperature_range(circuit) - if self._min_temp is None or self._max_temp is None: - raise BSBLANError(ErrorMsg.TEMPERATURE_RANGE) - min_temp = self._min_temp max_temp = self._max_temp else: @@ -1230,15 +1235,12 @@ async def _validate_target_temperature( min_temp = temp_range.get("min") max_temp = temp_range.get("max") - if min_temp is None or max_temp is None: - raise BSBLANError(ErrorMsg.TEMPERATURE_RANGE) + # Skip range validation if device doesn't provide min/max + if min_temp is None or max_temp is None: + return - try: - temp = float(target_temperature) - if not (min_temp <= temp <= max_temp): - raise BSBLANInvalidParameterError(target_temperature) - except ValueError as err: - raise BSBLANInvalidParameterError(target_temperature) from err + if not (min_temp <= temp <= max_temp): + raise BSBLANInvalidParameterError(target_temperature) def _validate_hvac_mode(self, hvac_mode: int) -> None: """Validate the HVAC mode. diff --git a/tests/test_circuit.py b/tests/test_circuit.py index 913a1169..79e5db0e 100644 --- a/tests/test_circuit.py +++ b/tests/test_circuit.py @@ -332,7 +332,7 @@ async def test_thermostat_circuit2_invalid_temperature( async def test_thermostat_circuit2_no_temp_range( mock_bsblan_circuit: BSBLAN, ) -> None: - """Test error when HC2 temp range not available.""" + """Test thermostat passes through when HC2 temp range not available.""" # Set HC2 as initialized but with None range mock_bsblan_circuit._circuit_temp_ranges[2] = { "min": None, @@ -340,11 +340,13 @@ async def test_thermostat_circuit2_no_temp_range( } mock_bsblan_circuit._circuit_temp_initialized.add(2) - with pytest.raises(BSBLANError, match="Temperature range"): - await mock_bsblan_circuit.thermostat( - target_temperature="20", - circuit=2, - ) + # Should pass through without range validation when min/max are None + mock_bsblan_circuit._request = AsyncMock(return_value={"status": "success"}) + await mock_bsblan_circuit.thermostat( + target_temperature="20", + circuit=2, + ) + mock_bsblan_circuit._request.assert_awaited_once() # --- Validation tests --- diff --git a/tests/test_temperature_validation.py b/tests/test_temperature_validation.py index dd6290b3..8e53cf2a 100644 --- a/tests/test_temperature_validation.py +++ b/tests/test_temperature_validation.py @@ -9,26 +9,25 @@ from bsblan import BSBLAN from bsblan.bsblan import BSBLANConfig -from bsblan.constants import API_VERSIONS, APIConfig, ErrorMsg +from bsblan.constants import API_VERSIONS, APIConfig from bsblan.exceptions import BSBLANError, BSBLANInvalidParameterError from bsblan.utility import APIValidator @pytest.mark.asyncio async def test_validate_target_temperature_no_range() -> None: - """Test validating target temperature with temperature range not initialized.""" + """Test validating target temperature when device has no min/max params.""" config = BSBLANConfig(host="example.com") bsblan = BSBLAN(config) - # Mock _initialize_temperature_range to do nothing (simulate failure) + # Mock _initialize_temperature_range to do nothing (simulate no min/max) async def mock_init_temp_range(circuit: int = 1) -> None: pass bsblan._initialize_temperature_range = mock_init_temp_range # type: ignore[method-assign] - # Temperature range is not initialized by default - with pytest.raises(BSBLANError, match=ErrorMsg.TEMPERATURE_RANGE): - await bsblan._validate_target_temperature("22.0") + # Should pass through without error when min/max are not available + await bsblan._validate_target_temperature("22.0") @pytest.mark.asyncio