@@ -323,6 +323,57 @@ async def test_consume_all_continues_on_queue_empty_if_not_really_closed(
323323 # The second QueueClosed is not reached because Message breaks the loop.
324324 assert mock_event_queue .is_closed .call_count == 1
325325
326+ @pytest .mark .asyncio
327+ async def test_consume_all_handles_queue_empty_when_closed_python_version_agnostic (
328+ event_consumer : EventConsumer , mock_event_queue : AsyncMock , monkeypatch
329+ ):
330+ """Ensure consume_all stops with no events when queue is closed and dequeue_event raises asyncio.QueueEmpty (Python version-agnostic)."""
331+ # Make QueueClosed a distinct exception (not QueueEmpty) to emulate py3.13 semantics
332+ from a2a .server .events import event_consumer as ec
333+
334+ class QueueShutDown (Exception ):
335+ pass
336+
337+ monkeypatch .setattr (ec , "QueueClosed" , QueueShutDown , raising = True )
338+
339+ # Simulate queue reporting closed while dequeue raises QueueEmpty
340+ mock_event_queue .dequeue_event .side_effect = asyncio .QueueEmpty ("closed/empty" )
341+ mock_event_queue .is_closed .return_value = True
342+
343+
344+ consumed_events = []
345+ async for event in event_consumer .consume_all ():
346+ consumed_events .append (event )
347+
348+ assert consumed_events == []
349+ mock_event_queue .dequeue_event .assert_called_once ()
350+ mock_event_queue .is_closed .assert_called_once ()
351+
352+
353+ @pytest .mark .asyncio
354+ async def test_consume_all_continues_on_queue_empty_when_not_closed (
355+ event_consumer : EventConsumer , mock_event_queue : AsyncMock , monkeypatch
356+ ):
357+ """Ensure consume_all continues after asyncio.QueueEmpty when queue is open, yielding the next (final) event."""
358+ # First dequeue raises QueueEmpty (transient empty), then a final Message arrives
359+ final = Message (role = 'agent' , parts = [{'text' : 'done' }], message_id = 'final' )
360+ mock_event_queue .dequeue_event .side_effect = [
361+ asyncio .QueueEmpty ('temporarily empty' ),
362+ final ,
363+ ]
364+ mock_event_queue .is_closed .return_value = False
365+
366+ # Make the polling responsive in tests
367+ event_consumer ._timeout = 0.001
368+
369+ consumed = []
370+ async for ev in event_consumer .consume_all ():
371+ consumed .append (ev )
372+
373+ assert consumed == [final ]
374+ assert mock_event_queue .dequeue_event .call_count == 2
375+ mock_event_queue .is_closed .assert_called_once ()
376+
326377
327378def test_agent_task_callback_sets_exception (event_consumer : EventConsumer ):
328379 """Test that agent_task_callback sets _exception if the task had one."""
0 commit comments