@@ -5177,15 +5177,21 @@ def test_replay_for_the_newest_id_after_an_earlier_send_applied(self):
51775177 self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
51785178 clock ["now" ] = 1030.0
51795179 self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5180+ clock ["now" ] = 1031.0
51805181 self ._ack (gw , "PBAT1" , "set_charge_rate" , applied = {"power_w" : 2000 , "slot" : 0 })
5182+ clock ["now" ] = 1059.0
51815183 self ._ack (gw , "PBAT2" , "set_charge_rate" , ok = False , error = "replay" )
51825184 entry = gw ._pending_commands [self .CHARGE_RATE ]
51835185 assert entry ["command_ids" ] == ["PBAT1" ]
51845186 assert entry ["state" ] == "applied"
51855187 assert self ._logged (gw , "already applied by another send" )
5186- clock ["now" ] = 1040.0
5188+ # The replay is a refusal too: the window runs from it
5189+ clock ["now" ] = 1060.0
51875190 self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5188- assert len (gw ._published ) == 2
5191+ assert len (gw ._published ) == 2
5192+ clock ["now" ] = 1089.0
5193+ self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5194+ assert len (gw ._published ) == 3
51895195
51905196 def test_replay_for_the_newest_id_with_only_an_older_attempts_ok_drops_the_entry (self ):
51915197 """An ok from an earlier attempt does not count: a replay of the fresh attempt's id re-sends straight away."""
@@ -5232,6 +5238,34 @@ async def failing_publish_command(command, command_id=None, **kwargs):
52325238 self ._ack (gw , "PBAT1" , "set_charge_rate" , applied = {"power_w" : 2000 , "slot" : 0 })
52335239 assert gw ._pending_commands [self .CHARGE_RATE ]["state" ] == "applied"
52345240
5241+ def test_repeated_refusal_during_a_failing_publish_is_not_rolled_back (self ):
5242+ """A refusal repeated while a later publish awaits the broker keeps its restarted window when that publish fails."""
5243+ gw = self ._make_gateway ()
5244+ clock , patcher = self ._clock ()
5245+ with patcher :
5246+ self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5247+ self ._ack (gw , "PBAT1" , "set_charge_rate" , ok = False , error = "not_managed" )
5248+
5249+ async def refusal_then_fail (command , command_id = None , ** kwargs ):
5250+ clock ["now" ] = 1035.0
5251+ self ._ack (gw , "PBAT1" , "set_charge_rate" , ok = False , error = "not_managed" )
5252+ raise RuntimeError ("broker gone" )
5253+
5254+ real_publish = gw .publish_command
5255+ gw .publish_command = refusal_then_fail
5256+ clock ["now" ] = 1031.0
5257+ try :
5258+ self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5259+ except RuntimeError :
5260+ pass
5261+ gw .publish_command = real_publish
5262+ entry = gw ._pending_commands [self .CHARGE_RATE ]
5263+ assert entry ["command_ids" ] == ["PBAT1" ]
5264+ assert entry ["sent_at" ] == 1035.0
5265+ clock ["now" ] = 1036.0
5266+ self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5267+ assert len (gw ._published ) == 1
5268+
52355269 def test_outcomes_do_not_grow_past_the_kept_ids (self ):
52365270 """Per-id outcomes are pruned with the ids, so a value re-sent for a long time does not grow the entry."""
52375271 gw = self ._make_gateway ()
@@ -5245,6 +5279,19 @@ def test_outcomes_do_not_grow_past_the_kept_ids(self):
52455279 assert len (entry ["command_ids" ]) == 16
52465280 assert set (entry ["outcomes" ]) <= set (entry ["command_ids" ])
52475281
5282+ def test_attempt_ids_do_not_grow_without_acks (self ):
5283+ """Subscribed but no acks ever: every call re-sends, and the id lists stay bounded."""
5284+ gw = self ._make_gateway (acks_seen = False )
5285+ clock , patcher = self ._clock ()
5286+ with patcher :
5287+ for step in range (50 ):
5288+ clock ["now" ] = 1000.0 + step * 2
5289+ self ._run (gw .number_event (self .CHARGE_RATE , 2000 ))
5290+ entry = gw ._pending_commands [self .CHARGE_RATE ]
5291+ assert len (gw ._published ) == 50
5292+ assert len (entry ["command_ids" ]) == 16
5293+ assert len (entry ["attempt_ids" ]) == 16
5294+
52485295 def test_second_ok_ack_does_not_overwrite_cache (self ):
52495296 """Only the first ok ack for a command updates the cached slot times."""
52505297 gw = self ._make_gateway ()
0 commit comments