Gateway tests

This commit is contained in:
Cyber MacGeddon 2025-07-12 12:35:07 +01:00
parent 9661736a65
commit 4e71679e5a
6 changed files with 733 additions and 0 deletions

View file

@ -0,0 +1,165 @@
"""
Tests for Gateway Dispatch Mux
"""
import pytest
import asyncio
from unittest.mock import MagicMock, AsyncMock
from trustgraph.gateway.dispatch.mux import Mux, MAX_QUEUE_SIZE
class TestMux:
"""Test cases for Mux class"""
def test_mux_initialization(self):
"""Test Mux initialization"""
mock_dispatcher_manager = MagicMock()
mock_ws = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
assert mux.dispatcher_manager == mock_dispatcher_manager
assert mux.ws == mock_ws
assert mux.running == mock_running
assert isinstance(mux.q, asyncio.Queue)
assert mux.q.maxsize == MAX_QUEUE_SIZE
@pytest.mark.asyncio
async def test_mux_destroy_with_websocket(self):
"""Test Mux destroy method with websocket"""
mock_dispatcher_manager = MagicMock()
mock_ws = AsyncMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
# Call destroy
await mux.destroy()
# Verify running.stop was called
mock_running.stop.assert_called_once()
# Verify websocket close was called
mock_ws.close.assert_called_once()
@pytest.mark.asyncio
async def test_mux_destroy_without_websocket(self):
"""Test Mux destroy method without websocket"""
mock_dispatcher_manager = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=None,
running=mock_running
)
# Call destroy
await mux.destroy()
# Verify running.stop was called
mock_running.stop.assert_called_once()
# No websocket to close
@pytest.mark.asyncio
async def test_mux_receive_valid_message(self):
"""Test Mux receive method with valid message"""
mock_dispatcher_manager = MagicMock()
mock_ws = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
# Mock message with valid JSON
mock_msg = MagicMock()
mock_msg.json.return_value = {
"request": {"type": "test"},
"id": "test-id-123"
}
# Call receive
await mux.receive(mock_msg)
# Verify json was called
mock_msg.json.assert_called_once()
@pytest.mark.asyncio
async def test_mux_receive_message_without_request(self):
"""Test Mux receive method with message missing request field"""
mock_dispatcher_manager = MagicMock()
mock_ws = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
# Mock message without request field
mock_msg = MagicMock()
mock_msg.json.return_value = {
"id": "test-id-123"
}
# receive method should handle the RuntimeError internally
# Based on the code, it seems to catch exceptions
await mux.receive(mock_msg)
@pytest.mark.asyncio
async def test_mux_receive_message_without_id(self):
"""Test Mux receive method with message missing id field"""
mock_dispatcher_manager = MagicMock()
mock_ws = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
# Mock message without id field
mock_msg = MagicMock()
mock_msg.json.return_value = {
"request": {"type": "test"}
}
# receive method should handle the RuntimeError internally
await mux.receive(mock_msg)
@pytest.mark.asyncio
async def test_mux_receive_invalid_json(self):
"""Test Mux receive method with invalid JSON"""
mock_dispatcher_manager = MagicMock()
mock_ws = MagicMock()
mock_running = MagicMock()
mux = Mux(
dispatcher_manager=mock_dispatcher_manager,
ws=mock_ws,
running=mock_running
)
# Mock message with invalid JSON
mock_msg = MagicMock()
mock_msg.json.side_effect = ValueError("Invalid JSON")
# receive method should handle the ValueError internally
await mux.receive(mock_msg)
mock_msg.json.assert_called_once()

View file

@ -0,0 +1,115 @@
"""
Tests for Gateway Service Requestor
"""
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from trustgraph.gateway.dispatch.requestor import ServiceRequestor
class TestServiceRequestor:
"""Test cases for ServiceRequestor class"""
@patch('trustgraph.gateway.dispatch.requestor.Publisher')
@patch('trustgraph.gateway.dispatch.requestor.Subscriber')
def test_service_requestor_initialization(self, mock_subscriber, mock_publisher):
"""Test ServiceRequestor initialization"""
mock_pulsar_client = MagicMock()
mock_request_schema = MagicMock()
mock_response_schema = MagicMock()
requestor = ServiceRequestor(
pulsar_client=mock_pulsar_client,
request_queue="test-request-queue",
request_schema=mock_request_schema,
response_queue="test-response-queue",
response_schema=mock_response_schema,
subscription="test-subscription",
consumer_name="test-consumer",
timeout=300
)
# Verify Publisher was created correctly
mock_publisher.assert_called_once_with(
mock_pulsar_client, "test-request-queue", schema=mock_request_schema
)
# Verify Subscriber was created correctly
mock_subscriber.assert_called_once_with(
mock_pulsar_client, "test-response-queue",
"test-subscription", "test-consumer", mock_response_schema
)
assert requestor.timeout == 300
assert requestor.running is True
@patch('trustgraph.gateway.dispatch.requestor.Publisher')
@patch('trustgraph.gateway.dispatch.requestor.Subscriber')
def test_service_requestor_with_defaults(self, mock_subscriber, mock_publisher):
"""Test ServiceRequestor initialization with default parameters"""
mock_pulsar_client = MagicMock()
mock_request_schema = MagicMock()
mock_response_schema = MagicMock()
requestor = ServiceRequestor(
pulsar_client=mock_pulsar_client,
request_queue="test-queue",
request_schema=mock_request_schema,
response_queue="response-queue",
response_schema=mock_response_schema
)
# Verify default values
mock_subscriber.assert_called_once_with(
mock_pulsar_client, "response-queue",
"api-gateway", "api-gateway", mock_response_schema
)
assert requestor.timeout == 600 # Default timeout
@patch('trustgraph.gateway.dispatch.requestor.Publisher')
@patch('trustgraph.gateway.dispatch.requestor.Subscriber')
@pytest.mark.asyncio
async def test_service_requestor_start(self, mock_subscriber, mock_publisher):
"""Test ServiceRequestor start method"""
mock_pulsar_client = MagicMock()
mock_sub_instance = AsyncMock()
mock_subscriber.return_value = mock_sub_instance
requestor = ServiceRequestor(
pulsar_client=mock_pulsar_client,
request_queue="test-queue",
request_schema=MagicMock(),
response_queue="response-queue",
response_schema=MagicMock()
)
# Call start
await requestor.start()
# Verify subscriber start was called
mock_sub_instance.start.assert_called_once()
assert requestor.running is True
@patch('trustgraph.gateway.dispatch.requestor.Publisher')
@patch('trustgraph.gateway.dispatch.requestor.Subscriber')
def test_service_requestor_attributes(self, mock_subscriber, mock_publisher):
"""Test ServiceRequestor has correct attributes"""
mock_pulsar_client = MagicMock()
mock_pub_instance = MagicMock()
mock_sub_instance = MagicMock()
mock_publisher.return_value = mock_pub_instance
mock_subscriber.return_value = mock_sub_instance
requestor = ServiceRequestor(
pulsar_client=mock_pulsar_client,
request_queue="test-queue",
request_schema=MagicMock(),
response_queue="response-queue",
response_schema=MagicMock()
)
# Verify attributes are set correctly
assert requestor.pub == mock_pub_instance
assert requestor.sub == mock_sub_instance
assert requestor.running is True

View file

@ -0,0 +1,120 @@
"""
Tests for Gateway Service Sender
"""
import pytest
from unittest.mock import MagicMock, AsyncMock, patch
from trustgraph.gateway.dispatch.sender import ServiceSender
class TestServiceSender:
"""Test cases for ServiceSender class"""
@patch('trustgraph.gateway.dispatch.sender.Publisher')
def test_service_sender_initialization(self, mock_publisher):
"""Test ServiceSender initialization"""
mock_pulsar_client = MagicMock()
mock_schema = MagicMock()
sender = ServiceSender(
pulsar_client=mock_pulsar_client,
queue="test-queue",
schema=mock_schema
)
# Verify Publisher was created correctly
mock_publisher.assert_called_once_with(
mock_pulsar_client, "test-queue", schema=mock_schema
)
@patch('trustgraph.gateway.dispatch.sender.Publisher')
@pytest.mark.asyncio
async def test_service_sender_start(self, mock_publisher):
"""Test ServiceSender start method"""
mock_pub_instance = AsyncMock()
mock_publisher.return_value = mock_pub_instance
sender = ServiceSender(
pulsar_client=MagicMock(),
queue="test-queue",
schema=MagicMock()
)
# Call start
await sender.start()
# Verify publisher start was called
mock_pub_instance.start.assert_called_once()
@patch('trustgraph.gateway.dispatch.sender.Publisher')
@pytest.mark.asyncio
async def test_service_sender_stop(self, mock_publisher):
"""Test ServiceSender stop method"""
mock_pub_instance = AsyncMock()
mock_publisher.return_value = mock_pub_instance
sender = ServiceSender(
pulsar_client=MagicMock(),
queue="test-queue",
schema=MagicMock()
)
# Call stop
await sender.stop()
# Verify publisher stop was called
mock_pub_instance.stop.assert_called_once()
@patch('trustgraph.gateway.dispatch.sender.Publisher')
def test_service_sender_to_request_not_implemented(self, mock_publisher):
"""Test ServiceSender to_request method raises RuntimeError"""
sender = ServiceSender(
pulsar_client=MagicMock(),
queue="test-queue",
schema=MagicMock()
)
with pytest.raises(RuntimeError, match="Not defined"):
sender.to_request({"test": "request"})
@patch('trustgraph.gateway.dispatch.sender.Publisher')
@pytest.mark.asyncio
async def test_service_sender_process(self, mock_publisher):
"""Test ServiceSender process method"""
mock_pub_instance = AsyncMock()
mock_publisher.return_value = mock_pub_instance
# Create a concrete sender that implements to_request
class ConcreteSender(ServiceSender):
def to_request(self, request):
return {"processed": request}
sender = ConcreteSender(
pulsar_client=MagicMock(),
queue="test-queue",
schema=MagicMock()
)
test_request = {"test": "data"}
# Call process
await sender.process(test_request)
# Verify publisher send was called with processed request
mock_pub_instance.send.assert_called_once_with(None, {"processed": test_request})
@patch('trustgraph.gateway.dispatch.sender.Publisher')
def test_service_sender_attributes(self, mock_publisher):
"""Test ServiceSender has correct attributes"""
mock_pub_instance = MagicMock()
mock_publisher.return_value = mock_pub_instance
sender = ServiceSender(
pulsar_client=MagicMock(),
queue="test-queue",
schema=MagicMock()
)
# Verify attributes are set correctly
assert sender.pub == mock_pub_instance

View file

@ -0,0 +1,85 @@
"""
Tests for Gateway Endpoint Manager
"""
import pytest
from unittest.mock import MagicMock
from trustgraph.gateway.endpoint.manager import EndpointManager
class TestEndpointManager:
"""Test cases for EndpointManager class"""
def test_endpoint_manager_initialization(self):
"""Test EndpointManager initialization creates all endpoints"""
mock_dispatcher_manager = MagicMock()
mock_auth = MagicMock()
# Mock dispatcher methods
mock_dispatcher_manager.dispatch_global_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_socket.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_import.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_export.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_stream.return_value = MagicMock()
manager = EndpointManager(
dispatcher_manager=mock_dispatcher_manager,
auth=mock_auth,
prometheus_url="http://prometheus:9090",
timeout=300
)
assert manager.dispatcher_manager == mock_dispatcher_manager
assert manager.timeout == 300
assert manager.services == {}
assert len(manager.endpoints) > 0 # Should have multiple endpoints
def test_endpoint_manager_with_default_timeout(self):
"""Test EndpointManager with default timeout value"""
mock_dispatcher_manager = MagicMock()
mock_auth = MagicMock()
# Mock dispatcher methods
mock_dispatcher_manager.dispatch_global_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_socket.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_import.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_export.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_stream.return_value = MagicMock()
manager = EndpointManager(
dispatcher_manager=mock_dispatcher_manager,
auth=mock_auth,
prometheus_url="http://prometheus:9090"
)
assert manager.timeout == 600 # Default value
def test_endpoint_manager_dispatcher_calls(self):
"""Test EndpointManager calls all required dispatcher methods"""
mock_dispatcher_manager = MagicMock()
mock_auth = MagicMock()
# Mock dispatcher methods
mock_dispatcher_manager.dispatch_global_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_socket.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_service.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_import.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_export.return_value = MagicMock()
mock_dispatcher_manager.dispatch_flow_stream.return_value = MagicMock()
EndpointManager(
dispatcher_manager=mock_dispatcher_manager,
auth=mock_auth,
prometheus_url="http://test:9090"
)
# Verify all dispatcher methods were called during initialization
mock_dispatcher_manager.dispatch_global_service.assert_called_once()
mock_dispatcher_manager.dispatch_socket.assert_called()
mock_dispatcher_manager.dispatch_flow_service.assert_called_once()
mock_dispatcher_manager.dispatch_flow_import.assert_called()
mock_dispatcher_manager.dispatch_flow_export.assert_called()
mock_dispatcher_manager.dispatch_flow_stream.assert_called()

View file

@ -0,0 +1,124 @@
"""
Tests for Gateway Socket Endpoint
"""
import pytest
from unittest.mock import MagicMock, AsyncMock
from aiohttp import WSMsgType
from trustgraph.gateway.endpoint.socket import SocketEndpoint
class TestSocketEndpoint:
"""Test cases for SocketEndpoint class"""
def test_socket_endpoint_initialization(self):
"""Test SocketEndpoint initialization"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
endpoint = SocketEndpoint(
endpoint_path="/api/socket",
auth=mock_auth,
dispatcher=mock_dispatcher
)
assert endpoint.path == "/api/socket"
assert endpoint.auth == mock_auth
assert endpoint.dispatcher == mock_dispatcher
assert endpoint.operation == "socket"
@pytest.mark.asyncio
async def test_worker_method(self):
"""Test SocketEndpoint worker method"""
mock_auth = MagicMock()
mock_dispatcher = AsyncMock()
endpoint = SocketEndpoint("/api/socket", mock_auth, mock_dispatcher)
mock_ws = MagicMock()
mock_running = MagicMock()
# Call worker method
await endpoint.worker(mock_ws, mock_dispatcher, mock_running)
# Verify dispatcher.run was called
mock_dispatcher.run.assert_called_once()
@pytest.mark.asyncio
async def test_listener_method_with_text_message(self):
"""Test SocketEndpoint listener method with text message"""
mock_auth = MagicMock()
mock_dispatcher = AsyncMock()
endpoint = SocketEndpoint("/api/socket", mock_auth, mock_dispatcher)
# Mock websocket with text message
mock_msg = MagicMock()
mock_msg.type = WSMsgType.TEXT
# Create async iterator for websocket
async def async_iter():
yield mock_msg
mock_ws = MagicMock()
mock_ws.__aiter__ = lambda: async_iter()
mock_running = MagicMock()
# Call listener method
await endpoint.listener(mock_ws, mock_dispatcher, mock_running)
# Verify dispatcher.receive was called with the message
mock_dispatcher.receive.assert_called_once_with(mock_msg)
@pytest.mark.asyncio
async def test_listener_method_with_binary_message(self):
"""Test SocketEndpoint listener method with binary message"""
mock_auth = MagicMock()
mock_dispatcher = AsyncMock()
endpoint = SocketEndpoint("/api/socket", mock_auth, mock_dispatcher)
# Mock websocket with binary message
mock_msg = MagicMock()
mock_msg.type = WSMsgType.BINARY
# Create async iterator for websocket
async def async_iter():
yield mock_msg
mock_ws = MagicMock()
mock_ws.__aiter__ = lambda: async_iter()
mock_running = MagicMock()
# Call listener method
await endpoint.listener(mock_ws, mock_dispatcher, mock_running)
# Verify dispatcher.receive was called with the message
mock_dispatcher.receive.assert_called_once_with(mock_msg)
@pytest.mark.asyncio
async def test_listener_method_with_close_message(self):
"""Test SocketEndpoint listener method with close message"""
mock_auth = MagicMock()
mock_dispatcher = AsyncMock()
endpoint = SocketEndpoint("/api/socket", mock_auth, mock_dispatcher)
# Mock websocket with close message
mock_msg = MagicMock()
mock_msg.type = WSMsgType.CLOSE
# Create async iterator for websocket
async def async_iter():
yield mock_msg
mock_ws = MagicMock()
mock_ws.__aiter__ = lambda: async_iter()
mock_running = MagicMock()
# Call listener method
await endpoint.listener(mock_ws, mock_dispatcher, mock_running)
# Verify dispatcher.receive was NOT called for close message
mock_dispatcher.receive.assert_not_called()

View file

@ -0,0 +1,124 @@
"""
Tests for Gateway Stream Endpoint
"""
import pytest
from unittest.mock import MagicMock
from trustgraph.gateway.endpoint.stream_endpoint import StreamEndpoint
class TestStreamEndpoint:
"""Test cases for StreamEndpoint class"""
def test_stream_endpoint_initialization_with_post(self):
"""Test StreamEndpoint initialization with POST method"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher,
method="POST"
)
assert endpoint.path == "/api/stream"
assert endpoint.auth == mock_auth
assert endpoint.dispatcher == mock_dispatcher
assert endpoint.operation == "service"
assert endpoint.method == "POST"
def test_stream_endpoint_initialization_with_get(self):
"""Test StreamEndpoint initialization with GET method"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher,
method="GET"
)
assert endpoint.method == "GET"
def test_stream_endpoint_initialization_default_method(self):
"""Test StreamEndpoint initialization with default POST method"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher
)
assert endpoint.method == "POST" # Default value
@pytest.mark.asyncio
async def test_stream_endpoint_start_method(self):
"""Test StreamEndpoint start method (should be no-op)"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
endpoint = StreamEndpoint("/api/stream", mock_auth, mock_dispatcher)
# start() should complete without error
await endpoint.start()
def test_add_routes_with_post_method(self):
"""Test add_routes method with POST method"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
mock_app = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher,
method="POST"
)
endpoint.add_routes(mock_app)
# Verify add_routes was called with POST route
mock_app.add_routes.assert_called_once()
call_args = mock_app.add_routes.call_args[0][0]
assert len(call_args) == 1 # One route added
def test_add_routes_with_get_method(self):
"""Test add_routes method with GET method"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
mock_app = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher,
method="GET"
)
endpoint.add_routes(mock_app)
# Verify add_routes was called with GET route
mock_app.add_routes.assert_called_once()
call_args = mock_app.add_routes.call_args[0][0]
assert len(call_args) == 1 # One route added
def test_add_routes_with_invalid_method_raises_error(self):
"""Test add_routes method with invalid method raises RuntimeError"""
mock_auth = MagicMock()
mock_dispatcher = MagicMock()
mock_app = MagicMock()
endpoint = StreamEndpoint(
endpoint_path="/api/stream",
auth=mock_auth,
dispatcher=mock_dispatcher,
method="INVALID"
)
with pytest.raises(RuntimeError, match="Bad method"):
endpoint.add_routes(mock_app)