@@ -68,7 +68,7 @@ def test_configure_no_proxy_in_env(monkeypatch, no_proxy_user_configuration):
6868 )
6969
7070
71- def test_create_app (event_loop ):
71+ async def test_create_app ():
7272 """Test if aiohttp server is being created successfully.
7373
7474 Checks if the aiohttp server is created successfully, routes, startup and cleanup
@@ -83,7 +83,7 @@ def test_create_app(event_loop):
8383 # By default there is 1 for clean up task
8484 assert len (test_server .on_shutdown ) == 1
8585 assert len (test_server .on_cleanup ) == 1
86- event_loop . run_until_complete ( test_server ["state" ].stop_server_tasks () )
86+ await test_server ["state" ].stop_server_tasks ()
8787
8888
8989def get_email ():
@@ -231,6 +231,60 @@ def test_marshal_error(actual_error, expected_error):
231231 assert app .marshal_error (actual_error ) == expected_error
232232
233233
234+ async def async_configure_and_start (app_instance ):
235+ """Async version of app.configure_and_start
236+ This async version is necessary for pytest testing because:
237+ 1. In the test environment, we're already running inside an event loop managed by pytest-aiohttp
238+ 2. The app.py configure_and_start uses loop.run_until_complete() which would block the test event loop
239+ 3. Using 'await' instead allows the test event loop to continue processing other tasks
240+ """
241+ # Get web logger
242+ web_logger = None if not mwi_env .is_web_logging_enabled () else app .logger
243+
244+ # Setup the session storage,
245+ # Uniqified per session to prevent multiple proxy servers on the same FQDN from interfering with each other.
246+ uniqify_session_cookie = app .secrets .token_hex ()
247+ fernet_key = app .fernet .Fernet .generate_key ()
248+ f = app .fernet .Fernet (fernet_key )
249+ app .aiohttp_session_setup (
250+ app_instance ,
251+ app .EncryptedCookieStorage (
252+ f , cookie_name = "matlab-proxy-session-" + uniqify_session_cookie
253+ ),
254+ )
255+
256+ # Setup runner
257+ runner = app .web .AppRunner (app_instance , access_log = web_logger )
258+
259+ await runner .setup ()
260+
261+ # Prepare site to start, then set port of the app.
262+ site = app .util .prepare_site (app_instance , runner )
263+
264+ # This would be required when MWI_APP_PORT env variable is not set and the site starts on a random port.
265+ app_instance ["settings" ]["app_port" ] = site ._port
266+
267+ # Update the site origin in settings.
268+ # The origin will be used for communicating with the Embedded connector.
269+ app_instance ["settings" ]["mwi_server_url" ] = app .util .get_access_url (app_instance )
270+ await site .start ()
271+
272+ app .logger .debug ("Starting MATLAB proxy app" )
273+ app .logger .debug (
274+ f" with base_url: { app_instance ['settings' ]['base_url' ]} and app_port:{ app_instance ['settings' ]['app_port' ]} ."
275+ )
276+
277+ app_instance ["state" ].create_server_info_file ()
278+
279+ # Startup tasks are being done here as app.on_startup leads
280+ # to a race condition for mwi_server_url information which is
281+ # extracted from the site info.
282+ # Use await instead of run_until_complete
283+ await app .start_background_tasks (app_instance )
284+
285+ return app_instance
286+
287+
234288class FakeServer :
235289 """Context Manager class which returns a web server wrapped in aiohttp_client pytest fixture
236290 for testing.
@@ -239,18 +293,20 @@ class FakeServer:
239293 Setting up the server in the context of Pytest.
240294 """
241295
242- def __init__ (self , event_loop , aiohttp_client ):
243- self .loop = event_loop
244- self .aiohttp_client = aiohttp_client
296+ def __init__ (self , aiohttp_client ):
297+ self .aiohttp_client = aiohttp_client # This is the pytest fixture (factory function) that is passed to the constructor
298+ self .server = None # This will hold the created server instance
299+ self .client = None # This will hold the actual client instance created by calling the factory function with our server
245300
246- def __enter__ (self ):
247- server = app .create_app ()
248- self .server = app .configure_and_start (server )
249- return self .loop .run_until_complete (self .aiohttp_client (self .server ))
301+ async def __aenter__ (self ):
302+ self .server = app .create_app ()
303+ self .server = await async_configure_and_start (self .server )
304+ self .client = await self .aiohttp_client (self .server )
305+ return self .client
250306
251- def __exit__ (self , exc_type , exc_value , exc_traceback ):
252- self .loop . run_until_complete ( self . server .shutdown () )
253- self .loop . run_until_complete ( self . server .cleanup () )
307+ async def __aexit__ (self , exc_type , exc_value , exc_traceback ):
308+ await self .server .shutdown ()
309+ await self .server .cleanup ()
254310
255311
256312@pytest .fixture
@@ -281,12 +337,11 @@ def mock_messages(mocker):
281337 ]
282338
283339
284- @pytest .fixture ( name = "test_server" )
285- def test_server_fixture ( event_loop , aiohttp_client , monkeypatch , request ):
340+ @pytest .fixture
341+ async def test_server ( aiohttp_client , monkeypatch , request ):
286342 """A pytest fixture which yields a test server to be used by tests.
287343
288344 Args:
289- loop (Event loop): The built-in event loop provided by pytest.
290345 aiohttp_client (aiohttp_client): Built-in pytest fixture used as a wrapper to the aiohttp web server.
291346
292347 Yields:
@@ -305,7 +360,7 @@ def test_server_fixture(event_loop, aiohttp_client, monkeypatch, request):
305360 monkeypatch .setenv (env_var_name , env_var_value )
306361
307362 try :
308- with FakeServer (event_loop , aiohttp_client ) as test_server :
363+ async with FakeServer (aiohttp_client ) as test_server :
309364 yield test_server
310365
311366 except ProcessLookupError :
0 commit comments