55# pyright: basic
66
77import asyncio
8- from datetime import datetime , timedelta
98from unittest .mock import MagicMock
109
1110import pytest
@@ -35,13 +34,12 @@ def mock_api_client(self):
3534 client .conversations = mock_conversations
3635
3736 client .send_call_count = 0
38- client .send_times = []
3937 client .sent_activities = []
4038
4139 async def mock_send (activity ):
4240 client .send_call_count += 1
43- client .send_times .append (datetime .now ())
4441 client .sent_activities .append (activity )
42+ await asyncio .sleep (0.05 ) # Simulate network delay
4543 return SentActivity (id = f"test-id-{ client .send_call_count } " , activity_params = activity )
4644
4745 client .conversations .activities ().create = mock_send
@@ -63,37 +61,45 @@ def http_stream(self, mock_api_client, conversation_reference, mock_logger):
6361 return HttpStream (mock_api_client , conversation_reference , mock_logger )
6462
6563 @pytest .mark .asyncio
66- async def test_stream_emit_message_flushes_after_500ms (self , mock_api_client , conversation_reference , mock_logger ):
67- """Test that messages are flushed after 500ms timeout ."""
64+ async def test_stream_emit_message_flushes_immediately (self , mock_api_client , conversation_reference , mock_logger ):
65+ """Test that messages are flushed immediately ."""
6866
6967 stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
70- start_time = datetime .now ()
71-
7268 stream .emit ("Test message" )
73- await asyncio .sleep (0.6 ) # Wait longer than 500ms timeout
74-
75- assert mock_api_client .send_call_count > 0 , "Should have sent at least one message"
76- assert any (t >= start_time + timedelta (milliseconds = 450 ) for t in mock_api_client .send_times ), (
77- "Should have waited approximately 500ms before sending"
78- )
69+ await asyncio .sleep (0.07 ) # Wait for the flush task to complete
70+ assert mock_api_client .send_call_count == 1
7971
8072 @pytest .mark .asyncio
81- async def test_stream_multiple_emits_restarts_timer (self , mock_api_client , conversation_reference , mock_logger ):
73+ async def test_stream_multiple_emits_timer_check (self , mock_api_client , conversation_reference , mock_logger ):
8274 """Test that multiple emits reset the timer."""
8375
8476 stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
8577
8678 stream .emit ("First message" )
87- await asyncio .sleep (0.3 ) # Wait less than 500ms
88-
89- stream .emit ("Second message" ) # This should reset the timer
90- await asyncio .sleep (0.3 ) # Still less than 500ms from second emit
91- assert mock_api_client .send_call_count == 0 , "Should not have sent yet"
92- await asyncio .sleep (0.3 ) # Now over 500ms from second emit
93- assert mock_api_client .send_call_count > 0 , "Should have sent messages after timer expired"
79+ stream .emit ("Second message" )
80+ stream .emit ("Third message" )
81+ stream .emit ("Fourth message" )
82+ stream .emit ("Fifth message" )
83+ stream .emit ("Sixth message" )
84+ stream .emit ("Seventh message" )
85+ stream .emit ("Eighth message" )
86+ stream .emit ("Ninth message" )
87+ stream .emit ("Tenth message" )
88+ stream .emit ("Eleventh message" )
89+ stream .emit ("Twelfth message" )
90+
91+ await asyncio .sleep (0.07 ) # Wait for the flush task to complete
92+ assert mock_api_client .send_call_count == 1 # First message should trigger flush immediately
93+
94+ stream .emit ("Thirteenth message" )
95+ await asyncio .sleep (0.3 ) # Less than 500ms from first flush
96+ assert mock_api_client .send_call_count == 1 , "No new flush should have occurred yet"
97+
98+ await asyncio .sleep (0.3 ) # Now exceed 500ms from last emit
99+ assert mock_api_client .send_call_count == 2 , "Second flush should have occurred"
94100
95101 @pytest .mark .asyncio
96- async def test_stream_send_timeout_handled_gracefully (self , mock_api_client , conversation_reference , mock_logger ):
102+ async def test_stream_error_handled_gracefully (self , mock_api_client , conversation_reference , mock_logger ):
97103 """Test that send timeouts are handled gracefully with retries."""
98104 call_count = 0
99105
@@ -104,14 +110,15 @@ async def mock_send_with_timeout(activity):
104110 raise TimeoutError ("Operation timed out" )
105111
106112 # Succeed on second attempt
113+ await asyncio .sleep (0.05 ) # Simulate delay
107114 return SentActivity (id = f"success-after-timeout-{ call_count } " , activity_params = activity )
108115
109116 mock_api_client .conversations .activities ().create = mock_send_with_timeout
110117
111118 stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
112119
113120 stream .emit ("Test message with timeout" )
114- await asyncio .sleep (0.8 ) # Wait for flush and retries
121+ await asyncio .sleep (0.6 ) # Wait for flush and 1 retry to complete
115122
116123 result = await stream .close ()
117124
@@ -148,7 +155,7 @@ async def test_stream_update_status_sends_typing_activity(
148155 stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
149156
150157 stream .update ("Thinking..." )
151- await asyncio .sleep (0.6 ) # Wait for the flush task to complete
158+ await asyncio .sleep (0.07 ) # Wait for the flush task to complete
152159
153160 assert stream .count > 0 or len (mock_api_client .sent_activities ) > 0 , "Should have processed the update"
154161 assert stream .sequence >= 2 , "Should increment sequence after sending"
@@ -167,10 +174,9 @@ async def test_stream_sequence_of_update_and_emit(self, mock_api_client, convers
167174 stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
168175
169176 stream .update ("Preparing response..." )
170- await asyncio .sleep (0.6 )
171-
172177 stream .emit ("Final response message" )
173- await asyncio .sleep (0.6 )
178+
179+ await asyncio .sleep (0.5 ) # Wait for the flush task to complete
174180
175181 assert len (mock_api_client .sent_activities ) >= 2 , "Should have sent typing activity and message"
176182
@@ -186,3 +192,41 @@ async def test_stream_sequence_of_update_and_emit(self, mock_api_client, convers
186192
187193 # Sequence numbers should have increased
188194 assert stream .sequence >= 3 , "Sequence should increment for both update and emit"
195+
196+ @pytest .mark .asyncio
197+ async def test_stream_concurrent_emits_do_not_flush_simultaneously (
198+ self , mock_api_client , conversation_reference , mock_logger
199+ ):
200+ """
201+ Test that multiple concurrent emits do not allow simultaneous flush execution.
202+ """
203+ concurrent_entries = 0
204+ max_concurrent_entries = 0
205+ lock = asyncio .Lock ()
206+
207+ async def mock_send (activity ):
208+ nonlocal concurrent_entries , max_concurrent_entries
209+ async with lock :
210+ concurrent_entries += 1
211+ max_concurrent_entries = max (max_concurrent_entries , concurrent_entries )
212+ await asyncio .sleep (0.05 ) # simulate delay in sending
213+ async with lock :
214+ concurrent_entries -= 1
215+ return activity
216+
217+ mock_api_client .conversations .activities ().create = mock_send
218+
219+ stream = HttpStream (mock_api_client , conversation_reference , mock_logger )
220+
221+ # Schedule multiple emits concurrently
222+ async def emit_task ():
223+ stream .emit ("Concurrent message" )
224+
225+ tasks = [asyncio .create_task (emit_task ()) for _ in range (10 )]
226+ await asyncio .gather (* tasks )
227+
228+ # Wait for flushes to complete
229+ await asyncio .sleep (0.07 )
230+
231+ # Only one flush should have entered the critical section at a time
232+ assert max_concurrent_entries == 1 , f"Flush entered concurrently { max_concurrent_entries } times, expected 1"
0 commit comments