diff --git a/CHANGELOG.md b/CHANGELOG.md index 14acf22..ba51057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [5.3.0] - 2023-08-07 + +### Changed + +- Adds a new setting option (STOMP_DEFAULT_EXCLUSIVE_QUEUE) that allows the app to create a queue in RabbitMQ as exclusive or not. + ## [5.2.0] - 2023-01-16 ### Changed diff --git a/README.md b/README.md index f7e9966..2c2e940 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,10 @@ defined, this parameter **must** be an integer!   Optional parameter that controls how many seconds django-stomp will wait for a message to be processed. Defaults to 30 seconds. If defined, this parameter **must** be an integer! +***STOMP_DEFAULT_EXCLUSIVE_QUEUE*** + + Optional parameter that defines if the default value of `exclusive` queue parameter will be True or False when creating a queue in RabbitMQ. The default value is `False`. + ## Tests In order to execute tests for ActiveMQ, execute the following: diff --git a/django_stomp/services/consumer.py b/django_stomp/services/consumer.py index 4e15faf..9881fcd 100644 --- a/django_stomp/services/consumer.py +++ b/django_stomp/services/consumer.py @@ -18,6 +18,7 @@ from django_stomp.helpers import only_destination_name from django_stomp.helpers import set_ssl_connection from django_stomp.settings import DEFAULT_STOMP_SSL_VERSION +from django_stomp.settings import STOMP_DEFAULT_EXCLUSIVE_QUEUE from django_stomp.settings import STOMP_PROCESS_MSG_WORKERS from django_stomp.settings import STOMP_USE_SSL @@ -207,6 +208,8 @@ def build_listener( # These two parameters must be set on producer side as well, otherwise you'll get precondition_failed "x-dead-letter-routing-key": create_dlq_destination_from_another_destination(destination_name), "x-dead-letter-exchange": "", + # This parameter below defines if the queue to be created will be exclusive or not. + "exclusive": STOMP_DEFAULT_EXCLUSIVE_QUEUE, } if durable_topic_subscription is True: diff --git a/django_stomp/settings.py b/django_stomp/settings.py index e3a05d8..d359ffb 100644 --- a/django_stomp/settings.py +++ b/django_stomp/settings.py @@ -25,3 +25,4 @@ def eval_settings_otherwise_raise_exception( STOMP_USE_SSL = eval_str_as_boolean(getattr(django_settings, "STOMP_USE_SSL", "False")) DEFAULT_STOMP_SSL_VERSION = getattr(django_settings, "DEFAULT_STOMP_SSL_VERSION", ssl.PROTOCOL_TLS_CLIENT) +STOMP_DEFAULT_EXCLUSIVE_QUEUE = eval_str_as_boolean(getattr(django_settings, "STOMP_DEFAULT_EXCLUSIVE_QUEUE", "False")) diff --git a/pytest.ini b/pytest.ini index 8f0511a..c479be6 100644 --- a/pytest.ini +++ b/pytest.ini @@ -10,4 +10,5 @@ env = D:STOMP_CORRELATION_ID_REQUIRED=True D:STOMP_PROCESS_MSG_ON_BACKGROUND=False D:STOMP_OUTGOING_HEARTBEAT=0 - D:STOMP_INCOMING_HEARTBEAT=0 \ No newline at end of file + D:STOMP_INCOMING_HEARTBEAT=0 + D:STOMP_DEFAULT_EXCLUSIVE_QUEUE=False \ No newline at end of file diff --git a/setup.py b/setup.py index 32c7d73..87c7748 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ setup( name="django-stomp", - version="5.2.0", + version="5.3.0", description="A simple implementation of STOMP with Django", long_description=README, long_description_content_type="text/markdown", diff --git a/tests/conftest.py b/tests/conftest.py index 69c7df1..a802866 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -28,6 +28,7 @@ def pytest_configure(): STOMP_OUTGOING_HEARTBEAT=os.getenv("STOMP_OUTGOING_HEARTBEAT"), STOMP_INCOMING_HEARTBEAT=os.getenv("STOMP_INCOMING_HEARTBEAT"), STOMP_SERVER_VHOST=os.getenv("STOMP_SERVER_VHOST"), + STOMP_DEFAULT_EXCLUSIVE_QUEUE=os.getenv("STOMP_DEFAULT_EXCLUSIVE_QUEUE"), DATABASES={ "default": { "ENGINE": os.getenv("DB_ENGINE", "django.db.backends.sqlite3"), diff --git a/tests/integration/test_execution/test_execution.py b/tests/integration/test_execution/test_execution.py index b3ff198..3337103 100644 --- a/tests/integration/test_execution/test_execution.py +++ b/tests/integration/test_execution/test_execution.py @@ -959,3 +959,29 @@ def test_shouldnt_open_a_new_db_connection_when_there_is_one_still_usable(settin assert all(t.db.connection is not None for t in threads_with_db_connections) listener.close() + + +@pytest.mark.parametrize("setting_value,expected", [(True, True), (False, False)]) +def test_should_create_a_exclusive_queue_by_publisher_at_rabbitmq(mocker, setting_value, expected): + # Arrange - creates a publisher with the exclusive setting header that should create a new queue exclusive or not + mocker.patch("django_stomp.services.consumer.STOMP_DEFAULT_EXCLUSIVE_QUEUE", setting_value) + queue_name = f"my-test-exclusive-destination-{uuid.uuid4()}" + destination_three = f"/queue/{queue_name}" + start_processing( + destination_three, + callback_move_and_ack_path, + is_testing=True, + return_listener=True, + ) + try: + queue_status = current_queue_configuration(queue_name) + assert queue_status.number_of_pending_messages == 0 + assert queue_status.number_of_consumers == 1 + assert queue_status.messages_enqueued == 0 + assert queue_status.messages_dequeued == 0 + assert ( + queue_status.is_exclusive_destination_queue == None + ), "Header parameter 'exclusive' cannot change queue behavior in ActiveMQ" + except Exception: + queue_status = rabbitmq.current_queue_configuration(queue_name) + assert queue_status.is_exclusive_destination_queue == expected, "The queue must be exclusive or not" diff --git a/tests/support/dtos.py b/tests/support/dtos.py index a0a69fc..07642a9 100644 --- a/tests/support/dtos.py +++ b/tests/support/dtos.py @@ -9,6 +9,7 @@ class CurrentDestinationStatus: number_of_consumers: int messages_enqueued: int messages_dequeued: int + is_exclusive_destination_queue: Optional[bool] = None @dataclass(frozen=True) diff --git a/tests/support/rabbitmq/__init__.py b/tests/support/rabbitmq/__init__.py index 7a76257..acb87b6 100644 --- a/tests/support/rabbitmq/__init__.py +++ b/tests/support/rabbitmq/__init__.py @@ -40,9 +40,14 @@ def current_queue_configuration(queue_name, host="localhost", port=15672) -> Opt number_of_pending_messages = result["messages"] number_of_consumers = result["consumers"] + is_exclusive_destination_queue = result.get("exclusive", False) return CurrentDestinationStatus( - number_of_pending_messages, number_of_consumers, messages_enqueued, messages_dequeued + number_of_pending_messages, + number_of_consumers, + messages_enqueued, + messages_dequeued, + is_exclusive_destination_queue, ) diff --git a/tests/unit/services/test_consumer.py b/tests/unit/services/test_consumer.py index c0228fc..7194e4d 100644 --- a/tests/unit/services/test_consumer.py +++ b/tests/unit/services/test_consumer.py @@ -1,6 +1,8 @@ import json from uuid import uuid4 +import pytest + from django_stomp import builder from django_stomp.services.consumer import StompFrame from django_stomp.services.consumer import build_listener @@ -79,3 +81,41 @@ def test_should_have_only_one_django_stomp_listener_even_if_set_listener_is_call django_stomp_listener_id = django_stomp_listener._listener_id assert django_stomp_listener._connection.get_listener(django_stomp_listener_id) is not None + + +def test_listener_connection_configuration_must_have_headers_properly_configured(): + # Arrange - defines the expected headers setup + headers_setup_mapping = { + "client-id": str, + "activemq.prefetchSize": str, + "prefetch-count": str, + "x-dead-letter-routing-key": str, + "x-dead-letter-exchange": str, + "exclusive": bool, + } + + # Arrange - build listener to some arbirtrary queue and get its connection configuration headers + django_stomp_listener = builder.build_listener(f"some-destination-{uuid4()}") + created_header_setup = django_stomp_listener._connection_configuration["headers"] + + assert headers_setup_mapping.keys() == created_header_setup.keys(), "headers do not have all required keys" + + for key, value in headers_setup_mapping.items(): + assert isinstance(created_header_setup[key], value), "header has wrong type" + + assert int(created_header_setup["prefetch-count"]), "prefetch-count must be a number" + + +@pytest.mark.parametrize("setting_value,expected", [(True, True), (False, False)]) +def test_should_have_a_listener_with_param_exclusive_in_connection_configuration_headers( + mocker, setting_value, expected +): + # Arrange - mock the stomp default exclusive queue setting + mocker.patch("django_stomp.services.consumer.STOMP_DEFAULT_EXCLUSIVE_QUEUE", setting_value) + + # Arrange - build listener to some arbirtrary queue + django_stomp_listener = builder.build_listener(f"some-destination-{uuid4()}") + + assert ( + django_stomp_listener._connection_configuration["headers"]["exclusive"] is expected + ), "The stomp default exclusive queue parameter was not configured correctly."