@@ -60,8 +60,25 @@ def setUp(self):
6060 update_interval = 0.1
6161 )
6262
63+ # Set up metrics collector mock to avoid real background processes
64+ self .metrics_collector_mock = Mock ()
65+ self .metrics_collector_mock .record_api_request_time = Mock ()
66+
67+ # Start the patch
68+ self .metrics_collector_patch = patch (
69+ 'conductor.client.automator.task_runner_asyncio.MetricsCollector' ,
70+ return_value = self .metrics_collector_mock
71+ )
72+ self .metrics_collector_patch .start ()
73+
6374 def tearDown (self ):
6475 """Clean up test fixtures"""
76+ # Reset the mock for next test
77+ self .metrics_collector_mock .reset_mock ()
78+
79+ # Stop the patch
80+ self .metrics_collector_patch .stop ()
81+
6582 if os .path .exists (self .metrics_dir ):
6683 shutil .rmtree (self .metrics_dir )
6784
@@ -73,9 +90,6 @@ def test_api_timing_successful_poll(self):
7390 metrics_settings = self .metrics_settings
7491 )
7592
76- # Mock the metrics_collector's record method
77- runner .metrics_collector .record_api_request_time = Mock ()
78-
7993 # Mock successful HTTP response
8094 mock_response = Mock ()
8195 mock_response .status_code = 200
@@ -89,8 +103,8 @@ async def run_test():
89103 await runner ._poll_tasks_from_server (count = 1 )
90104
91105 # Verify API timing was recorded
92- runner . metrics_collector .record_api_request_time .assert_called ()
93- call_args = runner . metrics_collector .record_api_request_time .call_args
106+ self . metrics_collector_mock .record_api_request_time .assert_called ()
107+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
94108
95109 # Check parameters
96110 self .assertEqual (call_args .kwargs ['method' ], 'GET' )
@@ -109,9 +123,6 @@ def test_api_timing_failed_poll_with_status_code(self):
109123 metrics_settings = self .metrics_settings
110124 )
111125
112- # Mock the metrics_collector\'s record method
113- runner .metrics_collector .record_api_request_time = Mock ()
114-
115126 # Mock HTTP error with response
116127 mock_response = Mock ()
117128 mock_response .status_code = 500
@@ -128,8 +139,8 @@ async def run_test():
128139 pass
129140
130141 # Verify API timing was recorded with error status
131- runner . metrics_collector .record_api_request_time .assert_called ()
132- call_args = runner . metrics_collector .record_api_request_time .call_args
142+ self . metrics_collector_mock .record_api_request_time .assert_called ()
143+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
133144
134145 self .assertEqual (call_args .kwargs ['method' ], 'GET' )
135146 self .assertEqual (call_args .kwargs ['status' ], '500' )
@@ -145,9 +156,6 @@ def test_api_timing_failed_poll_without_status_code(self):
145156 metrics_settings = self .metrics_settings
146157 )
147158
148- # Mock the metrics_collector\'s record method
149- runner .metrics_collector .record_api_request_time = Mock ()
150-
151159 # Mock generic network error
152160 error = httpx .ConnectError ("Connection refused" )
153161
@@ -162,8 +170,8 @@ async def run_test():
162170 pass
163171
164172 # Verify API timing was recorded with "error" status
165- runner . metrics_collector .record_api_request_time .assert_called ()
166- call_args = runner . metrics_collector .record_api_request_time .call_args
173+ self . metrics_collector_mock .record_api_request_time .assert_called ()
174+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
167175
168176 self .assertEqual (call_args .kwargs ['method' ], 'GET' )
169177 self .assertEqual (call_args .kwargs ['status' ], 'error' )
@@ -178,8 +186,6 @@ def test_api_timing_successful_update(self):
178186 metrics_settings = self .metrics_settings
179187 )
180188
181- # Mock the metrics_collector's record method
182- runner .metrics_collector .record_api_request_time = Mock ()
183189
184190 # Create task result
185191 task_result = TaskResult (
@@ -202,8 +208,8 @@ async def run_test():
202208 await runner ._update_task (task_result )
203209
204210 # Verify API timing was recorded
205- runner . metrics_collector .record_api_request_time .assert_called ()
206- call_args = runner . metrics_collector .record_api_request_time .call_args
211+ self . metrics_collector_mock .record_api_request_time .assert_called ()
212+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
207213
208214 self .assertEqual (call_args .kwargs ['method' ], 'POST' )
209215 self .assertIn ('/tasks/update' , call_args .kwargs ['uri' ])
@@ -220,37 +226,40 @@ def test_api_timing_failed_update(self):
220226 metrics_settings = self .metrics_settings
221227 )
222228
223- # Mock the metrics_collector's record method
224- runner .metrics_collector .record_api_request_time = Mock ()
225-
226229 # Create task result with required fields
227230 task_result = TaskResult (
228231 task_id = 'task1' ,
229232 workflow_instance_id = 'wf1' ,
230233 status = TaskResultStatus .COMPLETED
231234 )
232235
233- # Mock HTTP error
234- mock_response = Mock ()
235- mock_response .status_code = 503
236- error = httpx .HTTPStatusError ("Service unavailable" , request = Mock (), response = mock_response )
236+ # Mock HTTP error for first call, then success to avoid retries
237+ mock_error_response = Mock ()
238+ mock_error_response .status_code = 503
239+ error = httpx .HTTPStatusError ("Service unavailable" , request = Mock (), response = mock_error_response )
240+
241+ mock_success_response = Mock ()
242+ mock_success_response .status_code = 200
243+ mock_success_response .text = ''
237244
238245 async def run_test ():
239246 runner .http_client = AsyncMock ()
240- runner .http_client .post = AsyncMock (side_effect = error )
247+ # First call fails with 503, second call succeeds (to avoid 14s of retries)
248+ runner .http_client .post = AsyncMock (side_effect = [error , mock_success_response ])
241249
242- # Call update (only needs task_result)
243- try :
250+ # Mock asyncio.sleep to avoid waiting during retry
251+ with patch ('asyncio.sleep' , new_callable = AsyncMock ):
252+ # Call update - will fail once then succeed on retry
244253 await runner ._update_task (task_result )
245- except :
246- pass
247254
248- # Verify API timing was recorded
249- runner . metrics_collector . record_api_request_time . assert_called ()
250- call_args = runner . metrics_collector .record_api_request_time .call_args
255+ # Verify API timing was recorded for the failed request
256+ # The first call should have recorded the 503 error
257+ self . metrics_collector_mock .record_api_request_time .assert_called ()
251258
252- self .assertEqual (call_args .kwargs ['method' ], 'POST' )
253- self .assertEqual (call_args .kwargs ['status' ], '503' )
259+ # Check the first call (which failed)
260+ first_call = self .metrics_collector_mock .record_api_request_time .call_args_list [0 ]
261+ self .assertEqual (first_call .kwargs ['method' ], 'POST' )
262+ self .assertEqual (first_call .kwargs ['status' ], '503' )
254263
255264 asyncio .run (run_test ())
256265
@@ -262,8 +271,6 @@ def test_api_timing_multiple_requests(self):
262271 metrics_settings = self .metrics_settings
263272 )
264273
265- # Mock the metrics_collector's record method
266- runner .metrics_collector .record_api_request_time = Mock ()
267274
268275 mock_response = Mock ()
269276 mock_response .status_code = 200
@@ -279,10 +286,10 @@ async def run_test():
279286 await runner ._poll_tasks_from_server (count = 1 )
280287
281288 # Should have 3 API timing records
282- self .assertEqual (runner . metrics_collector .record_api_request_time .call_count , 3 )
289+ self .assertEqual (self . metrics_collector_mock .record_api_request_time .call_count ,3 )
283290
284291 # All should be successful
285- for call in runner . metrics_collector .record_api_request_time .call_args_list :
292+ for call in self . metrics_collector_mock .record_api_request_time .call_args_list :
286293 self .assertEqual (call .kwargs ['status' ], '200' )
287294
288295 asyncio .run (run_test ())
@@ -318,9 +325,6 @@ def test_api_timing_precision(self):
318325 metrics_settings = self .metrics_settings
319326 )
320327
321- # Mock the metrics_collector\'s record method
322- runner .metrics_collector .record_api_request_time = Mock ()
323-
324328 # Mock fast response
325329 mock_response = Mock ()
326330 mock_response .status_code = 200
@@ -339,7 +343,7 @@ async def mock_get(*args, **kwargs):
339343 await runner ._poll_tasks_from_server (count = 1 )
340344
341345 # Verify timing captured sub-second precision
342- call_args = runner . metrics_collector .record_api_request_time .call_args
346+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
343347 time_spent = call_args .kwargs ['time_spent' ]
344348
345349 # Should be at least 1ms, but less than 100ms
@@ -356,8 +360,6 @@ def test_api_timing_auth_error_401(self):
356360 metrics_settings = self .metrics_settings
357361 )
358362
359- # Mock the metrics_collector's record method
360- runner .metrics_collector .record_api_request_time = Mock ()
361363
362364 mock_response = Mock ()
363365 mock_response .status_code = 401
@@ -373,7 +375,7 @@ async def run_test():
373375 pass
374376
375377 # Verify 401 status captured
376- call_args = runner . metrics_collector .record_api_request_time .call_args
378+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
377379 self .assertEqual (call_args .kwargs ['status' ], '401' )
378380
379381 asyncio .run (run_test ())
@@ -386,8 +388,6 @@ def test_api_timing_timeout_error(self):
386388 metrics_settings = self .metrics_settings
387389 )
388390
389- # Mock the metrics_collector's record method
390- runner .metrics_collector .record_api_request_time = Mock ()
391391
392392 error = httpx .TimeoutException ("Request timeout" )
393393
@@ -401,7 +401,7 @@ async def run_test():
401401 pass
402402
403403 # Verify "error" status for timeout
404- call_args = runner . metrics_collector .record_api_request_time .call_args
404+ call_args = self . metrics_collector_mock .record_api_request_time .call_args
405405 self .assertEqual (call_args .kwargs ['status' ], 'error' )
406406
407407 asyncio .run (run_test ())
@@ -414,8 +414,6 @@ def test_api_timing_concurrent_requests(self):
414414 metrics_settings = self .metrics_settings
415415 )
416416
417- # Mock the metrics_collector's record method
418- runner .metrics_collector .record_api_request_time = Mock ()
419417
420418 mock_response = Mock ()
421419 mock_response .status_code = 200
@@ -431,10 +429,22 @@ async def run_test():
431429 ])
432430
433431 # Should have 5 timing records
434- self .assertEqual (runner . metrics_collector .record_api_request_time .call_count , 5 )
432+ self .assertEqual (self . metrics_collector_mock .record_api_request_time .call_count ,5 )
435433
436434 asyncio .run (run_test ())
437435
438436
437+ def tearDownModule ():
438+ """Module-level teardown to clean up any lingering resources"""
439+ import gc
440+ import time
441+
442+ # Force garbage collection
443+ gc .collect ()
444+
445+ # Small delay to let async resources clean up
446+ time .sleep (0.1 )
447+
448+
439449if __name__ == '__main__' :
440450 unittest .main ()
0 commit comments