-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathwsgi_prism_websocket_proxy_test.py
More file actions
330 lines (272 loc) · 12.2 KB
/
wsgi_prism_websocket_proxy_test.py
File metadata and controls
330 lines (272 loc) · 12.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
"""
Unit tests for the WSGIPrismWebsocketProxy class and its associated functions.
This module contains tests for the following functionalities:
- Handling WebSocket connections with missing UUID.
- Handling WebSocket connections without a session cookie.
- Handling WebSocket connections with an invalid URI.
- Handling WebSocket connections with an invalid handshake.
- Successful WebSocket connection handling.
- Client-side WebSocket message handling.
- Server-side WebSocket message handling.
Fixtures:
- proxy: Provides an instance of WSGIPrismWebsocketProxy for testing.
- uuid4: Provides a unique UUID for testing.
Test Cases:
- test_prism_websocket_handler_missing_uuid: Tests handling of WebSocket
connections with missing UUID.
- test_prism_websocket_handler_no_session_cookie: Tests handling of
WebSocket connections without a session cookie.
- test_prism_websocket_handler_invalid_uri: Tests handling of WebSocket
connections with an invalid URI.
- test_prism_websocket_handler_invalid_handshake: Tests handling of
WebSocket connections with an invalid handshake.
- test_prism_websocket_handler_success: Tests successful handling of
WebSocket connections.
- test_client_await: Tests client-side WebSocket message handling.
- test_server_await: Tests server-side WebSocket message handling.
Author:
Jon Kohler (jon@nutanix.com)
Copyright:
(c) 2025 Nutanix Inc. All rights reserved.
"""
import logging
import uuid
from unittest.mock import patch, AsyncMock, MagicMock
import pytest
import aiohttp
import websockets
import websockets.exceptions
from aiohttp import web
from wsgi_prism_websocket_proxy import WSGIPrismWebsocketProxy, client_await, \
server_await
# Configure logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
@pytest.fixture(name="proxy")
def proxy_setup():
"""
Creates and returns an instance of WSGIPrismWebsocketProxy with test
credentials.
Returns:
WSGIPrismWebsocketProxy: An instance of WSGIPrismWebsocketProxy
initialized with 'test_host', 'test_user', and 'test_password'.
"""
return WSGIPrismWebsocketProxy('test_host', 'test_user', 'test_password')
@pytest.fixture(name="uuid4")
def uuid4_setup():
"""
Generate a random UUID (Universally Unique Identifier) version 4 and
return it as a string.
Returns:
str: A randomly generated UUID version 4.
"""
return str(uuid.uuid4())
@pytest.mark.asyncio
async def test_prism_websocket_handler_missing_uuid(proxy):
"""
Test the `prism_websocket_handler` method of the proxy when the UUID is
missing.
This test verifies that the `prism_websocket_handler` method returns a 400
status code and an appropriate error message when the UUID is not provided
in the request.
Args:
proxy: The instance of the proxy being tested.
Assertions:
- The response status code should be 400.
- The response text should indicate that the VM UUID is required.
"""
logger.debug("Starting test_prism_websocket_handler_missing_uuid")
request = MagicMock()
request.match_info.get.return_value = None
response = await proxy.prism_websocket_handler(request)
assert response.status == 400
assert response.text == "VM UUID is required"
logger.debug("Completed test_prism_websocket_handler_missing_uuid")
@pytest.mark.asyncio
async def test_prism_websocket_handler_invalid_vm_uuid(proxy):
"""
Test the `prism_websocket_handler` method of the proxy with an invalid VM
UUID.
This test ensures that the `prism_websocket_handler` method correctly
handles the case where an invalid VM UUID is provided. It verifies that
an HTTPBadRequest exception is raised.
Args:
proxy: The proxy instance being tested.
Assertions:
- The response status code should be 400.
- The response text should indicate that the VM UUID format is
invalid.
"""
logger.debug("Starting test_prism_websocket_handler_invalid_vm_uuid")
request = MagicMock()
request.match_info.get.return_value = "invalid-uuid"
response = await proxy.prism_websocket_handler(request)
assert response.status == 400
assert response.text == "Invalid VM UUID format"
logger.debug("Completed test_prism_websocket_handler_invalid_vm_uuid")
@pytest.mark.asyncio
async def test_prism_websocket_handler_no_session_cookie(proxy, uuid4):
"""
Test the `prism_websocket_handler` method of the `proxy` object when
there is no session cookie.
This test ensures that the `prism_websocket_handler` method raises an
`HTTPBadRequest` exception when the session cookie is not present in the
request.
Args:
proxy: The proxy object being tested.
uuid4: A UUID value used to simulate the request's match information.
Steps:
1. Create a mock request object.
2. Set the return value of the request's match_info.get method to the
provided UUID.
3. Patch the `_get_session_cookie` method of the proxy object to return
None.
4. Assert that calling `prism_websocket_handler` with the mock request
raises an `HTTPBadRequest` exception.
"""
logger.debug("Starting test_prism_websocket_handler_no_session_cookie")
request = MagicMock()
request.match_info.get.return_value = uuid4
with patch.object(proxy, '_get_session_cookie', return_value=None):
with pytest.raises(web.HTTPBadRequest):
await proxy.prism_websocket_handler(request)
logger.debug("Completed test_prism_websocket_handler_no_session_cookie")
@pytest.mark.asyncio
async def test_prism_websocket_handler_invalid_uri(proxy, uuid4):
"""
Test the `prism_websocket_handler` method of the proxy with an invalid URI.
This test ensures that the `prism_websocket_handler` method correctly
handles the case where an invalid URI is provided. It mocks the necessary
components and verifies that an HTTPBadRequest exception is raised.
Args:
proxy: The proxy instance being tested.
uuid4: A mock UUID value used for the request.
Raises:
web.HTTPBadRequest: If the URI is invalid.
"""
logger.debug("Starting test_prism_websocket_handler_invalid_uri")
request = MagicMock()
request.match_info.get.return_value = uuid4
with patch.object(proxy, '_get_session_cookie', return_value='test_cookie'):
with patch('websockets.connect',
side_effect=websockets.exceptions.InvalidURI('test_uri',
'Invalid URI')):
with pytest.raises(web.HTTPBadRequest):
await proxy.prism_websocket_handler(request)
logger.debug("Completed test_prism_websocket_handler_invalid_uri")
@pytest.mark.asyncio
async def test_prism_websocket_handler_invalid_handshake(proxy, uuid4):
"""
Test the `prism_websocket_handler` method of the `proxy` object when an
invalid handshake occurs during a WebSocket connection.
This test simulates an invalid handshake exception raised by the
`websockets.connect` method and verifies that the
`prism_websocket_handler` method raises an HTTPBadRequest exception in
response.
Args:
proxy: The proxy object whose `prism_websocket_handler` method is
being tested.
uuid4: A mock UUID value used to simulate the request's match_info.
Setup:
- Mocks the request object and sets its `match_info.get` method to
return the mock UUID.
- Patches the `_get_session_cookie` method of the proxy object to
return a test cookie.
- Patches the `websockets.connect` method to raise an
`InvalidHandshake` exception.
Assertions:
- Verifies that the `prism_websocket_handler` method raises an
`HTTPBadRequest` exception when an invalid handshake occurs.
"""
logger.debug("Starting test_prism_websocket_handler_invalid_handshake")
request = MagicMock()
request.match_info.get.return_value = uuid4
with patch.object(proxy, '_get_session_cookie', return_value='test_cookie'):
with patch('websockets.connect',
side_effect=websockets.exceptions.InvalidHandshake):
with pytest.raises(web.HTTPBadRequest):
await proxy.prism_websocket_handler(request)
logger.debug("Completed test_prism_websocket_handler_invalid_handshake")
@pytest.mark.asyncio
async def test_prism_websocket_handler_success(proxy, uuid4):
"""
Test the `prism_websocket_handler` method of the proxy for a successful
connection.
This test verifies that the `prism_websocket_handler` correctly handles a
websocket connection when provided with a valid UUID and session cookie.
Args:
proxy: The instance of the proxy being tested.
uuid4: A mock UUID to simulate the request's match_info.
Steps:
1. Mock the request object and set its `match_info.get` method to return
the provided UUID.
2. Patch the `_get_session_cookie` method of the proxy to return a test
cookie.
3. Patch the `websockets.connect` method to return an asynchronous mock.
4. Patch the `asyncio.create_task` method to return an asynchronous mock.
5. Call the `prism_websocket_handler` method with the mocked request.
6. Assert that the response status is 101, indicating a successful
websocket connection.
"""
logger.debug("Starting test_prism_websocket_handler_success")
request = MagicMock()
request.match_info.get.return_value = uuid4
with patch.object(proxy, '_get_session_cookie', return_value='test_cookie'):
with patch('websockets.connect', new_callable=AsyncMock) as mock_connect:
mock_connect.return_value = AsyncMock()
with patch('asyncio.create_task', new_callable=AsyncMock):
response = await proxy.prism_websocket_handler(request)
assert response.status == 101
logger.debug("Completed test_prism_websocket_handler_success")
@pytest.mark.asyncio
async def test_client_await():
"""
Test the `client_await` function.
This test simulates a WebSocket client and server interaction where the
client receives a 'close' message. It verifies that the `client_await`
function properly handles the message and closes the client WebSocket
connection.
Steps:
1. Create mock objects for the request, client WebSocket, and server
WebSocket.
2. Set up the client WebSocket to simulate receiving a 'close' message.
3. Call the `client_await` function with the mock objects.
4. Verify that the client WebSocket's `close` method is called once.
Assertions:
- The client WebSocket's `close` method is called exactly once.
"""
logger.debug("Starting test_client_await")
request = MagicMock()
client_ws = AsyncMock()
server_ws = AsyncMock()
client_ws.__aiter__.return_value = [
MagicMock(type=aiohttp.WSMsgType.TEXT, data='close')]
await client_await(request, client_ws, server_ws)
client_ws.close.assert_called_once()
logger.debug("Completed test_client_await")
@pytest.mark.asyncio
async def test_server_await():
"""
Test the server_await function.
This test function mocks the request, server WebSocket, and client
WebSocket objects, and verifies that the server_await function correctly
processes a message from the server WebSocket and sends it to the client
WebSocket.
Steps:
1. Mock the request, server WebSocket, and client WebSocket objects.
2. Set the server WebSocket to return a test message when iterated.
3. Call the server_await function with the mocked objects.
4. Assert that the client WebSocket's send_str method was called once with
the test message.
Assertions:
- The client WebSocket's send_str method is called once with the message
'test_message'.
"""
logger.debug("Starting test_server_await")
request = MagicMock()
server_ws = AsyncMock()
client_ws = AsyncMock()
server_ws.__aiter__.return_value = ['test_message']
await server_await(request, server_ws, client_ws)
client_ws.send_str.assert_called_once_with('test_message')
logger.debug("Completed test_server_await")