@@ -428,3 +428,98 @@ async def iter_chunks(self):
428428
429429 client .handle_response_exception .assert_awaited_once ()
430430 mock_response .release .assert_called_once ()
431+
432+
433+ @pytest .mark .asyncio
434+ async def test_stream_releases_conn_on_error_status ():
435+ """Ensure release() is called even when handle_response_exception raises,
436+ so the connection is returned to the pool and not leaked."""
437+ mock_config = MagicMock ()
438+ mock_config .ssl_ca_cert = None
439+ mock_config .cert_file = None
440+ mock_config .key_file = None
441+ mock_config .verify_ssl = True
442+ mock_config .connection_pool_maxsize = 4
443+ mock_config .proxy = None
444+ mock_config .proxy_headers = None
445+ mock_config .timeout_millisec = 5000
446+
447+ client = RESTClientObject (configuration = mock_config )
448+ mock_session = MagicMock ()
449+ client .pool_manager = mock_session
450+
451+ class FakeContent :
452+ async def iter_chunks (self ):
453+ yield (b'{"ok":true}\n ' , None )
454+
455+ mock_response = MagicMock ()
456+ mock_response .status = 500
457+ mock_response .reason = "Internal Server Error"
458+ mock_response .data = None
459+ mock_response .content = FakeContent ()
460+
461+ mock_context_manager = AsyncMock ()
462+ mock_context_manager .__aenter__ .return_value = mock_response
463+ mock_context_manager .__aexit__ .return_value = None
464+
465+ mock_session .request .return_value = mock_context_manager
466+
467+ # Make handle_response_exception raise an exception
468+ client .handle_response_exception = AsyncMock (
469+ side_effect = ServiceException (status = 500 , reason = "Internal Server Error" )
470+ )
471+ client .close = AsyncMock ()
472+
473+ results = []
474+ with pytest .raises (ServiceException ):
475+ async for item in client .stream ("GET" , "http://example.com" ):
476+ results .append (item )
477+
478+ # The critical assertion: release() must be called even though
479+ # handle_response_exception raised ServiceException
480+ mock_response .release .assert_called_once ()
481+
482+
483+ @pytest .mark .asyncio
484+ async def test_stream_releases_conn_on_success ():
485+ """Ensure release() is called on successful stream completion."""
486+ mock_config = MagicMock ()
487+ mock_config .ssl_ca_cert = None
488+ mock_config .cert_file = None
489+ mock_config .key_file = None
490+ mock_config .verify_ssl = True
491+ mock_config .connection_pool_maxsize = 4
492+ mock_config .proxy = None
493+ mock_config .proxy_headers = None
494+ mock_config .timeout_millisec = 5000
495+
496+ client = RESTClientObject (configuration = mock_config )
497+ mock_session = MagicMock ()
498+ client .pool_manager = mock_session
499+
500+ class FakeContent :
501+ async def iter_chunks (self ):
502+ yield (b'{"ok":true}\n ' , None )
503+
504+ mock_response = MagicMock ()
505+ mock_response .status = 200
506+ mock_response .reason = "OK"
507+ mock_response .data = None
508+ mock_response .content = FakeContent ()
509+
510+ mock_context_manager = AsyncMock ()
511+ mock_context_manager .__aenter__ .return_value = mock_response
512+ mock_context_manager .__aexit__ .return_value = None
513+
514+ mock_session .request .return_value = mock_context_manager
515+
516+ client .handle_response_exception = AsyncMock ()
517+ client .close = AsyncMock ()
518+
519+ results = []
520+ async for item in client .stream ("GET" , "http://example.com" ):
521+ results .append (item )
522+
523+ assert results == [{"ok" : True }]
524+ client .handle_response_exception .assert_awaited_once ()
525+ mock_response .release .assert_called_once ()
0 commit comments