1- # Copyright 2020-2024 The MathWorks, Inc.
1+ # Copyright 2020-2025 The MathWorks, Inc.
22
33import asyncio
44import datetime
99from datetime import timedelta , timezone
1010from http import HTTPStatus
1111
12- import aiohttp
1312import pytest
14- import tests .unit .test_constants as test_constants
13+ from aiohttp import WSMsgType
14+ from aiohttp .web import WebSocketResponse
15+ from multidict import CIMultiDict
1516
17+ import tests .unit .test_constants as test_constants
1618from matlab_proxy import app , util
19+ from matlab_proxy .app import matlab_view
1720from matlab_proxy .util .mwi import environment_variables as mwi_env
1821from matlab_proxy .util .mwi .exceptions import EntitlementError , MatlabInstallError
22+ from tests .unit .fixtures .fixture_auth import patch_authenticate_access_decorator
23+ from tests .unit .mocks .mock_client import MockWebSocketClient
1924
2025
2126@pytest .mark .parametrize (
@@ -61,7 +66,7 @@ def test_configure_no_proxy_in_env(monkeypatch, no_proxy_user_configuration):
6166 )
6267
6368
64- def test_create_app (loop ):
69+ def test_create_app (event_loop ):
6570 """Test if aiohttp server is being created successfully.
6671
6772 Checks if the aiohttp server is created successfully, routes, startup and cleanup
@@ -75,7 +80,7 @@ def test_create_app(loop):
7580 # Verify app server has a cleanup task
7681 # By default there is 1 for clean up task
7782 assert len (test_server .on_cleanup ) > 1
78- loop .run_until_complete (test_server ["state" ].stop_server_tasks ())
83+ event_loop .run_until_complete (test_server ["state" ].stop_server_tasks ())
7984
8085
8186def get_email ():
@@ -245,9 +250,33 @@ def __exit__(self, exc_type, exc_value, exc_traceback):
245250 self .loop .run_until_complete (self .server .cleanup ())
246251
247252
253+ @pytest .fixture
254+ def mock_request (mocker ):
255+ """Creates a mock request with required attributes"""
256+ req = mocker .MagicMock ()
257+ req .app = {
258+ "state" : mocker .MagicMock (matlab_port = 8000 ),
259+ "settings" : {"matlab_protocol" : "http" , "mwapikey" : "test-key" },
260+ }
261+ req .headers = CIMultiDict ()
262+ req .cookies = {}
263+ return req
264+
265+
266+ @pytest .fixture (name = "mock_websocket_messages" )
267+ def mock_messages (mocker ):
268+ # Mock WebSocket messages
269+ return [
270+ mocker .MagicMock (type = WSMsgType .TEXT , data = "test message" ),
271+ mocker .MagicMock (type = WSMsgType .BINARY , data = b"test binary" ),
272+ mocker .MagicMock (type = WSMsgType .PING ),
273+ mocker .MagicMock (type = WSMsgType .PONG ),
274+ ]
275+
276+
248277@pytest .fixture (name = "test_server" )
249278def test_server_fixture (
250- loop ,
279+ event_loop ,
251280 aiohttp_client ,
252281 monkeypatch ,
253282):
@@ -263,7 +292,7 @@ def test_server_fixture(
263292 # Disabling the authentication token mechanism explicitly
264293 monkeypatch .setenv (mwi_env .get_env_name_enable_mwi_auth_token (), "False" )
265294 try :
266- with FakeServer (loop , aiohttp_client ) as test_server :
295+ with FakeServer (event_loop , aiohttp_client ) as test_server :
267296 yield test_server
268297 except ProcessLookupError :
269298 pass
@@ -350,7 +379,7 @@ async def test_start_matlab_route(test_server):
350379 await __check_for_matlab_status (test_server , "starting" )
351380
352381
353- async def __check_for_matlab_status (test_server , status ):
382+ async def __check_for_matlab_status (test_server , status , sleep_interval = 0.5 ):
354383 """Helper function to check if the status of MATLAB returned by the server is either of the values mentioned in statuses
355384
356385 Args:
@@ -369,7 +398,7 @@ async def __check_for_matlab_status(test_server, status):
369398 break
370399 else :
371400 count += 1
372- await asyncio .sleep (0.5 )
401+ await asyncio .sleep (sleep_interval )
373402 if count > test_constants .FIVE_MAX_TRIES :
374403 raise ConnectionError
375404
@@ -592,19 +621,6 @@ async def test_matlab_proxy_http_post_request(proxy_payload, test_server):
592621 raise ConnectionError
593622
594623
595- # While acceessing matlab-proxy directly, the web socket request looks like
596- # {
597- # "connection": "Upgrade",
598- # "Upgrade": "websocket",
599- # }
600- # whereas while accessing matlab-proxy with nginx as the reverse proxy, the nginx server
601- # modifies the web socket request to
602- # {
603- # "connection": "upgrade",
604- # "upgrade": "websocket",
605- # }
606-
607-
608624async def test_set_licensing_info_put_nlm (test_server ):
609625 """Test to check endpoint : "/set_licensing_info"
610626
@@ -641,35 +657,70 @@ async def test_set_licensing_info_put_invalid_license(test_server):
641657 assert resp .status == HTTPStatus .BAD_REQUEST
642658
643659
660+ # While acceessing matlab-proxy directly, the web socket request looks like
661+ # {
662+ # "connection": "Upgrade",
663+ # "Upgrade": "websocket",
664+ # }
665+ # whereas while accessing matlab-proxy with nginx as the reverse proxy, the nginx server
666+ # modifies the web socket request to
667+ # {
668+ # "connection": "upgrade",
669+ # "upgrade": "websocket",
670+ # }
644671@pytest .mark .parametrize (
645672 "headers" ,
646673 [
647- {
648- "connection" : "Upgrade" ,
649- "Upgrade" : "websocket" ,
650- },
651- {
652- "connection" : "upgrade" ,
653- "upgrade" : "websocket" ,
654- },
674+ CIMultiDict (
675+ {
676+ "connection" : "Upgrade" ,
677+ "Upgrade" : "websocket" ,
678+ }
679+ ),
680+ CIMultiDict (
681+ {
682+ "connection" : "upgrade" ,
683+ "upgrade" : "websocket" ,
684+ }
685+ ),
655686 ],
656687 ids = ["Uppercase header" , "Lowercase header" ],
657688)
658- async def test_matlab_proxy_web_socket (test_server , headers ):
659- """Test to check if test_server proxies web socket request to fake matlab server
689+ async def test_matlab_view_websocket_success (
690+ mocker ,
691+ mock_request ,
692+ mock_websocket_messages ,
693+ headers ,
694+ patch_authenticate_access_decorator ,
695+ ):
696+ """Test successful websocket connection and message forwarding"""
660697
661- Args:
662- test_server (aiohttp_client): Test Server to send HTTP Requests.
663- """
698+ # Configure request for WebSocket
699+ mock_request .headers = headers
700+ mock_request .method = "GET"
701+ mock_request .path_qs = "/test"
664702
665- await wait_for_matlab_to_be_up (test_server , test_constants .ONE_SECOND_DELAY )
666- resp = await test_server .ws_connect ("/http_ws_request.html/" , headers = headers )
667- text = await resp .receive ()
668- websocket_response_string = (
669- "Hello world" # This string is set by the web_socket_handler in devel.py
703+ # Mock WebSocket setup
704+ mock_ws_server = mocker .MagicMock (spec = WebSocketResponse )
705+ mocker .patch (
706+ "matlab_proxy.app.aiohttp.web.WebSocketResponse" , return_value = mock_ws_server
707+ )
708+
709+ # Mock WebSocket client
710+ mock_ws_client = MockWebSocketClient (messages = mock_websocket_messages )
711+ mocker .patch (
712+ "matlab_proxy.app.aiohttp.ClientSession.ws_connect" , return_value = mock_ws_client
670713 )
671- assert text .type == aiohttp .WSMsgType .TEXT
672- assert text .data == websocket_response_string
714+
715+ # Execute
716+ result = await matlab_view (mock_request )
717+
718+ # Assertions
719+ assert result == mock_ws_server
720+ assert mock_ws_server .send_str .call_count == 1
721+ assert mock_ws_server .send_bytes .call_count == 1
722+ assert mock_ws_server .ping .call_count == 1
723+ assert mock_ws_server .pong .call_count == 1
673724
674725
675726async def test_set_licensing_info_put_mhlm (test_server ):
@@ -992,7 +1043,7 @@ async def test_set_licensing_mhlm_single_entitlement(
9921043 assert resp_json ["licensing" ]["entitlementId" ] == "Entitlement3"
9931044
9941045 # validate that MATLAB has started correctly
995- await __check_for_matlab_status (test_server , "up" )
1046+ await __check_for_matlab_status (test_server , "up" , sleep_interval = 2 )
9961047
9971048 # test-cleanup: unset licensing
9981049 # without this, we can leave test drool related to cached license file
0 commit comments