|
16 | 16 | from __future__ import annotations
|
17 | 17 |
|
18 | 18 | # Standard
|
| 19 | +import asyncio |
19 | 20 | from datetime import datetime, timezone
|
20 | 21 | from unittest.mock import AsyncMock, MagicMock, Mock
|
21 | 22 |
|
@@ -265,10 +266,10 @@ async def test_ssl_verification_bypass(self, gateway_service, monkeypatch):
|
265 | 266 | pass
|
266 | 267 |
|
267 | 268 | # ────────────────────────────────────────────────────────────────────
|
268 |
| - # Validate Gateway URL Auth Failure |
| 269 | + # Validate Gateway URL Auth Failure - 401 |
269 | 270 | # ────────────────────────────────────────────────────────────────────
|
270 | 271 | @pytest.mark.asyncio
|
271 |
| - async def test_validate_auth_failure(self, gateway_service, monkeypatch): |
| 272 | + async def test_validate_auth_failure_401(self, gateway_service, monkeypatch): |
272 | 273 | # Mock the response object to be returned inside the async with block
|
273 | 274 | response_mock = MagicMock()
|
274 | 275 | response_mock.status_code = 401
|
@@ -297,6 +298,39 @@ async def test_validate_auth_failure(self, gateway_service, monkeypatch):
|
297 | 298 | # Expect False due to 401
|
298 | 299 | assert result is False
|
299 | 300 |
|
| 301 | + # ──────────────────────────────────────────────────────────────────── |
| 302 | + # Validate Gateway URL Auth Failure - 403 |
| 303 | + # ──────────────────────────────────────────────────────────────────── |
| 304 | + @pytest.mark.asyncio |
| 305 | + async def test_validate_auth_failure_403(self, gateway_service, monkeypatch): |
| 306 | + # Mock the response object to be returned inside the async with block |
| 307 | + response_mock = MagicMock() |
| 308 | + response_mock.status_code = 403 |
| 309 | + response_mock.headers = {"content-type": "text/event-stream"} |
| 310 | + |
| 311 | + # Create an async context manager mock that returns response_mock |
| 312 | + stream_context = MagicMock() |
| 313 | + stream_context.__aenter__ = AsyncMock(return_value=response_mock) |
| 314 | + stream_context.__aexit__ = AsyncMock(return_value=None) |
| 315 | + |
| 316 | + # Mock the AsyncClient to return this context manager from .stream() |
| 317 | + client_mock = MagicMock() |
| 318 | + client_mock.stream = AsyncMock(return_value=stream_context) |
| 319 | + client_mock.aclose = AsyncMock() |
| 320 | + |
| 321 | + # Mock ResilientHttpClient to return this client |
| 322 | + resilient_client_mock = MagicMock() |
| 323 | + resilient_client_mock.client = client_mock |
| 324 | + resilient_client_mock.aclose = AsyncMock() |
| 325 | + |
| 326 | + monkeypatch.setattr("mcpgateway.services.gateway_service.ResilientHttpClient", MagicMock(return_value=resilient_client_mock)) |
| 327 | + |
| 328 | + # Run the method |
| 329 | + result = await gateway_service._validate_gateway_url(url="http://example.com", headers={}, transport_type="SSE") |
| 330 | + |
| 331 | + # Expect False due to 401 |
| 332 | + assert result is False |
| 333 | + |
300 | 334 | # ────────────────────────────────────────────────────────────────────
|
301 | 335 | # Validate Gateway URL Connection Error
|
302 | 336 | # ────────────────────────────────────────────────────────────────────
|
@@ -324,14 +358,6 @@ async def test_validate_connectivity_failure(self, gateway_service, monkeypatch)
|
324 | 358 |
|
325 | 359 | assert result is False
|
326 | 360 |
|
327 |
| - # ──────────────────────────────────────────────────────────────────── |
328 |
| - # Validate Gateway URL Bulk Connections Validation |
329 |
| - # ──────────────────────────────────────────────────────────────────── |
330 |
| - @pytest.mark.asyncio |
331 |
| - async def test_bulk_concurrent_validation(self, gateway_service, monkeypatch): |
332 |
| - # TODO |
333 |
| - pass |
334 |
| - |
335 | 361 | # ───────────────────────────────────────────────────────────────────────────
|
336 | 362 | # Validate Gateway - StreamableHTTP with mcp-session-id & redirected-url
|
337 | 363 | # ───────────────────────────────────────────────────────────────────────────
|
@@ -371,6 +397,38 @@ async def test_streamablehttp_redirect(self, gateway_service, monkeypatch):
|
371 | 397 | # assert result is True
|
372 | 398 | pass
|
373 | 399 |
|
| 400 | + # ─────────────────────────────────────────────────────────────────────────── |
| 401 | + # Validate Gateway URL - Bulk Concurrent requests Validation |
| 402 | + # ─────────────────────────────────────────────────────────────────────────── |
| 403 | + @pytest.mark.asyncio |
| 404 | + async def test_bulk_concurrent_validation(self, gateway_service, monkeypatch): |
| 405 | + urls = [f"http://gateway{i}.com" for i in range(20)] |
| 406 | + |
| 407 | + # Simulate a successful stream context |
| 408 | + stream_context = AsyncMock() |
| 409 | + stream_context.__aenter__.return_value.status_code = 200 |
| 410 | + stream_context.__aenter__.return_value.headers = {"content-type": "text/event-stream"} |
| 411 | + stream_context.__aexit__.return_value = AsyncMock() |
| 412 | + |
| 413 | + # Mock client to return the above stream context |
| 414 | + mock_client = MagicMock() |
| 415 | + mock_client.stream.return_value = stream_context |
| 416 | + mock_client.aclose = AsyncMock() |
| 417 | + |
| 418 | + # ResilientHttpClient mock returns a .client and .aclose |
| 419 | + resilient_client_mock = MagicMock() |
| 420 | + resilient_client_mock.client = mock_client |
| 421 | + resilient_client_mock.aclose = AsyncMock() |
| 422 | + |
| 423 | + # Patch ResilientHttpClient where it’s used in your module |
| 424 | + monkeypatch.setattr("mcpgateway.services.gateway_service.ResilientHttpClient", MagicMock(return_value=resilient_client_mock)) |
| 425 | + |
| 426 | + # Run the validations concurrently |
| 427 | + results = await asyncio.gather(*[gateway_service._validate_gateway_url(url, {}, "SSE") for url in urls]) |
| 428 | + |
| 429 | + # All should be True (validation success) |
| 430 | + assert all(results) |
| 431 | + |
374 | 432 | # ────────────────────────────────────────────────────────────────────
|
375 | 433 | # LIST / GET
|
376 | 434 | # ────────────────────────────────────────────────────────────────────
|
|
0 commit comments