@@ -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
167291async def test_device_duty_cycle_tracking_14_days ():
0 commit comments