Skip to content

Commit deb6950

Browse files
Luca AzzinnaroLuca Azzinnaro
authored andcommitted
test: cover repeated reservoir autofill in longterm
1 parent daf5d4a commit deb6950

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

custom_components/opengrowbox/OGBController/managers/hydro/tank/OGBReservoirManager.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,14 @@ def low_threshold(self) -> float:
9090
return float(value)
9191
except (ValueError, TypeError):
9292
return default
93+
94+
@low_threshold.setter
95+
def low_threshold(self, value: float):
96+
"""Set low threshold for compatibility with tests/direct overrides."""
97+
coerced = float(value)
98+
self._default_low = coerced
99+
if hasattr(self, "data_store") and self.data_store is not None:
100+
self.data_store.setDeep("Hydro.ReservoirMinLevel", coerced)
93101

94102
@property
95103
def high_threshold(self) -> float:
@@ -100,6 +108,15 @@ def high_threshold(self) -> float:
100108
return float(value)
101109
except (ValueError, TypeError):
102110
return default
111+
112+
@high_threshold.setter
113+
def high_threshold(self, value: float):
114+
"""Set high threshold for compatibility with tests/direct overrides."""
115+
coerced = float(value)
116+
self._default_high = coerced
117+
self._default_max_fill = coerced
118+
if hasattr(self, "data_store") and self.data_store is not None:
119+
self.data_store.setDeep("Hydro.ReservoirMaxLevel", coerced)
103120

104121
@property
105122
def max_fill_level(self) -> float:

tests/logic/longterm/test_longterm_integration.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,130 @@ async def test_extended_reservoir_management_30_days():
162162
print(f"✓ 30-day reservoir simulation: {len(refill_events)} refills, {len(alert_events)} alerts")
163163

164164

165+
@pytest.mark.longterm
166+
@pytest.mark.asyncio
167+
async def test_reservoir_autofill_handles_multiple_refills_over_time():
168+
"""
169+
Simulates repeated reservoir depletion and verifies that the real autofill loop
170+
can recover the tank multiple times without entering a blocked state.
171+
"""
172+
from custom_components.opengrowbox.OGBController.managers.hydro.tank.OGBReservoirManager import OGBReservoirManager
173+
174+
event_manager = FakeEventManager()
175+
store = FakeDataStore({
176+
"mainControl": "HomeAssistant",
177+
"Hydro": {
178+
"ReservoirLevel": 80.0,
179+
"ReservoirMinLevel": 15.0,
180+
"ReservoirMaxLevel": 85.0,
181+
},
182+
"capabilities": {
183+
"canReservoirFill": {
184+
"state": True,
185+
"count": 1,
186+
"devEntities": ["switch.test_reservoir_fill"],
187+
}
188+
},
189+
})
190+
191+
manager = OGBReservoirManager.__new__(OGBReservoirManager)
192+
manager.room = "test_room"
193+
manager.data_store = store
194+
manager.event_manager = event_manager
195+
manager.hass = MagicMock()
196+
manager.notificator = None
197+
manager._default_low = 25.0
198+
manager._default_high = 85.0
199+
manager._default_max_fill = 85.0
200+
manager.fill_step_size = 5.0
201+
manager.current_level = 80.0
202+
manager.current_level_raw = 80.0
203+
manager.level_unit = "%"
204+
manager.last_alert_time = None
205+
manager.last_alert_type = None
206+
manager.alert_cooldown = timedelta(minutes=30)
207+
manager.reservoir_sensor_entity = "sensor.test_reservoir_level"
208+
manager.reservoir_pump_entity = "switch.test_reservoir_fill"
209+
manager._is_filling = False
210+
manager._fill_cycles_completed = 0
211+
manager._fill_start_level = None
212+
manager._fill_start_time = None
213+
manager._last_fill_cycle_time = None
214+
manager._consecutive_sensor_errors = 0
215+
manager._max_sensor_errors = 2
216+
manager._fill_blocked = False
217+
manager._fill_block_reason = None
218+
manager._last_feed_mode = None
219+
manager._expected_pump_state = None
220+
221+
notifications = []
222+
refill_runs = []
223+
224+
async def _notify_user(title, message, level="info"):
225+
notifications.append({"title": title, "message": message, "level": level})
226+
227+
async def _run_fill_cycle(target_level: float) -> bool:
228+
manager.current_level = target_level
229+
store.setDeep("Hydro.ReservoirLevel", target_level)
230+
return True
231+
232+
async def _activate_pump():
233+
return None
234+
235+
async def _deactivate_pump():
236+
return None
237+
238+
async def _find_reservoir_pump(log_missing: bool = True):
239+
manager.reservoir_pump_entity = "switch.test_reservoir_fill"
240+
241+
manager._notify_user = _notify_user
242+
manager._run_fill_cycle = _run_fill_cycle
243+
manager._activate_pump = _activate_pump
244+
manager._deactivate_pump = _deactivate_pump
245+
manager._find_reservoir_pump = _find_reservoir_pump
246+
manager._log_to_client = lambda *args, **kwargs: None
247+
248+
original_sleep = asyncio.sleep
249+
250+
async def fast_sleep(_seconds):
251+
return None
252+
253+
try:
254+
asyncio.sleep = fast_sleep
255+
256+
for day in range(90):
257+
current_level = store.getDeep("Hydro.ReservoirLevel") or 80.0
258+
depleted_level = max(0.0, current_level - 3.8)
259+
store.setDeep("Hydro.ReservoirLevel", depleted_level)
260+
manager.current_level = depleted_level
261+
262+
if depleted_level < manager.low_threshold:
263+
refill_runs.append({"day": day, "start": depleted_level})
264+
await manager._auto_fill_reservoir()
265+
refill_runs[-1]["end"] = manager.current_level
266+
refill_runs[-1]["cycles"] = manager._fill_cycles_completed
267+
268+
assert len(refill_runs) >= 2, "Expected multiple refill runs over long-term depletion"
269+
assert all(run["end"] >= manager.max_fill_level for run in refill_runs)
270+
assert all(run["cycles"] >= 10 for run in refill_runs), "Each refill should require multiple 5% cycles"
271+
assert manager._fill_blocked is False, "Successful long-term refills should not block autofill"
272+
273+
start_messages = [n for n in notifications if "Fuellung gestartet" in n["message"]]
274+
progress_messages = [n for n in notifications if "Füllung läuft" in n["title"]]
275+
complete_messages = [n for n in notifications if "ABGESCHLOSSEN" in n["title"]]
276+
277+
assert len(start_messages) == len(refill_runs)
278+
assert len(complete_messages) == len(refill_runs)
279+
assert len(progress_messages) >= len(refill_runs) * 10
280+
281+
print(
282+
f"✓ Multi-refill simulation: {len(refill_runs)} refill runs, "
283+
f"{len(progress_messages)} progress notifications"
284+
)
285+
finally:
286+
asyncio.sleep = original_sleep
287+
288+
165289
@pytest.mark.longterm
166290
@pytest.mark.asyncio
167291
async def test_device_duty_cycle_tracking_14_days():

0 commit comments

Comments
 (0)