test(General): finished unit test, coverage 88%
ISSUES CLOSED: clevermicro/amq-adapter-python#16
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
import asyncio
|
||||
import unittest
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
from aio_pika import Message
|
||||
from aio_pika.abc import AbstractIncomingMessage, AbstractRobustExchange
|
||||
from opentelemetry.trace import Tracer
|
||||
|
||||
from amqp.adapter.backpressure_handler import BackpressureHandler
|
||||
from amqp.adapter.cleverthis_service_adapter import CleverThisServiceAdapter
|
||||
from amqp.adapter.data_message_factory import DataMessageFactory
|
||||
from amqp.adapter.data_response_factory import DataResponseFactory
|
||||
from amqp.adapter.file_handler import FileHandler
|
||||
from amqp.adapter.service_message_factory import ServiceMessageFactory
|
||||
from amqp.config.amq_configuration import AMQConfiguration
|
||||
from amqp.model.model import (
|
||||
DataMessage,
|
||||
DataMessageMagicByte,
|
||||
DataResponse,
|
||||
MultipartDataMessage,
|
||||
)
|
||||
from amqp.rabbitmq.rabbit_mq_client import RabbitMQClient
|
||||
from amqp.service.amq_message_handler import DataMessageHandler
|
||||
|
||||
|
||||
class TestDataMessageHandler(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
# Create mock objects for dependencies
|
||||
self.service_adapter = MagicMock(spec=CleverThisServiceAdapter)
|
||||
self.tracer = MagicMock(spec=Tracer)
|
||||
self.message_factory = MagicMock(spec=DataMessageFactory)
|
||||
self.service_message_factory = MagicMock(spec=ServiceMessageFactory)
|
||||
self.reply_to_exchange = MagicMock(spec=AbstractRobustExchange)
|
||||
self.rabbit_mq_client = MagicMock(spec=RabbitMQClient)
|
||||
|
||||
# Create a proper mock configuration with nested attributes
|
||||
self.amq_configuration = MagicMock(spec=AMQConfiguration)
|
||||
dispatch_mock = MagicMock()
|
||||
dispatch_mock.rabbit_mq_url = "amqp://test"
|
||||
dispatch_mock.download_dir = "/test/dir"
|
||||
self.amq_configuration.dispatch = dispatch_mock
|
||||
|
||||
self.loop = asyncio.new_event_loop()
|
||||
self.backpressure_handler = MagicMock(spec=BackpressureHandler)
|
||||
self.backpressure_handler.parallel_workers = 10
|
||||
self.backpressure_handler.current_parallel_executions = 0
|
||||
self.file_handler = MagicMock(spec=FileHandler)
|
||||
|
||||
# Create the handler instance
|
||||
self.handler = DataMessageHandler(
|
||||
service_adapter=self.service_adapter,
|
||||
tracer=self.tracer,
|
||||
message_factory=self.message_factory,
|
||||
service_message_factory=self.service_message_factory,
|
||||
reply_to_exchange=self.reply_to_exchange,
|
||||
rabbit_mq_client=self.rabbit_mq_client,
|
||||
amq_configuration=self.amq_configuration,
|
||||
loop=self.loop,
|
||||
backpressure_handler=self.backpressure_handler,
|
||||
file_handler=self.file_handler,
|
||||
)
|
||||
|
||||
def tearDown(self):
|
||||
self.loop.close()
|
||||
|
||||
@patch("amqp.service.amq_message_handler.threading.Thread")
|
||||
async def test_inbound_data_message_callback_as_thread(self, mock_thread):
|
||||
# Arrange
|
||||
message = MagicMock(spec=AbstractIncomingMessage)
|
||||
mock_thread.return_value.start = MagicMock()
|
||||
|
||||
# Act
|
||||
await self.handler.inbound_data_message_callback_as_thread(message)
|
||||
|
||||
# Assert
|
||||
mock_thread.assert_called_once()
|
||||
mock_thread.return_value.start.assert_called_once()
|
||||
|
||||
async def test_reconstitute_data_message_simple(self):
|
||||
# Arrange
|
||||
message = MagicMock(spec=AbstractIncomingMessage)
|
||||
message.headers = {"clevermicro.addressing": 0}
|
||||
message.body = (
|
||||
b"\002\027A64f3b5af4d00000a4000|m=POST|d=test.domain|p=/test|h={}|t={}|b=test_body"
|
||||
)
|
||||
message.delivery_tag = 123
|
||||
message.consumer_tag = "test_consumer"
|
||||
expected_message = MagicMock(spec=DataMessage)
|
||||
self.message_factory.from_bytes = MagicMock(return_value=expected_message)
|
||||
|
||||
# Act
|
||||
result = await self.handler.reconstitute_data_message(message)
|
||||
|
||||
# Assert
|
||||
self.assertEqual(result, expected_message)
|
||||
|
||||
async def test_reconstitute_data_message_multipart(self):
|
||||
# Arrange
|
||||
message = MagicMock(spec=AbstractIncomingMessage)
|
||||
message.headers = {"clevermicro.addressing": 1}
|
||||
message.body = b'{"req-info": "test_queue"}'
|
||||
message.delivery_tag = 123
|
||||
message.consumer_tag = "test_consumer"
|
||||
|
||||
base_message = MagicMock(spec=DataMessage)
|
||||
base_message.magic.return_value = DataMessageMagicByte.HTTP_REQUEST.value
|
||||
base_message.id.return_value = MagicMock(as_string=lambda: "test_id")
|
||||
base_message.method.return_value = "POST"
|
||||
base_message.domain.return_value = "test.domain"
|
||||
base_message.path.return_value = "/test"
|
||||
base_message.headers.return_value = {}
|
||||
base_message.trace_info.return_value = {}
|
||||
base_message.base64body.return_value = b"test_body"
|
||||
base_message.body.return_value = b"test_body"
|
||||
|
||||
# Mock the static methods
|
||||
with (
|
||||
patch(
|
||||
"amqp.adapter.data_message_factory.DataMessageFactory.from_bytes",
|
||||
return_value=base_message,
|
||||
),
|
||||
patch(
|
||||
"amqp.adapter.data_message_factory.DataMessageFactory.from_stream",
|
||||
new_callable=AsyncMock,
|
||||
return_value=base_message,
|
||||
),
|
||||
):
|
||||
self.file_handler.on_message = AsyncMock(return_value={"file1": "path1"})
|
||||
self.file_handler.download_buffer = AsyncMock(return_value=(b"test_body", 1))
|
||||
|
||||
# Act
|
||||
result = await self.handler.reconstitute_data_message(message)
|
||||
|
||||
# Assert
|
||||
self.assertIsInstance(result, MultipartDataMessage)
|
||||
self.file_handler.on_message.assert_called_once()
|
||||
|
||||
async def test_send_reply(self):
|
||||
# Arrange
|
||||
reply_to = "test_queue"
|
||||
response = MagicMock(spec=DataResponse)
|
||||
snowflake_id = MagicMock()
|
||||
snowflake_id.as_string.return_value = "test_id"
|
||||
response.id = MagicMock(return_value=snowflake_id)
|
||||
response.content_type = MagicMock(return_value="application/json")
|
||||
response.body = MagicMock(return_value=b"test_response")
|
||||
response.base64body = MagicMock(return_value=b"test_response")
|
||||
response.headers = MagicMock(return_value={"Content-Type": ["application/json"]})
|
||||
response.trace_info = MagicMock(return_value={})
|
||||
response.response_code = MagicMock(return_value="200")
|
||||
response.error = MagicMock(return_value="")
|
||||
response.error_cause = MagicMock(return_value="")
|
||||
|
||||
self.reply_to_exchange.publish = AsyncMock()
|
||||
self.reply_to_exchange.name = "test_exchange"
|
||||
|
||||
# Act
|
||||
await self.handler.send_reply(reply_to, response)
|
||||
|
||||
# Assert
|
||||
self.reply_to_exchange.publish.assert_called_once()
|
||||
call_args = self.reply_to_exchange.publish.call_args[1]
|
||||
self.assertEqual(call_args["routing_key"], reply_to)
|
||||
self.assertIsInstance(call_args["message"], Message)
|
||||
|
||||
async def test_reply_received_callback(self):
|
||||
# Arrange
|
||||
message = MagicMock(spec=AbstractIncomingMessage)
|
||||
message.correlation_id = "test_id"
|
||||
# Create a properly formatted response message body with magic byte
|
||||
message.body = (
|
||||
b"\002\027A64f3b5af4d00000a4000|r=200|c=application/json|e=|f=|t={}|b=test_response"
|
||||
)
|
||||
message.delivery_tag = 123
|
||||
message.headers = {}
|
||||
message.properties = {}
|
||||
message.ack = AsyncMock()
|
||||
|
||||
future = asyncio.Future()
|
||||
self.handler.outstanding["test_id"] = future
|
||||
|
||||
expected_body = MagicMock()
|
||||
|
||||
with patch.object(DataResponseFactory, "from_bytes") as mock_from_bytes:
|
||||
mock_from_bytes.return_value = expected_body
|
||||
|
||||
# Act
|
||||
await self.handler.reply_received_callback(message)
|
||||
|
||||
# Assert
|
||||
message.ack.assert_called_once()
|
||||
self.assertNotIn("test_id", self.handler.outstanding)
|
||||
self.assertEqual(future.result(), expected_body.body())
|
||||
|
||||
async def test_run_http_request_magic(self):
|
||||
# Arrange
|
||||
message = MagicMock(spec=AbstractIncomingMessage)
|
||||
message.reply_to = "test_reply_to"
|
||||
message.delivery_tag = 123
|
||||
|
||||
amq_message = MagicMock(spec=DataMessage)
|
||||
amq_message.magic.return_value = DataMessageMagicByte.HTTP_REQUEST.value
|
||||
|
||||
self.service_adapter.on_message = AsyncMock(return_value=MagicMock(spec=DataResponse))
|
||||
self.backpressure_handler.check_overload_condition = AsyncMock()
|
||||
self.handler.send_reply = AsyncMock()
|
||||
self.rabbit_mq_client.connection = MagicMock()
|
||||
|
||||
# Act
|
||||
await self.handler.run_http_request_magic(message, amq_message, self.loop)
|
||||
|
||||
# Assert
|
||||
self.backpressure_handler.increase_parallel_executions.assert_called_once()
|
||||
self.backpressure_handler.decrease_parallel_executions.assert_called_once()
|
||||
self.service_adapter.on_message.assert_called_once()
|
||||
self.handler.send_reply.assert_called_once()
|
||||
@@ -0,0 +1,185 @@
|
||||
import unittest
|
||||
from unittest.mock import AsyncMock, MagicMock, Mock, patch
|
||||
|
||||
from amqp.config.amq_configuration import AMQAdapter, AMQConfiguration
|
||||
from amqp.model.model import DataMessage
|
||||
from amqp.service.amq_service import AMQService
|
||||
|
||||
|
||||
class TestAMQService(unittest.IsolatedAsyncioTestCase):
|
||||
def setUp(self):
|
||||
# Create a mock configuration
|
||||
self.mock_adapter = Mock(spec=AMQAdapter)
|
||||
self.mock_adapter.generator_id = 12345
|
||||
self.mock_adapter.service_name = "test-service"
|
||||
self.mock_adapter.route_mapping = {}
|
||||
self.mock_adapter.swarm_service_id = "test-swarm-id"
|
||||
self.mock_adapter.swarm_task_id = "test-task-id"
|
||||
|
||||
self.mock_config = Mock(spec=AMQConfiguration)
|
||||
self.mock_config.amq_adapter = self.mock_adapter
|
||||
|
||||
# Add dispatch configuration
|
||||
self.mock_dispatch = Mock()
|
||||
self.mock_dispatch.amq_host = "localhost"
|
||||
self.mock_dispatch.amq_port = 5672
|
||||
self.mock_dispatch.rabbit_mq_user = "guest"
|
||||
self.mock_dispatch.rabbit_mq_password = "guest"
|
||||
self.mock_dispatch.use_dlq = False
|
||||
self.mock_config.dispatch = self.mock_dispatch
|
||||
|
||||
# Add backpressure configuration
|
||||
self.mock_backpressure = Mock()
|
||||
self.mock_backpressure.threshold_threads = 10
|
||||
self.mock_config.backpressure = self.mock_backpressure
|
||||
|
||||
# Create service instance with mocked configuration
|
||||
self.service = AMQService(self.mock_config)
|
||||
|
||||
# Mock the event loop
|
||||
self.mock_loop = Mock()
|
||||
self.service.loop = self.mock_loop
|
||||
|
||||
def tearDown(self):
|
||||
# Clean up any resources
|
||||
if hasattr(self, "service"):
|
||||
self.service.cleanup()
|
||||
|
||||
@patch("amqp.service.amq_service.RabbitMQClient")
|
||||
@patch("amqp.service.amq_service.DataMessageFactory")
|
||||
@patch("amqp.service.amq_service.ServiceMessageFactory")
|
||||
@patch("amqp.service.amq_service.initialize_telemetry")
|
||||
def test_init(
|
||||
self, mock_telemetry, mock_service_factory, mock_data_factory, mock_rabbit_client
|
||||
):
|
||||
# Test initialization of AMQService
|
||||
service = AMQService(self.mock_config)
|
||||
|
||||
# Verify that all required components are initialized
|
||||
self.assertIsNotNone(service.amq_configuration)
|
||||
self.assertIsNotNone(service.data_message_factory)
|
||||
self.assertIsNotNone(service.rabbit_mq_client)
|
||||
self.assertIsNotNone(service.tracer)
|
||||
self.assertIsNotNone(service.service_message_factory)
|
||||
|
||||
@patch("amqp.service.amq_service.RabbitMQClient")
|
||||
async def test_register_routes_valid(self, mock_rabbit_client):
|
||||
# Setup mock route mapping
|
||||
self.mock_adapter.route_mapping = "test-service:test-exchange:test-queue:test.#:5:0"
|
||||
|
||||
# Setup mock rabbit client
|
||||
mock_client = Mock()
|
||||
mock_client.register_inbound_routes = AsyncMock()
|
||||
mock_client.register_data_reply_callback = AsyncMock()
|
||||
mock_client.init = AsyncMock()
|
||||
mock_client.request_routes = AsyncMock()
|
||||
mock_client.channel = Mock()
|
||||
mock_client.channel.is_closed = False
|
||||
self.service.rabbit_mq_client = mock_client
|
||||
|
||||
# Setup mock service adapter
|
||||
mock_service_adapter = Mock()
|
||||
mock_service_adapter.loop = self.mock_loop
|
||||
|
||||
# Setup mock message handler
|
||||
mock_handler = Mock()
|
||||
mock_handler.inbound_data_message_callback_no_thread = AsyncMock()
|
||||
mock_handler.reply_received_callback = AsyncMock()
|
||||
self.service.amq_data_message_handler = mock_handler
|
||||
|
||||
# Initialize the service
|
||||
await self.service.init(self.mock_loop, mock_service_adapter)
|
||||
|
||||
# Call register_routes
|
||||
await self.service.register_routes()
|
||||
|
||||
# Verify that routes were registered twice (once in init, once in register_routes)
|
||||
self.assertEqual(mock_client.register_inbound_routes.call_count, 2)
|
||||
self.assertEqual(mock_client.register_data_reply_callback.call_count, 2)
|
||||
|
||||
@patch("amqp.service.amq_service.RabbitMQClient")
|
||||
async def test_register_routes_invalid(self, mock_rabbit_client):
|
||||
# Setup invalid route mapping
|
||||
self.mock_adapter.route_mapping = None
|
||||
|
||||
# Setup mock rabbit client
|
||||
mock_client = Mock()
|
||||
mock_client.register_inbound_routes = AsyncMock()
|
||||
mock_client.register_data_reply_callback = AsyncMock()
|
||||
self.service.rabbit_mq_client = mock_client
|
||||
|
||||
# Call register_routes
|
||||
result = await self.service.register_routes()
|
||||
|
||||
# Verify that no routes were registered
|
||||
mock_client.register_inbound_routes.assert_not_called()
|
||||
mock_client.register_data_reply_callback.assert_not_called()
|
||||
self.assertIsNone(result)
|
||||
|
||||
@patch("amqp.service.amq_service.RabbitMQClient")
|
||||
async def test_reinitialize_if_disconnected(self, mock_rabbit_client):
|
||||
# Setup mock route mapping
|
||||
self.mock_adapter.route_mapping = "test-service:test-exchange:test-queue:test.#:5:0"
|
||||
|
||||
# Setup mock rabbit client with closed channel
|
||||
mock_client = AsyncMock()
|
||||
mock_client.channel = Mock()
|
||||
mock_client.channel.is_closed = True
|
||||
mock_client.init = AsyncMock()
|
||||
mock_client.register_inbound_routes = AsyncMock()
|
||||
mock_client.register_data_reply_callback = AsyncMock()
|
||||
mock_client.request_routes = AsyncMock()
|
||||
|
||||
# Make the constructor return our mock client
|
||||
mock_rabbit_client.return_value = mock_client
|
||||
self.service.rabbit_mq_client = mock_client
|
||||
|
||||
# Setup mock message handler
|
||||
mock_handler = Mock()
|
||||
mock_handler.inbound_data_message_callback_no_thread = AsyncMock()
|
||||
mock_handler.reply_received_callback = AsyncMock()
|
||||
self.service.amq_data_message_handler = mock_handler
|
||||
|
||||
# Call reinitialize_if_disconnected
|
||||
await self.service.reinitialize_if_disconnected()
|
||||
|
||||
# Verify that client was reinitialized
|
||||
mock_client.init.assert_called_once()
|
||||
|
||||
def test_send_message(self):
|
||||
# Create mock message and future
|
||||
mock_message = Mock(spec=DataMessage)
|
||||
mock_message.id.return_value = "test-id"
|
||||
mock_future = MagicMock()
|
||||
|
||||
# Setup mock message handler
|
||||
self.service.amq_data_message_handler = Mock()
|
||||
self.service.amq_data_message_handler.outstanding = {}
|
||||
|
||||
# Send message
|
||||
self.service.send_message(mock_message, mock_future)
|
||||
|
||||
# Verify message was added to outstanding
|
||||
self.assertEqual(self.service.amq_data_message_handler.outstanding["test-id"], mock_future)
|
||||
|
||||
def test_remove_outstanding_future(self):
|
||||
# Setup mock message handler with outstanding message
|
||||
self.service.amq_data_message_handler = Mock()
|
||||
self.service.amq_data_message_handler.outstanding = {"test-id": Mock()}
|
||||
|
||||
# Remove future
|
||||
self.service.remove_outstanding_future("test-id")
|
||||
|
||||
# Verify message was removed from outstanding
|
||||
self.assertNotIn("test-id", self.service.amq_data_message_handler.outstanding)
|
||||
|
||||
def test_cleanup(self):
|
||||
# Setup mock rabbit client
|
||||
mock_client = Mock()
|
||||
self.service.rabbit_mq_client = mock_client
|
||||
|
||||
# Call cleanup
|
||||
self.service.cleanup()
|
||||
|
||||
# Verify rabbit client cleanup was called
|
||||
mock_client.cleanup.assert_called_once()
|
||||
Reference in New Issue
Block a user