ha-hunterbtt

Home Assistant integration for Hunter BTT
git clone https://git.stephank.nl/ha-hunterbtt
Log | Files | Refs | README | LICENSE | ZIP

test_runtime.py (2338B)


      1 """Test pure manual-watering state rules without Home Assistant dependencies."""
      2 
      3 import sys
      4 from datetime import UTC, datetime, timedelta
      5 from importlib.util import module_from_spec, spec_from_file_location
      6 from pathlib import Path
      7 
      8 import pytest
      9 
     10 _RUNTIME_PATH = Path(__file__).parents[1] / "custom_components/hunter_btt/runtime.py"
     11 _SPEC = spec_from_file_location("hunter_btt_runtime", _RUNTIME_PATH)
     12 assert _SPEC is not None and _SPEC.loader is not None
     13 _RUNTIME = module_from_spec(_SPEC)
     14 sys.modules[_SPEC.name] = _RUNTIME
     15 _SPEC.loader.exec_module(_RUNTIME)
     16 
     17 ManualControlError = _RUNTIME.ManualControlError
     18 command_end_time = _RUNTIME.command_end_time
     19 ensure_manual_start_allowed = _RUNTIME.ensure_manual_start_allowed
     20 should_send_manual_stop = _RUNTIME.should_send_manual_stop
     21 
     22 
     23 def test_start_uses_requested_duration_after_zone_confirms_watering() -> None:
     24     """Avoid FF8A's immediate stale-duration response after an HA start."""
     25     now = datetime(2026, 8, 31, 12, tzinfo=UTC)
     26     stale_reported_end = now + timedelta(minutes=10)
     27 
     28     end_time = command_end_time(True, now, 5 * 60, stale_reported_end)
     29 
     30     assert end_time == now + timedelta(minutes=5)
     31 
     32 
     33 def test_command_end_time_keeps_device_value_without_confirmed_start() -> None:
     34     """Use device data for a stop or an unconfirmed start."""
     35     now = datetime(2026, 8, 31, 12, tzinfo=UTC)
     36     reported_end = now + timedelta(minutes=10)
     37 
     38     assert command_end_time(False, now, 5 * 60, reported_end) == reported_end
     39     assert command_end_time(True, now, None, reported_end) == reported_end
     40 
     41 
     42 @pytest.mark.parametrize("is_closed", [False, None])
     43 def test_start_requires_a_confirmed_closed_valve(is_closed: bool | None) -> None:
     44     """Do not silently restart a run or command a valve with unknown state."""
     45     with pytest.raises(ManualControlError):
     46         ensure_manual_start_allowed(is_closed)
     47 
     48 
     49 def test_start_is_allowed_when_valve_is_confirmed_closed() -> None:
     50     """Allow the expected idle-to-watering transition."""
     51     ensure_manual_start_allowed(True)
     52 
     53 
     54 def test_stop_only_writes_when_valve_is_confirmed_open() -> None:
     55     """Keep close idempotent but reject a stop with unknown state."""
     56     assert should_send_manual_stop(False)
     57     assert not should_send_manual_stop(True)
     58     with pytest.raises(ManualControlError):
     59         should_send_manual_stop(None)