Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 14 additions & 12 deletions src/bsblan/bsblan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Comment on lines +1215 to +1220
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

float() will accept non-finite values like 'nan', 'inf', or '-inf'. With the new early-return when min/max are unavailable, those values will now pass validation and be sent to the device. Consider explicitly rejecting non-finite floats (e.g., via a finite check) so only real temperatures are accepted even when range bounds are missing.

Copilot uses AI. Check for mistakes.
# 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:
Expand All @@ -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.
Expand Down
14 changes: 8 additions & 6 deletions tests/test_circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -332,19 +332,21 @@ 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,
"max": None,
}
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()
Comment on lines +343 to +349
Copy link

Copilot AI Apr 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test now only asserts _request was awaited once, but it doesn't verify the request payload (e.g., base_path and data) for circuit=2. Adding an assertion on the awaited arguments would ensure the thermostat call still sends the correct parameter ID/value when range validation is skipped.

Copilot generated this review using guidance from repository custom instructions.


# --- Validation tests ---
Expand Down
11 changes: 5 additions & 6 deletions tests/test_temperature_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading