+
+ {{ include('@Enqueue/Icon/icon.svg') }}
Message Queue
{% endblock %}
{% block panel %}
+ {% if collector.count > 0 %}
Sent messages
-
-
-
- | # |
- Topic |
- Message |
- Priority |
-
-
-
- {% for sentMessage in collector.sentMessages %}
-
- | {{ loop.index }} |
- {{ sentMessage.topic }} |
- {{ collector.prettyPrintMessage(sentMessage.body)|raw }} |
- {{ collector.prettyPrintPriority(sentMessage.priority) }} |
-
- {% endfor %}
-
+ {% for clientName, sentMessages in collector.sentMessages %}
+ {% if sentMessages|length > 0 %}
+ Client: {{ clientName }}
+
+
+
+ | # |
+ Topic |
+ Command |
+ Message |
+ Priority |
+ Time |
+
+
+
+ {% for sentMessage in sentMessages %}
+
+ | {{ loop.index }} |
+ {{ sentMessage.topic|default(null) }} |
+ {{ sentMessage.command|default(null) }} |
+
+
+ {% set body = collector.ensureString(sentMessage.body) %}
+ {{ body|length > 40 ? body|slice(0, 40) ~ '...' : body }}
+
+ {% if body|length > 40 %}
+ Show body
+
+ {% endif %}
+ |
+ {{ collector.prettyPrintPriority(sentMessage.priority) }}
+ |
+
+ {{ sentMessage.sentAt|date('i:s.v') }}
+ |
+
+ {% endfor %}
+
-
+
+ {% endif %}
+ {% endfor %}
+ {% else %}
+
+
No messages were sent.
+
+ {% endif %}
{% endblock %}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/AbstractAsyncListener.php b/pkg/enqueue-bundle/Tests/Functional/App/AbstractAsyncListener.php
new file mode 100644
index 000000000..acf7406e1
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/AbstractAsyncListener.php
@@ -0,0 +1,48 @@
+producer = $producer;
+ $this->registry = $registry;
+ }
+
+ /**
+ * @param Event|ContractEvent $event
+ * @param string $eventName
+ */
+ protected function onEventInternal($event, $eventName)
+ {
+ if (false == $this->isSyncMode($eventName)) {
+ $transformerName = $this->registry->getTransformerNameForEvent($eventName);
+
+ $interopMessage = $this->registry->getTransformer($transformerName)->toMessage($eventName, $event);
+ $message = new Message($interopMessage->getBody());
+ $message->setScope(Message::SCOPE_APP);
+ $message->setProperty('event_name', $eventName);
+ $message->setProperty('transformer_name', $transformerName);
+
+ $this->producer->sendCommand(Commands::DISPATCH_ASYNC_EVENTS, $message);
+ }
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/AppKernel.php b/pkg/enqueue-bundle/Tests/Functional/App/AppKernel.php
new file mode 100644
index 000000000..3cafeedda
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/AppKernel.php
@@ -0,0 +1,46 @@
+load(__DIR__.'/config/config-sf5.yml');
+
+ return;
+ }
+
+ $loader->load(__DIR__.'/config/config.yml');
+ }
+
+ protected function getContainerClass(): string
+ {
+ return parent::getContainerClass().'BundleDefault';
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/AsyncListener.php b/pkg/enqueue-bundle/Tests/Functional/App/AsyncListener.php
new file mode 100644
index 000000000..23ab4af79
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/AsyncListener.php
@@ -0,0 +1,16 @@
+onEventInternal($event, $eventName);
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/CustomAppKernel.php b/pkg/enqueue-bundle/Tests/Functional/App/CustomAppKernel.php
new file mode 100644
index 000000000..81d73796e
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/CustomAppKernel.php
@@ -0,0 +1,81 @@
+ [
+ 'client' => [
+ 'prefix' => 'enqueue',
+ 'app_name' => '',
+ 'router_topic' => 'test',
+ 'router_queue' => 'test',
+ 'default_queue' => 'test',
+ ],
+ ],
+ ];
+
+ public function setEnqueueConfig(array $config): void
+ {
+ $this->enqueueConfig = array_replace_recursive($this->enqueueConfig, $config);
+ $this->enqueueConfig['default']['client']['app_name'] = str_replace('.', '', uniqid('app_name', true));
+ $this->enqueueConfigId = md5(json_encode($this->enqueueConfig));
+
+ $fs = new Filesystem();
+ $fs->remove(sys_get_temp_dir().'/EnqueueBundleCustom/cache/'.$this->enqueueConfigId);
+ $fs->mkdir(sys_get_temp_dir().'/EnqueueBundleCustom/cache/'.$this->enqueueConfigId);
+ }
+
+ public function registerBundles(): iterable
+ {
+ $bundles = [
+ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+ new \Doctrine\Bundle\DoctrineBundle\DoctrineBundle(),
+ new \Enqueue\Bundle\EnqueueBundle(),
+ ];
+
+ return $bundles;
+ }
+
+ public function getCacheDir(): string
+ {
+ return sys_get_temp_dir().'/EnqueueBundleCustom/cache/'.$this->enqueueConfigId;
+ }
+
+ public function getLogDir(): string
+ {
+ return sys_get_temp_dir().'/EnqueueBundleCustom/cache/logs/'.$this->enqueueConfigId;
+ }
+
+ protected function getContainerClass(): string
+ {
+ return parent::getContainerClass().'Custom'.$this->enqueueConfigId;
+ }
+
+ protected function configureContainer(ContainerBuilder $c, LoaderInterface $loader)
+ {
+ if (self::VERSION_ID < 60000) {
+ $loader->load(__DIR__.'/config/custom-config-sf5.yml');
+ } else {
+ $loader->load(__DIR__.'/config/custom-config.yml');
+ }
+
+ $c->loadFromExtension('enqueue', $this->enqueueConfig);
+ }
+
+ protected function configureRoutes(RoutingConfigurator $routes)
+ {
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/SqsCustomConnectionFactoryFactory.php b/pkg/enqueue-bundle/Tests/Functional/App/SqsCustomConnectionFactoryFactory.php
new file mode 100644
index 000000000..6ed70cd65
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/SqsCustomConnectionFactoryFactory.php
@@ -0,0 +1,30 @@
+container = $container;
+ }
+
+ public function create($config): ConnectionFactory
+ {
+ if (false == isset($config['service'])) {
+ throw new \LogicException('The sqs client has to be set');
+ }
+
+ return new SqsConnectionFactory($this->container->get($config['service']));
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncEventTransformer.php b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncEventTransformer.php
new file mode 100644
index 000000000..0a83a04b6
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncEventTransformer.php
@@ -0,0 +1,51 @@
+context = $context;
+ }
+
+ public function toMessage($eventName, ?Event $event = null)
+ {
+ if (Event::class === $event::class) {
+ return $this->context->createMessage(json_encode(''));
+ }
+
+ /** @var GenericEvent $event */
+ if (false == $event instanceof GenericEvent) {
+ throw new \LogicException('Must be GenericEvent');
+ }
+
+ return $this->context->createMessage(json_encode([
+ 'subject' => $event->getSubject(),
+ 'arguments' => $event->getArguments(),
+ ]));
+ }
+
+ public function toEvent($eventName, Message $message)
+ {
+ $data = JSON::decode($message->getBody());
+
+ if ('' === $data) {
+ return new Event();
+ }
+
+ return new GenericEvent($data['subject'], $data['arguments']);
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncListener.php b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncListener.php
new file mode 100644
index 000000000..fd0ec1d91
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncListener.php
@@ -0,0 +1,15 @@
+calls[] = func_get_args();
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncSubscriber.php b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncSubscriber.php
new file mode 100644
index 000000000..cd3beb45b
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestAsyncSubscriber.php
@@ -0,0 +1,21 @@
+calls[] = func_get_args();
+ }
+
+ public static function getSubscribedEvents()
+ {
+ return ['test_async_subscriber' => 'onEvent'];
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestCommandSubscriberProcessor.php b/pkg/enqueue-bundle/Tests/Functional/App/TestCommandSubscriberProcessor.php
new file mode 100644
index 000000000..480992ff1
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestCommandSubscriberProcessor.php
@@ -0,0 +1,28 @@
+calls[] = $message;
+
+ return Result::reply(
+ $context->createMessage($message->getBody().'Reply')
+ );
+ }
+
+ public static function getSubscribedCommand()
+ {
+ return 'theCommand';
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestExclusiveCommandSubscriberProcessor.php b/pkg/enqueue-bundle/Tests/Functional/App/TestExclusiveCommandSubscriberProcessor.php
new file mode 100644
index 000000000..999ad1f24
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestExclusiveCommandSubscriberProcessor.php
@@ -0,0 +1,32 @@
+calls[] = $message;
+
+ return Result::ACK;
+ }
+
+ public static function getSubscribedCommand()
+ {
+ return [
+ 'command' => 'theExclusiveCommandName',
+ 'processor' => 'theExclusiveCommandName',
+ 'queue' => 'the_exclusive_command_queue',
+ 'prefix_queue' => true,
+ 'exclusive' => true,
+ ];
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/TestTopicSubscriberProcessor.php b/pkg/enqueue-bundle/Tests/Functional/App/TestTopicSubscriberProcessor.php
new file mode 100644
index 000000000..2b9f16ead
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/TestTopicSubscriberProcessor.php
@@ -0,0 +1,28 @@
+calls[] = $message;
+
+ return Result::reply(
+ $context->createMessage($message->getBody().'Reply')
+ );
+ }
+
+ public static function getSubscribedTopics()
+ {
+ return 'theTopic';
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/config/config-sf5.yml b/pkg/enqueue-bundle/Tests/Functional/App/config/config-sf5.yml
new file mode 100644
index 000000000..e202bb86f
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/config/config-sf5.yml
@@ -0,0 +1,129 @@
+parameters:
+ locale: 'en'
+ secret: 'ThisTokenIsNotSoSecretChangeIt'
+
+
+framework:
+ #esi: ~
+ #translator: { fallback: "%locale%" }
+ test: ~
+ assets: false
+ session:
+ # option incompatible with Symfony 6
+ storage_id: session.storage.mock_file
+ secret: '%secret%'
+ router: { resource: '%kernel.project_dir%/config/routing.yml' }
+ default_locale: '%locale%'
+
+doctrine:
+ dbal:
+ url: "%env(DOCTRINE_DSN)%"
+ driver: pdo_mysql
+ charset: UTF8
+
+enqueue:
+ default:
+ transport: 'null:'
+ client:
+ traceable_producer: true
+ job: true
+ async_events: true
+ async_commands:
+ enabled: true
+ timeout: 60
+ command_name: ~
+ queue_name: ~
+
+services:
+ test_enqueue.client.default.traceable_producer:
+ alias: 'enqueue.client.default.traceable_producer'
+ public: true
+
+ test_enqueue.transport.default.queue_consumer:
+ alias: 'enqueue.transport.default.queue_consumer'
+ public: true
+
+ test_enqueue.client.default.queue_consumer:
+ alias: 'enqueue.client.default.queue_consumer'
+ public: true
+
+ test_enqueue.transport.default.rpc_client:
+ alias: 'enqueue.transport.default.rpc_client'
+ public: true
+
+ test_enqueue.client.default.producer:
+ alias: 'enqueue.client.default.producer'
+ public: true
+
+ test_enqueue.client.default.spool_producer:
+ alias: 'enqueue.client.default.spool_producer'
+ public: true
+
+ test_Enqueue\Client\ProducerInterface:
+ alias: 'Enqueue\Client\ProducerInterface'
+ public: true
+
+ test_enqueue.client.default.driver:
+ alias: 'enqueue.client.default.driver'
+ public: true
+
+ test_enqueue.transport.default.context:
+ alias: 'enqueue.transport.default.context'
+ public: true
+
+ test_enqueue.client.consume_command:
+ alias: 'enqueue.client.consume_command'
+ public: true
+
+ test.enqueue.client.routes_command:
+ alias: 'enqueue.client.routes_command'
+ public: true
+
+ test.enqueue.events.async_processor:
+ alias: 'enqueue.events.async_processor'
+ public: true
+
+ test_async_listener:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncListener'
+ public: true
+ tags:
+ - { name: 'kernel.event_listener', async: true, event: 'test_async', method: 'onEvent', dispatcher: 'enqueue.events.event_dispatcher' }
+
+ test_command_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestCommandSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test_topic_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestTopicSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.topic_subscriber', client: 'default' }
+
+ test_exclusive_command_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestExclusiveCommandSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test_async_subscriber:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncSubscriber'
+ public: true
+ tags:
+ - { name: 'kernel.event_subscriber', async: true, dispatcher: 'enqueue.events.event_dispatcher' }
+
+ test_async_event_transformer:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncEventTransformer'
+ public: true
+ arguments:
+ - '@enqueue.transport.default.context'
+ tags:
+ - {name: 'enqueue.event_transformer', eventName: 'test_async', transformerName: 'test_async' }
+ - {name: 'enqueue.event_transformer', eventName: 'test_async_subscriber', transformerName: 'test_async' }
+
+ # overwrite async listener with one based on client producer. so we can use traceable producer.
+ enqueue.events.async_listener:
+ class: 'Enqueue\Bundle\Tests\Functional\App\AsyncListener'
+ public: true
+ arguments: ['@enqueue.client.default.producer', '@enqueue.events.registry']
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/config/config.yml b/pkg/enqueue-bundle/Tests/Functional/App/config/config.yml
new file mode 100644
index 000000000..d3ca2a37f
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/config/config.yml
@@ -0,0 +1,128 @@
+parameters:
+ locale: 'en'
+ secret: 'ThisTokenIsNotSoSecretChangeIt'
+
+
+framework:
+ #esi: ~
+ #translator: { fallback: "%locale%" }
+ test: ~
+ assets: false
+ session:
+ storage_factory_id: session.storage.factory.mock_file
+ secret: '%secret%'
+ router: { resource: '%kernel.project_dir%/config/routing.yml' }
+ default_locale: '%locale%'
+
+doctrine:
+ dbal:
+ url: "%env(DOCTRINE_DSN)%"
+ driver: pdo_mysql
+ charset: UTF8
+
+enqueue:
+ default:
+ transport: 'null:'
+ client:
+ traceable_producer: true
+ job: true
+ async_events: true
+ async_commands:
+ enabled: true
+ timeout: 60
+ command_name: ~
+ queue_name: ~
+
+services:
+ test_enqueue.client.default.traceable_producer:
+ alias: 'enqueue.client.default.traceable_producer'
+ public: true
+
+ test_enqueue.transport.default.queue_consumer:
+ alias: 'enqueue.transport.default.queue_consumer'
+ public: true
+
+ test_enqueue.client.default.queue_consumer:
+ alias: 'enqueue.client.default.queue_consumer'
+ public: true
+
+ test_enqueue.transport.default.rpc_client:
+ alias: 'enqueue.transport.default.rpc_client'
+ public: true
+
+ test_enqueue.client.default.producer:
+ alias: 'enqueue.client.default.producer'
+ public: true
+
+ test_enqueue.client.default.spool_producer:
+ alias: 'enqueue.client.default.spool_producer'
+ public: true
+
+ test_Enqueue\Client\ProducerInterface:
+ alias: 'Enqueue\Client\ProducerInterface'
+ public: true
+
+ test_enqueue.client.default.driver:
+ alias: 'enqueue.client.default.driver'
+ public: true
+
+ test_enqueue.transport.default.context:
+ alias: 'enqueue.transport.default.context'
+ public: true
+
+ test_enqueue.client.consume_command:
+ alias: 'enqueue.client.consume_command'
+ public: true
+
+ test.enqueue.client.routes_command:
+ alias: 'enqueue.client.routes_command'
+ public: true
+
+ test.enqueue.events.async_processor:
+ alias: 'enqueue.events.async_processor'
+ public: true
+
+ test_async_listener:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncListener'
+ public: true
+ tags:
+ - { name: 'kernel.event_listener', async: true, event: 'test_async', method: 'onEvent', dispatcher: 'enqueue.events.event_dispatcher' }
+
+ test_command_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestCommandSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test_topic_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestTopicSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.topic_subscriber', client: 'default' }
+
+ test_exclusive_command_subscriber_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestExclusiveCommandSubscriberProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test_async_subscriber:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncSubscriber'
+ public: true
+ tags:
+ - { name: 'kernel.event_subscriber', async: true, dispatcher: 'enqueue.events.event_dispatcher' }
+
+ test_async_event_transformer:
+ class: 'Enqueue\Bundle\Tests\Functional\App\TestAsyncEventTransformer'
+ public: true
+ arguments:
+ - '@enqueue.transport.default.context'
+ tags:
+ - {name: 'enqueue.event_transformer', eventName: 'test_async', transformerName: 'test_async' }
+ - {name: 'enqueue.event_transformer', eventName: 'test_async_subscriber', transformerName: 'test_async' }
+
+ # overwrite async listener with one based on client producer. so we can use traceable producer.
+ enqueue.events.async_listener:
+ class: 'Enqueue\Bundle\Tests\Functional\App\AsyncListener'
+ public: true
+ arguments: ['@enqueue.client.default.producer', '@enqueue.events.registry']
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config-sf5.yml b/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config-sf5.yml
new file mode 100644
index 000000000..35192652e
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config-sf5.yml
@@ -0,0 +1,85 @@
+parameters:
+ locale: 'en'
+ secret: 'ThisTokenIsNotSoSecretChangeIt'
+
+framework:
+ #esi: ~
+ #translator: { fallback: "%locale%" }
+ test: ~
+ assets: false
+ session:
+ # the only option incompatible with Symfony 6
+ storage_id: session.storage.mock_file
+ secret: '%secret%'
+ router: { resource: '%kernel.project_dir%/config/routing.yml' }
+ default_locale: '%locale%'
+
+doctrine:
+ dbal:
+ connections:
+ custom:
+ url: "%env(DOCTRINE_DSN)%"
+ driver: pdo_mysql
+ charset: UTF8
+
+services:
+ test_enqueue.client.default.driver:
+ alias: 'enqueue.client.default.driver'
+ public: true
+
+ test_enqueue.client.default.producer:
+ alias: 'enqueue.client.default.producer'
+ public: true
+
+ test_enqueue.client.default.lazy_producer:
+ alias: 'enqueue.client.default.lazy_producer'
+ public: true
+
+ test_enqueue.transport.default.context:
+ alias: 'enqueue.transport.default.context'
+ public: true
+
+ test_enqueue.transport.consume_command:
+ alias: 'enqueue.transport.consume_command'
+ public: true
+
+ test_enqueue.client.consume_command:
+ alias: 'enqueue.client.consume_command'
+ public: true
+
+ test_enqueue.client.produce_command:
+ alias: 'enqueue.client.produce_command'
+ public: true
+
+ test_enqueue.client.setup_broker_command:
+ alias: 'enqueue.client.setup_broker_command'
+ public: true
+
+ test.message.processor:
+ class: 'Enqueue\Bundle\Tests\Functional\TestProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.topic_subscriber', client: 'default' }
+ - { name: 'enqueue.transport.processor', transport: 'default' }
+
+ test.message.command_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\TestCommandProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test.sqs_client:
+ public: true
+ class: 'Aws\Sqs\SqsClient'
+ arguments:
+ -
+ endpoint: '%env(AWS_SQS_ENDPOINT)%'
+ region: '%env(AWS_SQS_REGION)%'
+ version: '%env(AWS_SQS_VERSION)%'
+ credentials:
+ key: '%env(AWS_SQS_KEY)%'
+ secret: '%env(AWS_SQS_SECRET)%'
+
+ test.sqs_custom_connection_factory_factory:
+ class: 'Enqueue\Bundle\Tests\Functional\App\SqsCustomConnectionFactoryFactory'
+ arguments: ['@service_container']
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config.yml b/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config.yml
new file mode 100644
index 000000000..d02f3002d
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/config/custom-config.yml
@@ -0,0 +1,84 @@
+parameters:
+ locale: 'en'
+ secret: 'ThisTokenIsNotSoSecretChangeIt'
+
+framework:
+ #esi: ~
+ #translator: { fallback: "%locale%" }
+ test: ~
+ assets: false
+ session:
+ storage_factory_id: session.storage.factory.mock_file
+ secret: '%secret%'
+ router: { resource: '%kernel.project_dir%/config/routing.yml' }
+ default_locale: '%locale%'
+
+doctrine:
+ dbal:
+ connections:
+ custom:
+ url: "%env(DOCTRINE_DSN)%"
+ driver: pdo_mysql
+ charset: UTF8
+
+services:
+ test_enqueue.client.default.driver:
+ alias: 'enqueue.client.default.driver'
+ public: true
+
+ test_enqueue.client.default.producer:
+ alias: 'enqueue.client.default.producer'
+ public: true
+
+ test_enqueue.client.default.lazy_producer:
+ alias: 'enqueue.client.default.lazy_producer'
+ public: true
+
+ test_enqueue.transport.default.context:
+ alias: 'enqueue.transport.default.context'
+ public: true
+
+ test_enqueue.transport.consume_command:
+ alias: 'enqueue.transport.consume_command'
+ public: true
+
+ test_enqueue.client.consume_command:
+ alias: 'enqueue.client.consume_command'
+ public: true
+
+ test_enqueue.client.produce_command:
+ alias: 'enqueue.client.produce_command'
+ public: true
+
+ test_enqueue.client.setup_broker_command:
+ alias: 'enqueue.client.setup_broker_command'
+ public: true
+
+ test.message.processor:
+ class: 'Enqueue\Bundle\Tests\Functional\TestProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.topic_subscriber', client: 'default' }
+ - { name: 'enqueue.transport.processor', transport: 'default' }
+
+ test.message.command_processor:
+ class: 'Enqueue\Bundle\Tests\Functional\TestCommandProcessor'
+ public: true
+ tags:
+ - { name: 'enqueue.command_subscriber', client: 'default' }
+
+ test.sqs_client:
+ public: true
+ class: 'Aws\Sqs\SqsClient'
+ arguments:
+ -
+ endpoint: '%env(AWS_SQS_ENDPOINT)%'
+ region: '%env(AWS_SQS_REGION)%'
+ version: '%env(AWS_SQS_VERSION)%'
+ credentials:
+ key: '%env(AWS_SQS_KEY)%'
+ secret: '%env(AWS_SQS_SECRET)%'
+
+ test.sqs_custom_connection_factory_factory:
+ class: 'Enqueue\Bundle\Tests\Functional\App\SqsCustomConnectionFactoryFactory'
+ arguments: ['@service_container']
diff --git a/pkg/enqueue-bundle/Tests/Functional/app/config/routing.yml b/pkg/enqueue-bundle/Tests/Functional/App/config/routing.yml
similarity index 100%
rename from pkg/enqueue-bundle/Tests/Functional/app/config/routing.yml
rename to pkg/enqueue-bundle/Tests/Functional/App/config/routing.yml
diff --git a/pkg/enqueue-bundle/Tests/Functional/App/console.php b/pkg/enqueue-bundle/Tests/Functional/App/console.php
new file mode 100644
index 000000000..2f518c78f
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/App/console.php
@@ -0,0 +1,12 @@
+#!/usr/bin/env php
+run(new ArgvInput());
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/ConsumeMessagesCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/ConsumeMessagesCommandTest.php
deleted file mode 100644
index 8cad85ec6..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/Client/ConsumeMessagesCommandTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-container->get('enqueue.client.consume_messages_command');
-
- $this->assertInstanceOf(ConsumeMessagesCommand::class, $command);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/DriverTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/DriverTest.php
deleted file mode 100644
index 2900bcdd9..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/Client/DriverTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-container->get('enqueue.client.driver');
-
- $this->assertInstanceOf(DriverInterface::class, $driver);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/MessageProducerTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/MessageProducerTest.php
deleted file mode 100644
index ea9eb9ce9..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/Client/MessageProducerTest.php
+++ /dev/null
@@ -1,27 +0,0 @@
-container->get('enqueue.client.message_producer');
-
- $this->assertInstanceOf(MessageProducerInterface::class, $messageProducer);
- }
-
- public function testCouldBeGetFromContainerAsShortenAlias()
- {
- $messageProducer = $this->container->get('enqueue.client.message_producer');
- $aliasMessageProducer = $this->container->get('enqueue.message_producer');
-
- $this->assertSame($messageProducer, $aliasMessageProducer);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/ProduceMessageCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/ProduceMessageCommandTest.php
deleted file mode 100644
index b003805cf..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/Client/ProduceMessageCommandTest.php
+++ /dev/null
@@ -1,19 +0,0 @@
-container->get('enqueue.client.produce_message_command');
-
- $this->assertInstanceOf(ProduceMessageCommand::class, $command);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/ProducerTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/ProducerTest.php
new file mode 100644
index 000000000..29a96aa7d
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/Client/ProducerTest.php
@@ -0,0 +1,125 @@
+get('test_'.ProducerInterface::class);
+
+ $this->assertInstanceOf(ProducerInterface::class, $producer);
+ }
+
+ public function testCouldBeGetFromContainerByServiceId()
+ {
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $this->assertInstanceOf(ProducerInterface::class, $producer);
+ }
+
+ public function testShouldSendEvent()
+ {
+ /** @var ProducerInterface $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $producer->sendEvent('theTopic', 'theMessage');
+
+ $traces = $this->getTraceableProducer()->getTopicTraces('theTopic');
+
+ $this->assertCount(1, $traces);
+ $this->assertEquals('theMessage', $traces[0]['body']);
+ }
+
+ public function testShouldSendCommandWithoutNeedForReply()
+ {
+ /** @var ProducerInterface $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $result = $producer->sendCommand('theCommand', 'theMessage', false);
+
+ $this->assertNull($result);
+
+ $traces = $this->getTraceableProducer()->getCommandTraces('theCommand');
+
+ $this->assertCount(1, $traces);
+ $this->assertEquals('theMessage', $traces[0]['body']);
+ }
+
+ public function testShouldSendMessageInstanceAsCommandWithoutNeedForReply()
+ {
+ /** @var ProducerInterface $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $message = new Message('theMessage');
+
+ $result = $producer->sendCommand('theCommand', $message, false);
+
+ $this->assertNull($result);
+
+ $traces = $this->getTraceableProducer()->getCommandTraces('theCommand');
+
+ $this->assertCount(1, $traces);
+ $this->assertEquals('theMessage', $traces[0]['body']);
+ $this->assertEquals([
+ 'enqueue.processor' => 'test_command_subscriber_processor',
+ 'enqueue.command' => 'theCommand',
+ ], $traces[0]['properties']);
+ }
+
+ public function testShouldSendExclusiveCommandWithNeedForReply()
+ {
+ /** @var ProducerInterface $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $message = new Message('theMessage');
+
+ $result = $producer->sendCommand('theExclusiveCommandName', $message, false);
+
+ $this->assertNull($result);
+
+ $traces = $this->getTraceableProducer()->getCommandTraces('theExclusiveCommandName');
+
+ $this->assertCount(1, $traces);
+ $this->assertEquals('theMessage', $traces[0]['body']);
+ $this->assertEquals([
+ 'enqueue.processor' => 'theExclusiveCommandName',
+ 'enqueue.command' => 'theExclusiveCommandName',
+ ], $traces[0]['properties']);
+ }
+
+ public function testShouldSendMessageInstanceCommandWithNeedForReply()
+ {
+ /** @var ProducerInterface $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $message = new Message('theMessage');
+
+ $result = $producer->sendCommand('theCommand', $message, true);
+
+ $this->assertInstanceOf(Promise::class, $result);
+
+ $traces = $this->getTraceableProducer()->getCommandTraces('theCommand');
+
+ $this->assertCount(1, $traces);
+ $this->assertEquals('theMessage', $traces[0]['body']);
+ $this->assertEquals([
+ 'enqueue.processor' => 'test_command_subscriber_processor',
+ 'enqueue.command' => 'theCommand',
+ ], $traces[0]['properties']);
+ }
+
+ private function getTraceableProducer(): TraceableProducer
+ {
+ return static::$container->get('test_enqueue.client.default.traceable_producer');
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Client/SpoolProducerTest.php b/pkg/enqueue-bundle/Tests/Functional/Client/SpoolProducerTest.php
new file mode 100644
index 000000000..0bba43327
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/Client/SpoolProducerTest.php
@@ -0,0 +1,19 @@
+get('test_enqueue.client.default.spool_producer');
+
+ $this->assertInstanceOf(SpoolProducer::class, $producer);
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/ConsumeMessagesCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/ConsumeMessagesCommandTest.php
deleted file mode 100644
index 4e3825f21..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/ConsumeMessagesCommandTest.php
+++ /dev/null
@@ -1,105 +0,0 @@
-removeExchange('amqp.test');
- $this->removeQueue('amqp.app.test');
-
- $driver = $this->container->get('enqueue.client.driver');
- $driver->setupBroker();
- }
-
- public function testCouldBeGetFromContainerAsService()
- {
- $command = $this->container->get('enqueue.client.consume_messages_command');
-
- $this->assertInstanceOf(ConsumeMessagesCommand::class, $command);
- }
-
- public function testClientConsumeMessagesCommandShouldConsumeMessage()
- {
- $command = $this->container->get('enqueue.client.consume_messages_command');
- $processor = $this->container->get('test.message.processor');
-
- $this->getMessageProducer()->send(TestProcessor::TOPIC, 'test message body');
-
- $tester = new CommandTester($command);
- $tester->execute([
- '--message-limit' => 2,
- '--time-limit' => 'now +10 seconds',
- ]);
-
- $this->assertInstanceOf(AmqpMessage::class, $processor->message);
- $this->assertEquals('test message body', $processor->message->getBody());
- }
-
- public function testClientConsumeMessagesFromExplicitlySetQueue()
- {
- $command = $this->container->get('enqueue.client.consume_messages_command');
- $processor = $this->container->get('test.message.processor');
-
- $this->getMessageProducer()->send(TestProcessor::TOPIC, 'test message body');
-
- $tester = new CommandTester($command);
- $tester->execute([
- '--message-limit' => 2,
- '--time-limit' => 'now +10 seconds',
- 'client-queue-names' => ['test'],
- ]);
-
- $this->assertInstanceOf(AmqpMessage::class, $processor->message);
- $this->assertEquals('test message body', $processor->message->getBody());
- }
-
- public function testTransportConsumeMessagesCommandShouldConsumeMessage()
- {
- $command = $this->container->get('enqueue.command.consume_messages');
- $command->setContainer($this->container);
- $processor = $this->container->get('test.message.processor');
-
- $this->getMessageProducer()->send(TestProcessor::TOPIC, 'test message body');
-
- $tester = new CommandTester($command);
- $tester->execute([
- '--message-limit' => 1,
- '--time-limit' => '+10sec',
- 'queue' => 'amqp.app.test',
- 'processor-service' => 'test.message.processor',
- ]);
-
- $this->assertInstanceOf(AmqpMessage::class, $processor->message);
- $this->assertEquals('test message body', $processor->message->getBody());
- }
-
- /**
- * @return string
- */
- public static function getKernelClass()
- {
- include_once __DIR__.'/app/AmqpAppKernel.php';
-
- return AmqpAppKernel::class;
- }
-
- private function getMessageProducer()
- {
- return $this->container->get('enqueue.client.message_producer');
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/ContextTest.php b/pkg/enqueue-bundle/Tests/Functional/ContextTest.php
index f11a9b17a..87e2d95a0 100644
--- a/pkg/enqueue-bundle/Tests/Functional/ContextTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/ContextTest.php
@@ -2,7 +2,7 @@
namespace Enqueue\Bundle\Tests\Functional;
-use Enqueue\Psr\Context;
+use Interop\Queue\Context;
/**
* @group functional
@@ -11,7 +11,7 @@ class ContextTest extends WebTestCase
{
public function testCouldBeGetFromContainerAsService()
{
- $connection = $this->container->get('enqueue.transport.context');
+ $connection = static::$container->get('test_enqueue.transport.default.context');
$this->assertInstanceOf(Context::class, $connection);
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Events/AsyncListenerTest.php b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncListenerTest.php
new file mode 100644
index 000000000..7fb6fdd86
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncListenerTest.php
@@ -0,0 +1,111 @@
+get('enqueue.events.async_listener');
+
+ $asyncListener->resetSyncMode();
+ static::$container->get('test_async_subscriber')->calls = [];
+ static::$container->get('test_async_listener')->calls = [];
+ }
+
+ public function testShouldNotCallRealListenerIfMarkedAsAsync()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $this->dispatch($dispatcher, new GenericEvent('aSubject'), 'test_async');
+
+ /** @var TestAsyncListener $listener */
+ $listener = static::$container->get('test_async_listener');
+
+ $this->assertEmpty($listener->calls);
+ }
+
+ public function testShouldSendMessageToExpectedCommandInsteadOfCallingRealListener()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $event = new GenericEvent('theSubject', ['fooArg' => 'fooVal']);
+
+ $this->dispatch($dispatcher, $event, 'test_async');
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(1, $traces);
+
+ $this->assertEquals(Commands::DISPATCH_ASYNC_EVENTS, $traces[0]['command']);
+ $this->assertEquals('{"subject":"theSubject","arguments":{"fooArg":"fooVal"}}', $traces[0]['body']);
+ }
+
+ public function testShouldSendMessageForEveryDispatchCall()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async');
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async');
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async');
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(3, $traces);
+ }
+
+ public function testShouldSendMessageIfDispatchedFromInsideListener()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $eventName = 'an_event_'.uniqid();
+ $dispatcher->addListener($eventName, function ($event, $eventName, EventDispatcherInterface $dispatcher) {
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async');
+ });
+
+ $this->dispatch($dispatcher, new GenericEvent(), $eventName);
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(1, $traces);
+ }
+
+ private function dispatch(EventDispatcherInterface $dispatcher, $event, $eventName): void
+ {
+ if (!class_exists(Event::class)) {
+ // Symfony 5
+ $dispatcher->dispatch($event, $eventName);
+ } else {
+ // Symfony < 5
+ $dispatcher->dispatch($eventName, $event);
+ }
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Events/AsyncProcessorTest.php b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncProcessorTest.php
new file mode 100644
index 000000000..d85567509
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncProcessorTest.php
@@ -0,0 +1,123 @@
+get('enqueue.events.async_listener');
+
+ $asyncListener->resetSyncMode();
+ static::$container->get('test_async_subscriber')->calls = [];
+ static::$container->get('test_async_listener')->calls = [];
+ }
+
+ public function testCouldBeGetFromContainerAsService()
+ {
+ /** @var AsyncProcessor $processor */
+ $processor = static::$container->get('test.enqueue.events.async_processor');
+
+ $this->assertInstanceOf(AsyncProcessor::class, $processor);
+ }
+
+ public function testShouldRejectIfMessageDoesNotContainEventNameProperty()
+ {
+ /** @var AsyncProcessor $processor */
+ $processor = static::$container->get('test.enqueue.events.async_processor');
+
+ $message = new NullMessage();
+
+ $this->assertEquals(Processor::REJECT, $processor->process($message, new NullContext()));
+ }
+
+ public function testShouldRejectIfMessageDoesNotContainTransformerNameProperty()
+ {
+ /** @var AsyncProcessor $processor */
+ $processor = static::$container->get('test.enqueue.events.async_processor');
+
+ $message = new NullMessage();
+ $message->setProperty('event_name', 'anEventName');
+
+ $this->assertEquals(Processor::REJECT, $processor->process($message, new NullContext()));
+ }
+
+ public function testShouldCallRealListener()
+ {
+ /** @var AsyncProcessor $processor */
+ $processor = static::$container->get('test.enqueue.events.async_processor');
+
+ $message = new NullMessage();
+ $message->setProperty('event_name', 'test_async');
+ $message->setProperty('transformer_name', 'test_async');
+ $message->setBody(JSON::encode([
+ 'subject' => 'theSubject',
+ 'arguments' => ['fooArg' => 'fooVal'],
+ ]));
+
+ $this->assertEquals(Processor::ACK, $processor->process($message, new NullContext()));
+
+ /** @var TestAsyncListener $listener */
+ $listener = static::$container->get('test_async_listener');
+
+ $this->assertNotEmpty($listener->calls);
+
+ $this->assertInstanceOf(GenericEvent::class, $listener->calls[0][0]);
+ $this->assertEquals('theSubject', $listener->calls[0][0]->getSubject());
+ $this->assertEquals(['fooArg' => 'fooVal'], $listener->calls[0][0]->getArguments());
+ $this->assertEquals('test_async', $listener->calls[0][1]);
+
+ $this->assertSame(
+ static::$container->get('enqueue.events.event_dispatcher'),
+ $listener->calls[0][2]
+ );
+ }
+
+ public function testShouldCallRealSubscriber()
+ {
+ /** @var AsyncProcessor $processor */
+ $processor = static::$container->get('test.enqueue.events.async_processor');
+
+ $message = new NullMessage();
+ $message->setProperty('event_name', 'test_async_subscriber');
+ $message->setProperty('transformer_name', 'test_async');
+ $message->setBody(JSON::encode([
+ 'subject' => 'theSubject',
+ 'arguments' => ['fooArg' => 'fooVal'],
+ ]));
+
+ $this->assertEquals(Processor::ACK, $processor->process($message, new NullContext()));
+
+ /** @var TestAsyncSubscriber $subscriber */
+ $subscriber = static::$container->get('test_async_subscriber');
+
+ $this->assertNotEmpty($subscriber->calls);
+
+ $this->assertInstanceOf(GenericEvent::class, $subscriber->calls[0][0]);
+ $this->assertEquals('theSubject', $subscriber->calls[0][0]->getSubject());
+ $this->assertEquals(['fooArg' => 'fooVal'], $subscriber->calls[0][0]->getArguments());
+ $this->assertEquals('test_async_subscriber', $subscriber->calls[0][1]);
+
+ $this->assertSame(
+ static::$container->get('enqueue.events.event_dispatcher'),
+ $subscriber->calls[0][2]
+ );
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Events/AsyncSubscriberTest.php b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncSubscriberTest.php
new file mode 100644
index 000000000..4b145524a
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/Events/AsyncSubscriberTest.php
@@ -0,0 +1,111 @@
+get('enqueue.events.async_listener');
+
+ $asyncListener->resetSyncMode();
+ static::$container->get('test_async_subscriber')->calls = [];
+ static::$container->get('test_async_listener')->calls = [];
+ }
+
+ public function testShouldNotCallRealSubscriberIfMarkedAsAsync()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $this->dispatch($dispatcher, new GenericEvent('aSubject'), 'test_async_subscriber');
+
+ /** @var TestAsyncSubscriber $listener */
+ $listener = static::$container->get('test_async_subscriber');
+
+ $this->assertEmpty($listener->calls);
+ }
+
+ public function testShouldSendMessageToExpectedTopicInsteadOfCallingRealSubscriber()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $event = new GenericEvent('theSubject', ['fooArg' => 'fooVal']);
+
+ $this->dispatch($dispatcher, $event, 'test_async_subscriber');
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(1, $traces);
+
+ $this->assertEquals(Commands::DISPATCH_ASYNC_EVENTS, $traces[0]['command']);
+ $this->assertEquals('{"subject":"theSubject","arguments":{"fooArg":"fooVal"}}', $traces[0]['body']);
+ }
+
+ public function testShouldSendMessageForEveryDispatchCall()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async_subscriber');
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async_subscriber');
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async_subscriber');
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(3, $traces);
+ }
+
+ public function testShouldSendMessageIfDispatchedFromInsideListener()
+ {
+ /** @var EventDispatcherInterface $dispatcher */
+ $dispatcher = static::$container->get('event_dispatcher');
+
+ $eventName = 'anEvent'.uniqid();
+ $dispatcher->addListener($eventName, function ($event, $eventName, EventDispatcherInterface $dispatcher) {
+ $this->dispatch($dispatcher, new GenericEvent('theSubject', ['fooArg' => 'fooVal']), 'test_async_subscriber');
+ });
+
+ $this->dispatch($dispatcher, new GenericEvent(), $eventName);
+
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.producer');
+
+ $traces = $producer->getCommandTraces(Commands::DISPATCH_ASYNC_EVENTS);
+
+ $this->assertCount(1, $traces);
+ }
+
+ private function dispatch(EventDispatcherInterface $dispatcher, $event, $eventName): void
+ {
+ if (!class_exists(Event::class)) {
+ // Symfony 5
+ $dispatcher->dispatch($event, $eventName);
+ } else {
+ // Symfony < 5
+ $dispatcher->dispatch($eventName, $event);
+ }
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Job/CalculateRootJobStatusProcessorTest.php b/pkg/enqueue-bundle/Tests/Functional/Job/CalculateRootJobStatusProcessorTest.php
index cd8d92435..01346ad8c 100644
--- a/pkg/enqueue-bundle/Tests/Functional/Job/CalculateRootJobStatusProcessorTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/Job/CalculateRootJobStatusProcessorTest.php
@@ -12,7 +12,7 @@ class CalculateRootJobStatusProcessorTest extends WebTestCase
{
public function testCouldBeConstructedByContainer()
{
- $instance = $this->container->get('enqueue.job.calculate_root_job_status_processor');
+ $instance = static::$container->get(CalculateRootJobStatusProcessor::class);
$this->assertInstanceOf(CalculateRootJobStatusProcessor::class, $instance);
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Job/DependentJobServiceTest.php b/pkg/enqueue-bundle/Tests/Functional/Job/DependentJobServiceTest.php
index ca0fdccd4..1aec06410 100644
--- a/pkg/enqueue-bundle/Tests/Functional/Job/DependentJobServiceTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/Job/DependentJobServiceTest.php
@@ -12,7 +12,7 @@ class DependentJobServiceTest extends WebTestCase
{
public function testCouldBeConstructedByContainer()
{
- $instance = $this->container->get('enqueue.job.dependent_job_service');
+ $instance = static::$container->get(DependentJobService::class);
$this->assertInstanceOf(DependentJobService::class, $instance);
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Job/JobRunnerTest.php b/pkg/enqueue-bundle/Tests/Functional/Job/JobRunnerTest.php
index e63fc4f20..4aa647f77 100644
--- a/pkg/enqueue-bundle/Tests/Functional/Job/JobRunnerTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/Job/JobRunnerTest.php
@@ -12,7 +12,7 @@ class JobRunnerTest extends WebTestCase
{
public function testCouldBeConstructedByContainer()
{
- $instance = $this->container->get('enqueue.job.runner');
+ $instance = static::$container->get(JobRunner::class);
$this->assertInstanceOf(JobRunner::class, $instance);
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/Job/JobStorageTest.php b/pkg/enqueue-bundle/Tests/Functional/Job/JobStorageTest.php
index 93cff10a2..650326ad8 100644
--- a/pkg/enqueue-bundle/Tests/Functional/Job/JobStorageTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/Job/JobStorageTest.php
@@ -3,7 +3,7 @@
namespace Enqueue\Bundle\Tests\Functional\Job;
use Enqueue\Bundle\Tests\Functional\WebTestCase;
-use Enqueue\JobQueue\JobStorage;
+use Enqueue\JobQueue\Doctrine\JobStorage;
/**
* @group functional
@@ -12,7 +12,7 @@ class JobStorageTest extends WebTestCase
{
public function testCouldGetJobStorageAsServiceFromContainer()
{
- $instance = $this->container->get('enqueue.job.storage');
+ $instance = static::$container->get(JobStorage::class);
$this->assertInstanceOf(JobStorage::class, $instance);
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/LazyProducerTest.php b/pkg/enqueue-bundle/Tests/Functional/LazyProducerTest.php
new file mode 100644
index 000000000..18375aef3
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/LazyProducerTest.php
@@ -0,0 +1,64 @@
+customSetUp([
+ 'default' => [
+ 'transport' => [
+ 'dsn' => 'invalidDSN',
+ ],
+ ],
+ ]);
+
+ /** @var LazyProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.lazy_producer');
+ $this->assertInstanceOf(LazyProducer::class, $producer);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The DSN is invalid.');
+ $producer->sendEvent('foo', 'foo');
+ }
+
+ public static function getKernelClass(): string
+ {
+ include_once __DIR__.'/App/CustomAppKernel.php';
+
+ return CustomAppKernel::class;
+ }
+
+ protected function customSetUp(array $enqueueConfig)
+ {
+ static::$class = null;
+
+ static::$client = static::createClient(['enqueue_config' => $enqueueConfig]);
+ static::$client->getKernel()->boot();
+ static::$kernel = static::$client->getKernel();
+ static::$container = static::$kernel->getContainer();
+ }
+
+ protected static function createKernel(array $options = []): CustomAppKernel
+ {
+ /** @var CustomAppKernel $kernel */
+ $kernel = parent::createKernel($options);
+
+ $kernel->setEnqueueConfig(isset($options['enqueue_config']) ? $options['enqueue_config'] : []);
+
+ return $kernel;
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/QueueConsumerTest.php b/pkg/enqueue-bundle/Tests/Functional/QueueConsumerTest.php
index 8ad6c46ac..e05d1532d 100644
--- a/pkg/enqueue-bundle/Tests/Functional/QueueConsumerTest.php
+++ b/pkg/enqueue-bundle/Tests/Functional/QueueConsumerTest.php
@@ -11,8 +11,10 @@ class QueueConsumerTest extends WebTestCase
{
public function testCouldBeGetFromContainerAsService()
{
- $queueConsumer = $this->container->get('enqueue.consumption.queue_consumer');
+ $queueConsumer = static::$container->get('test_enqueue.client.default.queue_consumer');
+ $this->assertInstanceOf(QueueConsumer::class, $queueConsumer);
+ $queueConsumer = static::$container->get('test_enqueue.transport.default.queue_consumer');
$this->assertInstanceOf(QueueConsumer::class, $queueConsumer);
}
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/QueueRegistryTest.php b/pkg/enqueue-bundle/Tests/Functional/QueueRegistryTest.php
deleted file mode 100644
index fdd35549c..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/QueueRegistryTest.php
+++ /dev/null
@@ -1,18 +0,0 @@
-container->get('enqueue.client.meta.queue_meta_registry');
-
- $this->assertInstanceOf(QueueMetaRegistry::class, $connection);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/QueuesCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/QueuesCommandTest.php
deleted file mode 100644
index 76ff88a83..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/QueuesCommandTest.php
+++ /dev/null
@@ -1,33 +0,0 @@
-container->get('enqueue.client.meta.queues_command');
-
- $this->assertInstanceOf(QueuesCommand::class, $command);
- }
-
- public function testShouldDisplayRegisteredDestionations()
- {
- $command = $this->container->get('enqueue.client.meta.queues_command');
-
- $tester = new CommandTester($command);
- $tester->execute([]);
-
- $display = $tester->getDisplay();
-
- $this->assertContains(' default ', $display);
- $this->assertContains('enqueue.app.default', $display);
- $this->assertContains('enqueue.client.router_processor', $display);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/RoutesCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/RoutesCommandTest.php
new file mode 100644
index 000000000..66833b1ce
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/RoutesCommandTest.php
@@ -0,0 +1,51 @@
+get('test.enqueue.client.routes_command');
+
+ $this->assertInstanceOf(RoutesCommand::class, $command);
+ }
+
+ public function testShouldDisplayRegisteredTopics()
+ {
+ /** @var RoutesCommand $command */
+ $command = static::$container->get('test.enqueue.client.routes_command');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+
+ $this->assertSame(0, $tester->getStatusCode());
+ $this->assertStringContainsString('| topic', $tester->getDisplay());
+ $this->assertStringContainsString('| theTopic', $tester->getDisplay());
+ $this->assertStringContainsString('| default (prefixed)', $tester->getDisplay());
+ $this->assertStringContainsString('| test_topic_subscriber_processor', $tester->getDisplay());
+ $this->assertStringContainsString('| (hidden)', $tester->getDisplay());
+ }
+
+ public function testShouldDisplayCommands()
+ {
+ /** @var RoutesCommand $command */
+ $command = static::$container->get('test.enqueue.client.routes_command');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+
+ $this->assertSame(0, $tester->getStatusCode());
+ $this->assertStringContainsString('| command', $tester->getDisplay());
+ $this->assertStringContainsString('| theCommand', $tester->getDisplay());
+ $this->assertStringContainsString('| test_command_subscriber_processor', $tester->getDisplay());
+ $this->assertStringContainsString('| default (prefixed)', $tester->getDisplay());
+ $this->assertStringContainsString('| (hidden)', $tester->getDisplay());
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/RpcClientTest.php b/pkg/enqueue-bundle/Tests/Functional/RpcClientTest.php
new file mode 100644
index 000000000..3d99bcc72
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/RpcClientTest.php
@@ -0,0 +1,18 @@
+get('test_enqueue.transport.default.rpc_client');
+
+ $this->assertInstanceOf(RpcClient::class, $rpcClient);
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/TestCommandProcessor.php b/pkg/enqueue-bundle/Tests/Functional/TestCommandProcessor.php
new file mode 100644
index 000000000..dfc2bb864
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/TestCommandProcessor.php
@@ -0,0 +1,30 @@
+message = $message;
+
+ return self::ACK;
+ }
+
+ public static function getSubscribedCommand()
+ {
+ return self::COMMAND;
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/TestProcessor.php b/pkg/enqueue-bundle/Tests/Functional/TestProcessor.php
index 0f977dd80..9b54bdf2d 100644
--- a/pkg/enqueue-bundle/Tests/Functional/TestProcessor.php
+++ b/pkg/enqueue-bundle/Tests/Functional/TestProcessor.php
@@ -3,13 +3,13 @@
namespace Enqueue\Bundle\Tests\Functional;
use Enqueue\Client\TopicSubscriberInterface;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Message;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Context;
+use Interop\Queue\Message;
+use Interop\Queue\Processor;
class TestProcessor implements Processor, TopicSubscriberInterface
{
- const TOPIC = 'test-topic';
+ public const TOPIC = 'test-topic';
/**
* @var Message
diff --git a/pkg/enqueue-bundle/Tests/Functional/TopicRegistryTest.php b/pkg/enqueue-bundle/Tests/Functional/TopicRegistryTest.php
deleted file mode 100644
index 0db559da0..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/TopicRegistryTest.php
+++ /dev/null
@@ -1,18 +0,0 @@
-container->get('enqueue.client.meta.topic_meta_registry');
-
- $this->assertInstanceOf(TopicMetaRegistry::class, $connection);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/TopicsCommandTest.php b/pkg/enqueue-bundle/Tests/Functional/TopicsCommandTest.php
deleted file mode 100644
index bb47f7119..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/TopicsCommandTest.php
+++ /dev/null
@@ -1,32 +0,0 @@
-container->get('enqueue.client.meta.topics_command');
-
- $this->assertInstanceOf(TopicsCommand::class, $command);
- }
-
- public function testShouldDisplayRegisteredTopics()
- {
- $command = $this->container->get('enqueue.client.meta.topics_command');
-
- $tester = new CommandTester($command);
- $tester->execute([]);
-
- $display = $tester->getDisplay();
-
- $this->assertContains('__router__', $display);
- $this->assertContains('enqueue.client.router_processor', $display);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/UseCasesTest.php b/pkg/enqueue-bundle/Tests/Functional/UseCasesTest.php
new file mode 100644
index 000000000..7417412bd
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Functional/UseCasesTest.php
@@ -0,0 +1,414 @@
+getContext()) {
+ $this->getContext()->close();
+ }
+
+ parent::tearDown();
+ }
+
+ public function provideEnqueueConfigs()
+ {
+ $baseDir = realpath(__DIR__.'/../../../../');
+
+ // guard
+ $this->assertNotEmpty($baseDir);
+
+ $certDir = $baseDir.'/var/rabbitmq_certificates';
+ $this->assertDirectoryExists($certDir);
+
+ yield 'amqp_dsn' => [[
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]];
+
+ yield 'amqps_dsn' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('AMQPS_DSN'),
+ 'ssl_verify' => false,
+ 'ssl_cacert' => $certDir.'/cacert.pem',
+ 'ssl_cert' => $certDir.'/cert.pem',
+ 'ssl_key' => $certDir.'/key.pem',
+ ],
+ ],
+ ]];
+
+ yield 'dsn_as_env' => [[
+ 'default' => [
+ 'transport' => '%env(AMQP_DSN)%',
+ ],
+ ]];
+
+ yield 'dbal_dsn' => [[
+ 'default' => [
+ 'transport' => getenv('DOCTRINE_DSN'),
+ ],
+ ]];
+
+ yield 'rabbitmq_stomp' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('RABITMQ_STOMP_DSN'),
+ 'lazy' => false,
+ 'management_plugin_installed' => true,
+ ],
+ ],
+ ]];
+
+ yield 'predis_dsn' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('PREDIS_DSN'),
+ 'lazy' => false,
+ ],
+ ],
+ ]];
+
+ yield 'phpredis_dsn' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('PHPREDIS_DSN'),
+ 'lazy' => false,
+ ],
+ ],
+ ]];
+
+ yield 'fs_dsn' => [[
+ 'default' => [
+ 'transport' => 'file://'.sys_get_temp_dir(),
+ ],
+ ]];
+
+ yield 'sqs' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('SQS_DSN'),
+ ],
+ ],
+ ]];
+
+ yield 'sqs_client' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => 'sqs:',
+ 'service' => 'test.sqs_client',
+ 'factory_service' => 'test.sqs_custom_connection_factory_factory',
+ ],
+ ],
+ ]];
+
+ yield 'mongodb_dsn' => [[
+ 'default' => [
+ 'transport' => getenv('MONGO_DSN'),
+ ],
+ ]];
+
+ yield 'doctrine' => [[
+ 'default' => [
+ 'transport' => 'doctrine://custom',
+ ],
+ ]];
+
+ yield 'snsqs' => [[
+ 'default' => [
+ 'transport' => [
+ 'dsn' => getenv('SNSQS_DSN'),
+ ],
+ ],
+ ]];
+
+ //
+ // yield 'gps' => [[
+ // 'transport' => [
+ // 'dsn' => getenv('GPS_DSN'),
+ // ],
+ // ]];
+ }
+
+ /**
+ * @dataProvider provideEnqueueConfigs
+ */
+ public function testProducerSendsEventMessage(array $enqueueConfig)
+ {
+ $this->customSetUp($enqueueConfig);
+
+ $expectedBody = __METHOD__.time();
+
+ $this->getMessageProducer()->sendEvent(TestProcessor::TOPIC, $expectedBody);
+
+ $consumer = $this->getContext()->createConsumer($this->getTestQueue());
+
+ $message = $consumer->receive(self::RECEIVE_TIMEOUT);
+ $this->assertInstanceOf(Message::class, $message);
+ $consumer->acknowledge($message);
+
+ $this->assertSame($expectedBody, $message->getBody());
+ }
+
+ /**
+ * @dataProvider provideEnqueueConfigs
+ */
+ public function testProducerSendsCommandMessage(array $enqueueConfig)
+ {
+ $this->customSetUp($enqueueConfig);
+
+ $expectedBody = __METHOD__.time();
+
+ $this->getMessageProducer()->sendCommand(TestCommandProcessor::COMMAND, $expectedBody);
+
+ $consumer = $this->getContext()->createConsumer($this->getTestQueue());
+
+ $message = $consumer->receive(self::RECEIVE_TIMEOUT);
+ $this->assertInstanceOf(Message::class, $message);
+ $consumer->acknowledge($message);
+
+ $this->assertInstanceOf(Message::class, $message);
+ $this->assertSame($expectedBody, $message->getBody());
+ }
+
+ public function testProducerSendsEventMessageViaProduceCommand()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]);
+
+ $expectedBody = __METHOD__.time();
+
+ $command = static::$container->get('test_enqueue.client.produce_command');
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => $expectedBody,
+ '--topic' => TestProcessor::TOPIC,
+ '--client' => 'default',
+ ]);
+
+ $consumer = $this->getContext()->createConsumer($this->getTestQueue());
+
+ $message = $consumer->receive(self::RECEIVE_TIMEOUT);
+ $this->assertInstanceOf(Message::class, $message);
+ $consumer->acknowledge($message);
+
+ $this->assertSame($expectedBody, $message->getBody());
+ }
+
+ public function testProducerSendsCommandMessageViaProduceCommand()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]);
+
+ $expectedBody = __METHOD__.time();
+
+ $command = static::$container->get('test_enqueue.client.produce_command');
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => $expectedBody,
+ '--command' => TestCommandProcessor::COMMAND,
+ '--client' => 'default',
+ ]);
+
+ $consumer = $this->getContext()->createConsumer($this->getTestQueue());
+
+ $message = $consumer->receive(self::RECEIVE_TIMEOUT);
+ $this->assertInstanceOf(Message::class, $message);
+ $consumer->acknowledge($message);
+
+ $this->assertInstanceOf(Message::class, $message);
+ $this->assertSame($expectedBody, $message->getBody());
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => 'file://'.sys_get_temp_dir(),
+ ],
+ ]);
+
+ $command = static::$container->get('test_enqueue.client.setup_broker_command');
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+
+ $this->assertSame("Broker set up\n", $tester->getDisplay());
+ }
+
+ public function testClientConsumeCommandMessagesFromExplicitlySetQueue()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]);
+
+ $command = static::$container->get('test_enqueue.client.consume_command');
+ $processor = static::$container->get('test.message.command_processor');
+
+ $expectedBody = __METHOD__.time();
+
+ $this->getMessageProducer()->sendCommand(TestCommandProcessor::COMMAND, $expectedBody);
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--message-limit' => 2,
+ '--receive-timeout' => 100,
+ '--time-limit' => 'now + 2 seconds',
+ 'client-queue-names' => ['test'],
+ ]);
+
+ $this->assertInstanceOf(Message::class, $processor->message);
+ $this->assertEquals($expectedBody, $processor->message->getBody());
+ }
+
+ public function testClientConsumeMessagesFromExplicitlySetQueue()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]);
+
+ $expectedBody = __METHOD__.time();
+
+ $command = static::$container->get('test_enqueue.client.consume_command');
+ $processor = static::$container->get('test.message.processor');
+
+ $this->getMessageProducer()->sendEvent(TestProcessor::TOPIC, $expectedBody);
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--message-limit' => 2,
+ '--receive-timeout' => 100,
+ '--time-limit' => 'now + 2 seconds',
+ 'client-queue-names' => ['test'],
+ ]);
+
+ $this->assertInstanceOf(Message::class, $processor->message);
+ $this->assertEquals($expectedBody, $processor->message->getBody());
+ }
+
+ public function testTransportConsumeCommandShouldConsumeOneMessage()
+ {
+ $this->customSetUp([
+ 'default' => [
+ 'transport' => getenv('AMQP_DSN'),
+ ],
+ ]);
+
+ if ($this->getTestQueue() instanceof StompDestination) {
+ $this->markTestSkipped('The test fails with the exception Stomp\Exception\ErrorFrameException: Error "precondition_failed". '.
+ 'It happens because of the destination options are different from the one used while creating the dest. Nothing to do about it'
+ );
+ }
+
+ $expectedBody = __METHOD__.time();
+
+ $command = static::$container->get('test_enqueue.transport.consume_command');
+ $processor = static::$container->get('test.message.processor');
+
+ $this->getMessageProducer()->sendEvent(TestProcessor::TOPIC, $expectedBody);
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--message-limit' => 1,
+ '--time-limit' => '+2sec',
+ '--receive-timeout' => 1000,
+ 'processor' => 'test.message.processor',
+ 'queues' => [$this->getTestQueue()->getQueueName()],
+ ]);
+
+ $this->assertInstanceOf(Message::class, $processor->message);
+ $this->assertEquals($expectedBody, $processor->message->getBody());
+ }
+
+ public static function getKernelClass(): string
+ {
+ include_once __DIR__.'/App/CustomAppKernel.php';
+
+ return CustomAppKernel::class;
+ }
+
+ protected function customSetUp(array $enqueueConfig)
+ {
+ static::$class = null;
+
+ static::$client = static::createClient(['enqueue_config' => $enqueueConfig]);
+ static::$client->getKernel()->boot();
+ static::$kernel = static::$client->getKernel();
+ static::$container = static::$kernel->getContainer();
+
+ /** @var DriverInterface $driver */
+ $driver = static::$container->get('test_enqueue.client.default.driver');
+ $context = $this->getContext();
+
+ $driver->setupBroker();
+
+ try {
+ $context->purgeQueue($this->getTestQueue());
+ } catch (PurgeQueueNotSupportedException $e) {
+ }
+ }
+
+ /**
+ * @return Queue
+ */
+ protected function getTestQueue()
+ {
+ /** @var DriverInterface $driver */
+ $driver = static::$container->get('test_enqueue.client.default.driver');
+
+ return $driver->createQueue('test');
+ }
+
+ protected static function createKernel(array $options = []): CustomAppKernel
+ {
+ /** @var CustomAppKernel $kernel */
+ $kernel = parent::createKernel($options);
+
+ $kernel->setEnqueueConfig(isset($options['enqueue_config']) ? $options['enqueue_config'] : []);
+
+ return $kernel;
+ }
+
+ private function getMessageProducer(): ProducerInterface
+ {
+ return static::$container->get('test_enqueue.client.default.producer');
+ }
+
+ private function getContext(): Context
+ {
+ return static::$container->get('test_enqueue.transport.default.context');
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Functional/WebTestCase.php b/pkg/enqueue-bundle/Tests/Functional/WebTestCase.php
index 01596fd67..6a348a9f3 100644
--- a/pkg/enqueue-bundle/Tests/Functional/WebTestCase.php
+++ b/pkg/enqueue-bundle/Tests/Functional/WebTestCase.php
@@ -3,6 +3,7 @@
namespace Enqueue\Bundle\Tests\Functional;
use Enqueue\Bundle\Tests\Functional\App\AppKernel;
+use Enqueue\Client\TraceableProducer;
use Symfony\Bundle\FrameworkBundle\Client;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase as BaseWebTestCase;
use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -12,29 +13,35 @@ abstract class WebTestCase extends BaseWebTestCase
/**
* @var Client
*/
- protected $client;
+ protected static $client;
/**
* @var ContainerInterface
*/
- protected $container;
+ protected static $container;
- protected function setUp()
+ protected function setUp(): void
{
parent::setUp();
static::$class = null;
+ static::$client = static::createClient();
+ static::$container = static::$kernel->getContainer();
- $this->client = static::createClient();
- $this->container = static::$kernel->getContainer();
+ /** @var TraceableProducer $producer */
+ $producer = static::$container->get('test_enqueue.client.default.traceable_producer');
+ $producer->clearTraces();
}
- /**
- * @return string
- */
- public static function getKernelClass()
+ protected function tearDown(): void
+ {
+ static::ensureKernelShutdown();
+ static::$client = null;
+ }
+
+ public static function getKernelClass(): string
{
- include_once __DIR__.'/app/AppKernel.php';
+ include_once __DIR__.'/App/AppKernel.php';
return AppKernel::class;
}
diff --git a/pkg/enqueue-bundle/Tests/Functional/app/AmqpAppKernel.php b/pkg/enqueue-bundle/Tests/Functional/app/AmqpAppKernel.php
deleted file mode 100644
index 803a5c4d4..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/app/AmqpAppKernel.php
+++ /dev/null
@@ -1,53 +0,0 @@
-load(__DIR__.'/config/amqp-config.yml');
- }
-
- protected function getContainerClass()
- {
- return parent::getContainerClass().'BundleAmqp';
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/app/AppKernel.php b/pkg/enqueue-bundle/Tests/Functional/app/AppKernel.php
deleted file mode 100644
index e2ed065a3..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/app/AppKernel.php
+++ /dev/null
@@ -1,53 +0,0 @@
-load(__DIR__.'/config/config.yml');
- }
-
- protected function getContainerClass()
- {
- return parent::getContainerClass().'BundleDefault';
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Functional/app/config/amqp-config.yml b/pkg/enqueue-bundle/Tests/Functional/app/config/amqp-config.yml
deleted file mode 100644
index 312f81f52..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/app/config/amqp-config.yml
+++ /dev/null
@@ -1,43 +0,0 @@
-parameters:
- locale: 'en'
- secret: 'ThisTokenIsNotSoSecretChangeIt'
-
-
-framework:
- #esi: ~
- #translator: { fallback: "%locale%" }
- test: ~
- assets: false
- templating: false
- session:
- storage_id: session.storage.mock_file
- secret: '%secret%'
- router: { resource: '%kernel.root_dir%/config/routing.yml' }
- default_locale: '%locale%'
-
-monolog:
- handlers:
- main:
- type: 'null'
- level: 'error'
-
-enqueue:
- transport:
- default: 'amqp'
- amqp:
- host: '%rabbitmq.host%'
- port: '%rabbitmq.amqp.port%'
- login: '%rabbitmq.user%'
- password: '%rabbitmq.password%'
- vhost: '%rabbitmq.vhost%'
- client:
- prefix: 'amqp'
- router_topic: 'test'
- router_queue: 'test'
- default_processor_queue: 'test'
-
-services:
- test.message.processor:
- class: 'Enqueue\Bundle\Tests\Functional\TestProcessor'
- tags:
- - { name: 'enqueue.client.processor' }
diff --git a/pkg/enqueue-bundle/Tests/Functional/app/config/config.yml b/pkg/enqueue-bundle/Tests/Functional/app/config/config.yml
deleted file mode 100644
index b76ee45b2..000000000
--- a/pkg/enqueue-bundle/Tests/Functional/app/config/config.yml
+++ /dev/null
@@ -1,39 +0,0 @@
-parameters:
- locale: 'en'
- secret: 'ThisTokenIsNotSoSecretChangeIt'
-
-
-framework:
- #esi: ~
- #translator: { fallback: "%locale%" }
- test: ~
- assets: false
- templating: false
- session:
- storage_id: session.storage.mock_file
- secret: '%secret%'
- router: { resource: '%kernel.root_dir%/config/routing.yml' }
- default_locale: '%locale%'
-
-monolog:
- handlers:
- main:
- type: 'null'
- level: 'error'
-
-doctrine:
- dbal:
- driver: "%db.driver%"
- host: "%db.host%"
- port: "%db.port%"
- dbname: "%db.name%"
- user: "%db.user%"
- password: "%db.password%"
- charset: UTF8
-
-enqueue:
- transport:
- default: 'null'
- 'null': ~
- client: ~
- job: true
diff --git a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClearIdentityMapExtensionTest.php b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClearIdentityMapExtensionTest.php
index 7bf9eaa36..7c5c2dd5d 100644
--- a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClearIdentityMapExtensionTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClearIdentityMapExtensionTest.php
@@ -2,22 +2,20 @@
namespace Enqueue\Bundle\Tests\Unit\Consumption\Extension;
-use Doctrine\Common\Persistence\ObjectManager;
+use Doctrine\Persistence\ManagerRegistry;
+use Doctrine\Persistence\ObjectManager;
use Enqueue\Bundle\Consumption\Extension\DoctrineClearIdentityMapExtension;
-use Enqueue\Consumption\Context;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Processor;
+use Enqueue\Consumption\Context\MessageReceived;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context as InteropContext;
+use Interop\Queue\Message;
+use Interop\Queue\Processor;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
-use Symfony\Bridge\Doctrine\RegistryInterface;
-class DoctrineClearIdentityMapExtensionTest extends \PHPUnit_Framework_TestCase
+class DoctrineClearIdentityMapExtensionTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
- {
- new DoctrineClearIdentityMapExtension($this->createRegistryMock());
- }
-
public function testShouldClearIdentityMap()
{
$manager = $this->createManagerMock();
@@ -30,10 +28,10 @@ public function testShouldClearIdentityMap()
$registry
->expects($this->once())
->method('getManagers')
- ->will($this->returnValue(['manager-name' => $manager]))
+ ->willReturn(['manager-name' => $manager])
;
- $context = $this->createPsrContext();
+ $context = $this->createContext();
$context->getLogger()
->expects($this->once())
->method('debug')
@@ -41,34 +39,33 @@ public function testShouldClearIdentityMap()
;
$extension = new DoctrineClearIdentityMapExtension($registry);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($context);
}
- /**
- * @return Context
- */
- protected function createPsrContext()
+ protected function createContext(): MessageReceived
{
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($this->createMock(LoggerInterface::class));
- $context->setPsrConsumer($this->createMock(Consumer::class));
- $context->setPsrProcessor($this->createMock(Processor::class));
-
- return $context;
+ return new MessageReceived(
+ $this->createMock(InteropContext::class),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ $this->createMock(Processor::class),
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|RegistryInterface
+ * @return MockObject|ManagerRegistry
*/
- protected function createRegistryMock()
+ protected function createRegistryMock(): ManagerRegistry
{
- return $this->createMock(RegistryInterface::class);
+ return $this->createMock(ManagerRegistry::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|ObjectManager
+ * @return MockObject|ObjectManager
*/
- protected function createManagerMock()
+ protected function createManagerMock(): ObjectManager
{
return $this->createMock(ObjectManager::class);
}
diff --git a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClosedEntityManagerExtensionTest.php b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClosedEntityManagerExtensionTest.php
new file mode 100644
index 000000000..8e7120325
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrineClosedEntityManagerExtensionTest.php
@@ -0,0 +1,223 @@
+createManagerMock(true);
+
+ $registry = $this->createRegistryMock([
+ 'manager' => $manager,
+ ]);
+
+ $message = new PreConsume(
+ $this->createMock(InteropContext::class),
+ $this->createMock(SubscriptionConsumer::class),
+ $this->createMock(LoggerInterface::class),
+ 1,
+ 2,
+ 3
+ );
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPreConsume($message);
+
+ self::assertFalse($message->isExecutionInterrupted());
+ }
+
+ public function testOnPreConsumeShouldInterruptExecutionIfAManagerIsClosed()
+ {
+ $manager1 = $this->createManagerMock(true);
+ $manager2 = $this->createManagerMock(false);
+
+ $registry = $this->createRegistryMock([
+ 'manager1' => $manager1,
+ 'manager2' => $manager2,
+ ]);
+
+ $message = new PreConsume(
+ $this->createMock(InteropContext::class),
+ $this->createMock(SubscriptionConsumer::class),
+ $this->createMock(LoggerInterface::class),
+ 1,
+ 2,
+ 3
+ );
+ $message->getLogger()
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[DoctrineClosedEntityManagerExtension] Interrupt execution as entity manager "manager2" has been closed')
+ ;
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPreConsume($message);
+
+ self::assertTrue($message->isExecutionInterrupted());
+ }
+
+ public function testOnPostConsumeShouldNotInterruptExecution()
+ {
+ $manager = $this->createManagerMock(true);
+
+ $registry = $this->createRegistryMock([
+ 'manager' => $manager,
+ ]);
+
+ $message = new PostConsume(
+ $this->createMock(InteropContext::class),
+ $this->createMock(SubscriptionConsumer::class),
+ 1,
+ 1,
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPostConsume($message);
+
+ self::assertFalse($message->isExecutionInterrupted());
+ }
+
+ public function testOnPostConsumeShouldInterruptExecutionIfAManagerIsClosed()
+ {
+ $manager1 = $this->createManagerMock(true);
+ $manager2 = $this->createManagerMock(false);
+
+ $registry = $this->createRegistryMock([
+ 'manager1' => $manager1,
+ 'manager2' => $manager2,
+ ]);
+
+ $message = new PostConsume(
+ $this->createMock(InteropContext::class),
+ $this->createMock(SubscriptionConsumer::class),
+ 1,
+ 1,
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
+ $message->getLogger()
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[DoctrineClosedEntityManagerExtension] Interrupt execution as entity manager "manager2" has been closed')
+ ;
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPostConsume($message);
+
+ self::assertTrue($message->isExecutionInterrupted());
+ }
+
+ public function testOnPostReceivedShouldNotInterruptExecution()
+ {
+ $manager = $this->createManagerMock(true);
+
+ $registry = $this->createRegistryMock([
+ 'manager' => $manager,
+ ]);
+
+ $message = new PostMessageReceived(
+ $this->createMock(InteropContext::class),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPostMessageReceived($message);
+
+ self::assertFalse($message->isExecutionInterrupted());
+ }
+
+ public function testOnPostReceivedShouldInterruptExecutionIfAManagerIsClosed()
+ {
+ $manager1 = $this->createManagerMock(true);
+ $manager2 = $this->createManagerMock(false);
+
+ $registry = $this->createRegistryMock([
+ 'manager1' => $manager1,
+ 'manager2' => $manager2,
+ ]);
+
+ $message = new PostMessageReceived(
+ $this->createMock(InteropContext::class),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
+ $message->getLogger()
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[DoctrineClosedEntityManagerExtension] Interrupt execution as entity manager "manager2" has been closed')
+ ;
+
+ self::assertFalse($message->isExecutionInterrupted());
+
+ $extension = new DoctrineClosedEntityManagerExtension($registry);
+ $extension->onPostMessageReceived($message);
+
+ self::assertTrue($message->isExecutionInterrupted());
+ }
+
+ /**
+ * @return MockObject|ManagerRegistry
+ */
+ protected function createRegistryMock(array $managers): ManagerRegistry
+ {
+ $mock = $this->createMock(ManagerRegistry::class);
+
+ $mock
+ ->expects($this->once())
+ ->method('getManagers')
+ ->willReturn($managers)
+ ;
+
+ return $mock;
+ }
+
+ /**
+ * @return MockObject|EntityManagerInterface
+ */
+ protected function createManagerMock(bool $open): EntityManagerInterface
+ {
+ $mock = $this->createMock(EntityManagerInterface::class);
+
+ $mock
+ ->expects($this->once())
+ ->method('isOpen')
+ ->willReturn($open)
+ ;
+
+ return $mock;
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrinePingConnectionExtensionTest.php b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrinePingConnectionExtensionTest.php
index 8e4f40bdf..36df82e52 100644
--- a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrinePingConnectionExtensionTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/DoctrinePingConnectionExtensionTest.php
@@ -3,28 +3,39 @@
namespace Enqueue\Bundle\Tests\Unit\Consumption\Extension;
use Doctrine\DBAL\Connection;
+use Doctrine\DBAL\Platforms\AbstractPlatform;
+use Doctrine\Persistence\ManagerRegistry;
use Enqueue\Bundle\Consumption\Extension\DoctrinePingConnectionExtension;
-use Enqueue\Consumption\Context;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Processor;
-use Psr\Log\LoggerInterface;
-use Symfony\Bridge\Doctrine\RegistryInterface;
-
-class DoctrinePingConnectionExtensionTest extends \PHPUnit_Framework_TestCase
-{
- public function testCouldBeConstructedWithRequiredAttributes()
- {
- new DoctrinePingConnectionExtension($this->createRegistryMock());
- }
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Test\TestLogger;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context as InteropContext;
+use Interop\Queue\Message;
+use Interop\Queue\Processor;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+class DoctrinePingConnectionExtensionTest extends TestCase
+{
public function testShouldNotReconnectIfConnectionIsOK()
{
$connection = $this->createConnectionMock();
$connection
->expects($this->once())
- ->method('ping')
- ->will($this->returnValue(true))
+ ->method('isConnected')
+ ->willReturn(true)
+ ;
+
+ $abstractPlatform = $this->createMock(AbstractPlatform::class);
+ $abstractPlatform->expects($this->once())
+ ->method('getDummySelectSQL')
+ ->willReturn('dummy')
+ ;
+
+ $connection
+ ->expects($this->once())
+ ->method('getDatabasePlatform')
+ ->willReturn($abstractPlatform)
;
$connection
->expects($this->never())
@@ -35,21 +46,21 @@ public function testShouldNotReconnectIfConnectionIsOK()
->method('connect')
;
- $context = $this->createPsrContext();
- $context->getLogger()
- ->expects($this->never())
- ->method('debug')
- ;
+ $context = $this->createContext();
$registry = $this->createRegistryMock();
$registry
- ->expects($this->once())
+ ->expects(self::once())
->method('getConnections')
- ->will($this->returnValue([$connection]))
+ ->willReturn([$connection])
;
$extension = new DoctrinePingConnectionExtension($registry);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($context);
+
+ /** @var TestLogger $logger */
+ $logger = $context->getLogger();
+ self::assertFalse($logger->hasDebugRecords());
}
public function testShouldDoesReconnectIfConnectionFailed()
@@ -57,8 +68,14 @@ public function testShouldDoesReconnectIfConnectionFailed()
$connection = $this->createConnectionMock();
$connection
->expects($this->once())
- ->method('ping')
- ->will($this->returnValue(false))
+ ->method('isConnected')
+ ->willReturn(true)
+ ;
+
+ $connection
+ ->expects($this->once())
+ ->method('getDatabasePlatform')
+ ->willThrowException(new \Exception())
;
$connection
->expects($this->once())
@@ -69,54 +86,105 @@ public function testShouldDoesReconnectIfConnectionFailed()
->method('connect')
;
- $context = $this->createPsrContext();
- $context->getLogger()
- ->expects($this->at(0))
- ->method('debug')
- ->with('[DoctrinePingConnectionExtension] Connection is not active trying to reconnect.')
+ $context = $this->createContext();
+
+ $registry = $this->createRegistryMock();
+ $registry
+ ->expects($this->once())
+ ->method('getConnections')
+ ->willReturn([$connection])
+ ;
+
+ $extension = new DoctrinePingConnectionExtension($registry);
+ $extension->onMessageReceived($context);
+
+ /** @var TestLogger $logger */
+ $logger = $context->getLogger();
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[DoctrinePingConnectionExtension] Connection is not active trying to reconnect.'
+ )
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[DoctrinePingConnectionExtension] Connection is active now.'
+ )
+ );
+ }
+
+ public function testShouldSkipIfConnectionWasNotOpened()
+ {
+ $connection1 = $this->createConnectionMock();
+ $connection1
+ ->expects($this->once())
+ ->method('isConnected')
+ ->willReturn(false)
+ ;
+ $connection1
+ ->expects($this->never())
+ ->method('getDatabasePlatform')
+ ;
+
+ // 2nd connection was opened in the past
+ $connection2 = $this->createConnectionMock();
+ $connection2
+ ->expects($this->once())
+ ->method('isConnected')
+ ->willReturn(true)
;
- $context->getLogger()
- ->expects($this->at(1))
- ->method('debug')
- ->with('[DoctrinePingConnectionExtension] Connection is active now.')
+ $abstractPlatform = $this->createMock(AbstractPlatform::class);
+ $abstractPlatform->expects($this->once())
+ ->method('getDummySelectSQL')
+ ->willReturn('dummy')
+ ;
+
+ $connection2
+ ->expects($this->once())
+ ->method('getDatabasePlatform')
+ ->willReturn($abstractPlatform)
;
+ $context = $this->createContext();
+
$registry = $this->createRegistryMock();
$registry
->expects($this->once())
->method('getConnections')
- ->will($this->returnValue([$connection]))
+ ->willReturn([$connection1, $connection2])
;
$extension = new DoctrinePingConnectionExtension($registry);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($context);
+
+ /** @var TestLogger $logger */
+ $logger = $context->getLogger();
+ $this->assertFalse($logger->hasDebugRecords());
}
- /**
- * @return Context
- */
- protected function createPsrContext()
+ protected function createContext(): MessageReceived
{
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($this->createMock(LoggerInterface::class));
- $context->setPsrConsumer($this->createMock(Consumer::class));
- $context->setPsrProcessor($this->createMock(Processor::class));
-
- return $context;
+ return new MessageReceived(
+ $this->createMock(InteropContext::class),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ $this->createMock(Processor::class),
+ 1,
+ new TestLogger()
+ );
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|RegistryInterface
+ * @return MockObject|ManagerRegistry
*/
protected function createRegistryMock()
{
- return $this->createMock(RegistryInterface::class);
+ return $this->createMock(ManagerRegistry::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Connection
+ * @return MockObject|Connection
*/
- protected function createConnectionMock()
+ protected function createConnectionMock(): Connection
{
return $this->createMock(Connection::class);
}
diff --git a/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/ResetServicesExtensionTest.php b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/ResetServicesExtensionTest.php
new file mode 100644
index 000000000..63282a255
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/Unit/Consumption/Extension/ResetServicesExtensionTest.php
@@ -0,0 +1,57 @@
+createResetterMock();
+ $resetter
+ ->expects($this->once())
+ ->method('reset')
+ ;
+
+ $context = $this->createContext();
+ $context->getLogger()
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[ResetServicesExtension] Resetting services.')
+ ;
+
+ $extension = new ResetServicesExtension($resetter);
+ $extension->onPostMessageReceived($context);
+ }
+
+ protected function createContext(): PostMessageReceived
+ {
+ return new PostMessageReceived(
+ $this->createMock(InteropContext::class),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ $this->createMock(Processor::class),
+ 1,
+ $this->createMock(LoggerInterface::class)
+ );
+ }
+
+ /**
+ * @return MockObject|ManagerRegistry
+ */
+ protected function createResetterMock(): ServicesResetter
+ {
+ return $this->createMock(ServicesResetter::class);
+ }
+}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/AddTopicMetaPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/AddTopicMetaPassTest.php
deleted file mode 100644
index b7efc8551..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/AddTopicMetaPassTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
-assertClassImplements(CompilerPassInterface::class, AddTopicMetaPass::class);
- }
-
- public function testCouldBeConstructedWithoutAntArguments()
- {
- new AddTopicMetaPass([]);
- }
-
- public function testCouldBeConstructedByCreateFactoryMethod()
- {
- $pass = AddTopicMetaPass::create();
-
- $this->assertInstanceOf(AddTopicMetaPass::class, $pass);
- }
-
- public function testShouldReturnSelfOnAdd()
- {
- $pass = AddTopicMetaPass::create();
-
- $this->assertSame($pass, $pass->add('aTopic'));
- }
-
- public function testShouldDoNothingIfContainerDoesNotHaveRegistryService()
- {
- $container = new ContainerBuilder();
-
- $pass = AddTopicMetaPass::create()
- ->add('fooTopic')
- ->add('barTopic')
- ;
-
- $pass->process($container);
- }
-
- public function testShouldAddTopicsInRegistryKeepingPreviouslyAdded()
- {
- $container = new ContainerBuilder();
-
- $registry = new Definition(null, [[
- 'bazTopic' => [],
- ]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $registry);
-
- $pass = AddTopicMetaPass::create()
- ->add('fooTopic')
- ->add('barTopic')
- ;
- $pass->process($container);
-
- $expectedTopics = [
- 'bazTopic' => [],
- 'fooTopic' => [],
- 'barTopic' => [],
- ];
-
- $this->assertSame($expectedTopics, $registry->getArgument(0));
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildClientRoutingPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildClientRoutingPassTest.php
deleted file mode 100644
index 6c8472fb3..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildClientRoutingPassTest.php
+++ /dev/null
@@ -1,241 +0,0 @@
-createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- 'processorName' => 'processor',
- 'queueName' => 'queue',
- ]);
- $container->setDefinition('processor', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic' => [
- ['processor', 'queue'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testThrowIfProcessorClassNameCouldNotBeFound()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition('notExistingClass');
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'processor',
- ]);
- $container->setDefinition('processor', $processor);
-
- $router = new Definition();
- $router->setArguments([null, []]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The class "notExistingClass" could not be found.');
- $pass->process($container);
- }
-
- public function testShouldThrowExceptionIfTopicNameIsNotSet()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Topic name is not set on message processor tag but it is required.');
- $pass->process($container);
- }
-
- public function testShouldSetServiceIdAdProcessorIdIfIsNotSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- 'queueName' => 'queue',
- ]);
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic' => [
- ['processor-service-id', 'queue'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testShouldSetDefaultQueueIfNotSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- ]);
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic' => [
- ['processor-service-id', 'aDefaultQueueName'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testShouldBuildRouteFromSubscriberIfOnlyTopicNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(OnlyTopicNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic-subscriber-name' => [
- ['processor-service-id', 'aDefaultQueueName'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testShouldBuildRouteFromSubscriberIfProcessorNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(ProcessorNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic-subscriber-name' => [
- ['subscriber-processor-name', 'aDefaultQueueName'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testShouldBuildRouteFromSubscriberIfQueueNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(QueueNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments([null, null, null]);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
-
- $expectedRoutes = [
- 'topic-subscriber-name' => [
- ['processor-service-id', 'subscriber-queue-name'],
- ],
- ];
-
- $this->assertEquals($expectedRoutes, $router->getArgument(1));
- }
-
- public function testShouldThrowExceptionWhenTopicSubscriberConfigurationIsInvalid()
- {
- $this->setExpectedException(\LogicException::class, 'Topic subscriber configuration is invalid. "[12345]"');
-
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(InvalidTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $router = new Definition();
- $router->setArguments(['', '']);
- $container->setDefinition('enqueue.client.router_processor', $router);
-
- $pass = new BuildClientRoutingPass();
- $pass->process($container);
- }
-
- /**
- * @return ContainerBuilder
- */
- private function createContainerBuilder()
- {
- $container = new ContainerBuilder();
- $container->setParameter('enqueue.client.default_queue_name', 'aDefaultQueueName');
-
- return $container;
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildExtensionsPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildExtensionsPassTest.php
deleted file mode 100644
index 8e4aa54bc..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildExtensionsPassTest.php
+++ /dev/null
@@ -1,110 +0,0 @@
-assertClassImplements(CompilerPassInterface::class, BuildExtensionsPass::class);
- }
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new BuildExtensionsPass();
- }
-
- public function testShouldReplaceFirstArgumentOfExtensionsServiceConstructorWithTaggsExtensions()
- {
- $container = new ContainerBuilder();
-
- $extensions = new Definition();
- $extensions->addArgument([]);
- $container->setDefinition('enqueue.consumption.extensions', $extensions);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension');
- $container->setDefinition('foo_extension', $extension);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension');
- $container->setDefinition('bar_extension', $extension);
-
- $pass = new BuildExtensionsPass();
- $pass->process($container);
-
- $this->assertEquals(
- [new Reference('foo_extension'), new Reference('bar_extension')],
- $extensions->getArgument(0)
- );
- }
-
- public function testShouldOrderExtensionsByPriority()
- {
- $container = new ContainerBuilder();
-
- $extensions = new Definition();
- $extensions->addArgument([]);
- $container->setDefinition('enqueue.consumption.extensions', $extensions);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension', ['priority' => 6]);
- $container->setDefinition('foo_extension', $extension);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension', ['priority' => -5]);
- $container->setDefinition('bar_extension', $extension);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension', ['priority' => 2]);
- $container->setDefinition('baz_extension', $extension);
-
- $pass = new BuildExtensionsPass();
- $pass->process($container);
-
- $orderedExtensions = $extensions->getArgument(0);
-
- $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[0]);
- $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[1]);
- $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[2]);
- }
-
- public function testShouldAssumePriorityZeroIfPriorityIsNotSet()
- {
- $container = new ContainerBuilder();
-
- $extensions = new Definition();
- $extensions->addArgument([]);
- $container->setDefinition('enqueue.consumption.extensions', $extensions);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension');
- $container->setDefinition('foo_extension', $extension);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension', ['priority' => 1]);
- $container->setDefinition('bar_extension', $extension);
-
- $extension = new Definition();
- $extension->addTag('enqueue.consumption.extension', ['priority' => -1]);
- $container->setDefinition('baz_extension', $extension);
-
- $pass = new BuildExtensionsPass();
- $pass->process($container);
-
- $orderedExtensions = $extensions->getArgument(0);
-
- $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[0]);
- $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[1]);
- $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[2]);
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildProcessorRegistryPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildProcessorRegistryPassTest.php
deleted file mode 100644
index b210ff51d..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildProcessorRegistryPassTest.php
+++ /dev/null
@@ -1,180 +0,0 @@
-createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- 'processorName' => 'processor-name',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
- $pass->process($container);
-
- $expectedValue = [
- 'processor-name' => 'processor-id',
- ];
-
- $this->assertEquals($expectedValue, $processorRegistry->getArgument(0));
- }
-
- public function testThrowIfProcessorClassNameCouldNotBeFound()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition('notExistingClass');
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'processor',
- ]);
- $container->setDefinition('processor', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The class "notExistingClass" could not be found.');
- $pass->process($container);
- }
-
- public function testShouldThrowExceptionIfTopicNameIsNotSet()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Topic name is not set on message processor tag but it is required.');
- $pass->process($container);
- }
-
- public function testShouldSetServiceIdAdProcessorIdIfIsNotSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
- $pass->process($container);
-
- $expectedValue = [
- 'processor-id' => 'processor-id',
- ];
-
- $this->assertEquals($expectedValue, $processorRegistry->getArgument(0));
- }
-
- public function testShouldBuildRouteFromSubscriberIfOnlyTopicNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(OnlyTopicNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
- $pass->process($container);
-
- $expectedValue = [
- 'processor-id' => 'processor-id',
- ];
-
- $this->assertEquals($expectedValue, $processorRegistry->getArgument(0));
- }
-
- public function testShouldBuildRouteFromSubscriberIfProcessorNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(ProcessorNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
- $pass->process($container);
-
- $expectedValue = [
- 'subscriber-processor-name' => 'processor-id',
- ];
-
- $this->assertEquals($expectedValue, $processorRegistry->getArgument(0));
- }
-
- public function testShouldThrowExceptionWhenTopicSubscriberConfigurationIsInvalid()
- {
- $this->setExpectedException(\LogicException::class, 'Topic subscriber configuration is invalid. "[12345]"');
-
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(InvalidTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $processorRegistry = new Definition();
- $processorRegistry->setArguments([]);
- $container->setDefinition('enqueue.client.processor_registry', $processorRegistry);
-
- $pass = new BuildProcessorRegistryPass();
- $pass->process($container);
- }
-
- /**
- * @return ContainerBuilder
- */
- private function createContainerBuilder()
- {
- $container = new ContainerBuilder();
- $container->setParameter('enqueue.client.default_queue_name', 'aDefaultQueueName');
-
- return $container;
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildQueueMetaRegistryPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildQueueMetaRegistryPassTest.php
deleted file mode 100644
index 79665080e..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildQueueMetaRegistryPassTest.php
+++ /dev/null
@@ -1,204 +0,0 @@
-createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'processor',
- ]);
- $container->setDefinition('processor', $processor);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
- }
-
- public function testThrowIfProcessorClassNameCouldNotBeFound()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition('notExistingClass');
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'processor',
- ]);
- $container->setDefinition('processor', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The class "notExistingClass" could not be found.');
- $pass->process($container);
- }
-
- public function testShouldBuildQueueMetaRegistry()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'theProcessorName',
- 'topicName' => 'aTopicName',
- ]);
- $container->setDefinition('processor', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'aDefaultQueueName' => ['processors' => ['theProcessorName']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- public function testShouldSetServiceIdAdProcessorIdIfIsNotSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'aTopicName',
- ]);
- $container->setDefinition('processor-service-id', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'aDefaultQueueName' => ['processors' => ['processor-service-id']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- public function testShouldSetQueueIfSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'queueName' => 'theClientQueueName',
- 'topicName' => 'aTopicName',
- ]);
- $container->setDefinition('processor-service-id', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'theClientQueueName' => ['processors' => ['processor-service-id']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- public function testShouldBuildQueueFromSubscriberIfOnlyTopicNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(OnlyTopicNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'aDefaultQueueName' => ['processors' => ['processor-service-id']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- public function testShouldBuildQueueFromSubscriberIfProcessorNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(ProcessorNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'aDefaultQueueName' => ['processors' => ['subscriber-processor-name']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- public function testShouldBuildQueueFromSubscriberIfQueueNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(QueueNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-service-id', $processor);
-
- $registry = new Definition();
- $registry->setArguments([null, []]);
- $container->setDefinition('enqueue.client.meta.queue_meta_registry', $registry);
-
- $pass = new BuildQueueMetaRegistryPass();
- $pass->process($container);
-
- $expectedQueues = [
- 'subscriber-queue-name' => ['processors' => ['processor-service-id']],
- ];
-
- $this->assertEquals($expectedQueues, $registry->getArgument(1));
- }
-
- /**
- * @return ContainerBuilder
- */
- private function createContainerBuilder()
- {
- $container = new ContainerBuilder();
- $container->setParameter('enqueue.client.default_queue_name', 'aDefaultQueueName');
-
- return $container;
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildTopicMetaSubscribersPassTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildTopicMetaSubscribersPassTest.php
deleted file mode 100644
index affac5854..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/BuildTopicMetaSubscribersPassTest.php
+++ /dev/null
@@ -1,313 +0,0 @@
-createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- 'processorName' => 'processor-name',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'topic' => ['processors' => ['processor-name']],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testThrowIfProcessorClassNameCouldNotBeFound()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition('notExistingClass');
- $processor->addTag('enqueue.client.processor', [
- 'processorName' => 'processor',
- ]);
- $container->setDefinition('processor', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The class "notExistingClass" could not be found.');
- $pass->process($container);
- }
-
- public function testShouldBuildTopicMetaSubscribersForOneTagAndSameMetaInRegistry()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- 'processorName' => 'barProcessorName',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[
- 'topic' => ['description' => 'aDescription', 'processors' => ['fooProcessorName']],
- ]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'topic' => [
- 'description' => 'aDescription',
- 'processors' => ['fooProcessorName', 'barProcessorName'],
- ],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldBuildTopicMetaSubscribersForOneTagAndSameMetaInPlusAnotherRegistry()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'fooTopic',
- 'processorName' => 'barProcessorName',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[
- 'fooTopic' => ['description' => 'aDescription', 'processors' => ['fooProcessorName']],
- 'barTopic' => ['description' => 'aBarDescription'],
- ]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'fooTopic' => [
- 'description' => 'aDescription',
- 'processors' => ['fooProcessorName', 'barProcessorName'],
- ],
- 'barTopic' => ['description' => 'aBarDescription'],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldBuildTopicMetaSubscribersForTwoTagAndEmptyRegistry()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'fooTopic',
- 'processorName' => 'fooProcessorName',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'fooTopic',
- 'processorName' => 'barProcessorName',
- ]);
- $container->setDefinition('another-processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'fooTopic' => [
- 'processors' => ['fooProcessorName', 'barProcessorName'],
- ],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldBuildTopicMetaSubscribersForTwoTagSameMetaRegistry()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'fooTopic',
- 'processorName' => 'fooProcessorName',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'fooTopic',
- 'processorName' => 'barProcessorName',
- ]);
- $container->setDefinition('another-processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[
- 'fooTopic' => ['description' => 'aDescription', 'processors' => ['bazProcessorName']],
- ]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'fooTopic' => [
- 'description' => 'aDescription',
- 'processors' => ['bazProcessorName', 'fooProcessorName', 'barProcessorName'],
- ],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testThrowIfTopicNameNotSetOnTagAsAttribute()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', []);
- $container->setDefinition('processor', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Topic name is not set on message processor tag but it is required.');
- $pass->process($container);
- }
-
- public function testShouldSetServiceIdAdProcessorIdIfIsNotSetInTag()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(\stdClass::class);
- $processor->addTag('enqueue.client.processor', [
- 'topicName' => 'topic',
- ]);
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'topic' => ['processors' => ['processor-id']],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldBuildMetaFromSubscriberIfOnlyTopicNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(OnlyTopicNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'topic-subscriber-name' => ['processors' => ['processor-id']],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldBuildMetaFromSubscriberIfProcessorNameSpecified()
- {
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(ProcessorNameTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
-
- $expectedValue = [
- 'topic-subscriber-name' => ['processors' => ['subscriber-processor-name']],
- ];
-
- $this->assertEquals($expectedValue, $topicMetaRegistry->getArgument(0));
- }
-
- public function testShouldThrowExceptionWhenTopicSubscriberConfigurationIsInvalid()
- {
- $this->setExpectedException(\LogicException::class, 'Topic subscriber configuration is invalid. "[12345]"');
-
- $container = $this->createContainerBuilder();
-
- $processor = new Definition(InvalidTopicSubscriber::class);
- $processor->addTag('enqueue.client.processor');
- $container->setDefinition('processor-id', $processor);
-
- $topicMetaRegistry = new Definition();
- $topicMetaRegistry->setArguments([[]]);
- $container->setDefinition('enqueue.client.meta.topic_meta_registry', $topicMetaRegistry);
-
- $pass = new BuildTopicMetaSubscribersPass();
- $pass->process($container);
- }
-
- /**
- * @return ContainerBuilder
- */
- private function createContainerBuilder()
- {
- $container = new ContainerBuilder();
- $container->setParameter('enqueue.client.default_queue_name', 'aDefaultQueueName');
-
- return $container;
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/InvalidTopicSubscriber.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/InvalidTopicSubscriber.php
deleted file mode 100644
index 05ab5361d..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/InvalidTopicSubscriber.php
+++ /dev/null
@@ -1,13 +0,0 @@
- [
- 'processorName' => 'subscriber-processor-name',
- ],
- ];
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/QueueNameTopicSubscriber.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/QueueNameTopicSubscriber.php
deleted file mode 100644
index 6ed163d13..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/Compiler/Mock/QueueNameTopicSubscriber.php
+++ /dev/null
@@ -1,17 +0,0 @@
- [
- 'queueName' => 'subscriber-queue-name',
- ],
- ];
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/ConfigurationTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/ConfigurationTest.php
index 535f6051b..5330cde82 100644
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/ConfigurationTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/ConfigurationTest.php
@@ -2,16 +2,16 @@
namespace Enqueue\Bundle\Tests\Unit\DependencyInjection;
+use DMS\PHPUnitExtensions\ArraySubset\Assert;
use Enqueue\Bundle\DependencyInjection\Configuration;
-use Enqueue\Bundle\Tests\Unit\Mocks\FooTransportFactory;
-use Enqueue\Symfony\DefaultTransportFactory;
-use Enqueue\Symfony\NullTransportFactory;
use Enqueue\Test\ClassExtensionTrait;
+use PHPUnit\Framework\TestCase;
use Symfony\Component\Config\Definition\ConfigurationInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
+use Symfony\Component\Config\Definition\Exception\InvalidTypeException;
use Symfony\Component\Config\Definition\Processor;
-class ConfigurationTest extends \PHPUnit_Framework_TestCase
+class ConfigurationTest extends TestCase
{
use ClassExtensionTrait;
@@ -20,332 +20,643 @@ public function testShouldImplementConfigurationInterface()
$this->assertClassImplements(ConfigurationInterface::class, Configuration::class);
}
- public function testCouldBeConstructedWithFactoriesAsFirstArgument()
+ public function testShouldBeFinal()
{
- new Configuration([]);
+ $this->assertClassFinal(Configuration::class);
}
- public function testThrowIfTransportNotConfigured()
+ public function testShouldProcessSeveralTransports()
{
- $this->setExpectedException(
- InvalidConfigurationException::class,
- 'The child node "transport" at path "enqueue" must be configured.'
- );
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => 'default:',
+ ],
+ 'foo' => [
+ 'transport' => 'foo:',
+ ],
+ ]]);
- $configuration = new Configuration([]);
+ $this->assertConfigEquals([
+ 'default' => [
+ 'transport' => [
+ 'dsn' => 'default:',
+ ],
+ ],
+ 'foo' => [
+ 'transport' => [
+ 'dsn' => 'foo:',
+ ],
+ ],
+ ], $config);
+ }
+
+ public function testTransportFactoryShouldValidateEachTransportAccordingToItsRules()
+ {
+ $configuration = new Configuration(true);
$processor = new Processor();
- $processor->processConfiguration($configuration, [[]]);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Both options factory_class and factory_service are set. Please choose one.');
+ $processor->processConfiguration($configuration, [
+ [
+ 'default' => [
+ 'transport' => [
+ 'factory_class' => 'aClass',
+ 'factory_service' => 'aService',
+ ],
+ ],
+ ],
+ ]);
}
- public function testShouldInjectFooTransportFactoryConfig()
+ public function testShouldSetDefaultConfigurationForClient()
{
- $configuration = new Configuration([new FooTransportFactory()]);
+ $configuration = new Configuration(true);
$processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => null,
+ ],
+ ]]);
+
+ $this->assertConfigEquals([
+ 'default' => [
+ 'client' => [
+ 'prefix' => 'enqueue',
+ 'app_name' => 'app',
+ 'router_processor' => null,
+ 'router_topic' => 'default',
+ 'router_queue' => 'default',
+ 'default_queue' => 'default',
+ 'traceable_producer' => true,
+ 'redelivered_delay_time' => 0,
+ ],
+ ],
+ ], $config);
+ }
+
+ public function testThrowIfClientDriverOptionsIsNotArray()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+
+ $this->expectException(InvalidTypeException::class);
+ // Exception messages vary slightly between versions
+ $this->expectExceptionMessageMatches(
+ '/Invalid type for path "enqueue\.default\.client\.driver_options"\. Expected "?array"?, but got "?string"?/'
+ );
+
$processor->processConfiguration($configuration, [[
- 'transport' => [
- 'foo' => [
- 'foo_param' => 'aParam',
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => [
+ 'driver_options' => 'invalidOption',
],
],
]]);
}
- public function testThrowExceptionIfFooTransportConfigInvalid()
+ public function testShouldConfigureClientDriverOptions()
{
- $configuration = new Configuration([new FooTransportFactory()]);
+ $configuration = new Configuration(true);
$processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => [
+ 'driver_options' => [
+ 'foo' => 'fooVal',
+ ],
+ ],
+ ],
+ ]]);
- $this->setExpectedException(
- InvalidConfigurationException::class,
- 'The path "enqueue.transport.foo.foo_param" cannot contain an empty value, but got null.'
- );
+ $this->assertConfigEquals([
+ 'default' => [
+ 'client' => [
+ 'prefix' => 'enqueue',
+ 'app_name' => 'app',
+ 'router_processor' => null,
+ 'router_topic' => 'default',
+ 'router_queue' => 'default',
+ 'default_queue' => 'default',
+ 'traceable_producer' => true,
+ 'driver_options' => [
+ 'foo' => 'fooVal',
+ ],
+ ],
+ ],
+ ], $config);
+ }
+ public function testThrowExceptionIfRouterTopicIsEmpty()
+ {
+ $this->expectException(InvalidConfigurationException::class);
+ $this->expectExceptionMessage('The path "enqueue.default.client.router_topic" cannot contain an empty value, but got "".');
+
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
$processor->processConfiguration($configuration, [[
- 'transport' => [
- 'foo' => [
- 'foo_param' => null,
+ 'default' => [
+ 'transport' => ['dsn' => 'null:'],
+ 'client' => [
+ 'router_topic' => '',
],
],
]]);
}
- public function testShouldAllowConfigureDefaultTransport()
+ public function testThrowExceptionIfRouterQueueIsEmpty()
{
- $configuration = new Configuration([new DefaultTransportFactory()]);
+ $this->expectException(InvalidConfigurationException::class);
+ $this->expectExceptionMessage('The path "enqueue.default.client.router_queue" cannot contain an empty value, but got "".');
+
+ $configuration = new Configuration(true);
$processor = new Processor();
$processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ 'default' => [
+ 'transport' => ['dsn' => 'null:'],
+ 'client' => [
+ 'router_queue' => '',
+ ],
+ ],
+ ]]);
+ }
+
+ public function testShouldThrowExceptionIfDefaultProcessorQueueIsEmpty()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+
+ $this->expectException(InvalidConfigurationException::class);
+ $this->expectExceptionMessage('The path "enqueue.default.client.default_queue" cannot contain an empty value, but got "".');
+ $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => ['dsn' => 'null:'],
+ 'client' => [
+ 'default_queue' => '',
+ ],
],
]]);
}
- public function testShouldAllowConfigureNullTransport()
+ public function testJobShouldBeDisabledByDefault()
{
- $configuration = new Configuration([new NullTransportFactory()]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'null' => true,
+ 'default' => [
+ 'transport' => [],
],
]]);
- $this->assertArraySubset([
- 'transport' => [
- 'null' => [],
+ Assert::assertArraySubset([
+ 'default' => [
+ 'job' => [
+ 'enabled' => false,
+ ],
],
], $config);
}
- public function testShouldAllowConfigureSeveralTransportsSameTime()
+ public function testCouldEnableJob()
{
- $configuration = new Configuration([
- new NullTransportFactory(),
- new DefaultTransportFactory(),
- new FooTransportFactory(),
- ]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => 'foo',
- 'null' => true,
- 'foo' => ['foo_param' => 'aParam'],
+ 'default' => [
+ 'transport' => [],
+ 'job' => true,
],
]]);
- $this->assertArraySubset([
- 'transport' => [
- 'default' => ['alias' => 'foo'],
- 'null' => [],
- 'foo' => ['foo_param' => 'aParam'],
+ Assert::assertArraySubset([
+ 'default' => [
+ 'job' => true,
],
], $config);
}
- public function testShouldSetDefaultConfigurationForClient()
+ public function testDoctrinePingConnectionExtensionShouldBeDisabledByDefault()
{
- $configuration = new Configuration([new DefaultTransportFactory()]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ 'default' => [
+ 'transport' => null,
],
- 'client' => null,
]]);
- $this->assertArraySubset([
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_ping_connection_extension' => false,
+ ],
+ ],
+ ], $config);
+ }
+
+ public function testDoctrinePingConnectionExtensionCouldBeEnabled()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
+ 'extensions' => [
+ 'doctrine_ping_connection_extension' => true,
+ ],
],
- 'client' => [
- 'prefix' => 'enqueue',
- 'app_name' => 'app',
- 'router_processor' => 'enqueue.client.router_processor',
- 'router_topic' => 'router',
- 'router_queue' => 'default',
- 'default_processor_queue' => 'default',
- 'traceable_producer' => false,
- 'redelivered_delay_time' => 0,
+ ]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_ping_connection_extension' => true,
+ ],
],
], $config);
}
- public function testThrowExceptionIfRouterTopicIsEmpty()
+ public function testDoctrineClearIdentityMapExtensionShouldBeDisabledByDefault()
{
- $this->setExpectedException(
- InvalidConfigurationException::class,
- 'The path "enqueue.client.router_topic" cannot contain an empty value, but got "".'
- );
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
+ ],
+ ]]);
- $configuration = new Configuration([new DefaultTransportFactory()]);
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_clear_identity_map_extension' => false,
+ ],
+ ],
+ ], $config);
+ }
+
+ public function testDoctrineClearIdentityMapExtensionCouldBeEnabled()
+ {
+ $configuration = new Configuration(true);
$processor = new Processor();
- $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_clear_identity_map_extension' => true,
+ ],
+ ],
+ ]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_clear_identity_map_extension' => true,
+ ],
],
- 'client' => [
- 'router_topic' => '',
+ ], $config);
+ }
+
+ public function testDoctrineOdmClearIdentityMapExtensionShouldBeDisabledByDefault()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
],
]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_odm_clear_identity_map_extension' => false,
+ ],
+ ],
+ ], $config);
}
- public function testThrowExceptionIfRouterQueueIsEmpty()
+ public function testDoctrineOdmClearIdentityMapExtensionCouldBeEnabled()
{
- $this->setExpectedException(
- InvalidConfigurationException::class,
- 'The path "enqueue.client.router_queue" cannot contain an empty value, but got "".'
- );
+ $configuration = new Configuration(true);
- $configuration = new Configuration([new DefaultTransportFactory()]);
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_odm_clear_identity_map_extension' => true,
+ ],
+ ],
+ ]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_odm_clear_identity_map_extension' => true,
+ ],
+ ],
+ ], $config);
+ }
+
+ public function testDoctrineClosedEntityManagerExtensionShouldBeDisabledByDefault()
+ {
+ $configuration = new Configuration(true);
$processor = new Processor();
- $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
+ ],
+ ]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_closed_entity_manager_extension' => false,
+ ],
],
- 'client' => [
- 'router_queue' => '',
+ ], $config);
+ }
+
+ public function testDoctrineClosedEntityManagerExtensionCouldBeEnabled()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
+ 'extensions' => [
+ 'doctrine_closed_entity_manager_extension' => true,
+ ],
],
]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'doctrine_closed_entity_manager_extension' => true,
+ ],
+ ],
+ ], $config);
}
- public function testShouldThrowExceptionIfDefaultProcessorQueueIsEmpty()
+ public function testResetServicesExtensionShouldBeDisabledByDefault()
{
- $configuration = new Configuration([new DefaultTransportFactory()]);
+ $configuration = new Configuration(true);
$processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => null,
+ ],
+ ]]);
- $this->expectException(InvalidConfigurationException::class);
- $this->expectExceptionMessage('The path "enqueue.client.default_processor_queue" cannot contain an empty value, but got "".');
- $processor->processConfiguration($configuration, [[
- 'transport' => [
- 'default' => ['alias' => 'foo'],
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'reset_services_extension' => false,
+ ],
],
- 'client' => [
- 'default_processor_queue' => '',
+ ], $config);
+ }
+
+ public function testResetServicesExtensionCouldBeEnabled()
+ {
+ $configuration = new Configuration(true);
+
+ $processor = new Processor();
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reset_services_extension' => true,
+ ],
],
]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'reset_services_extension' => true,
+ ],
+ ],
+ ], $config);
}
- public function testJobShouldBeDisabledByDefault()
+ public function testSignalExtensionShouldBeEnabledIfPcntlExtensionIsLoaded()
{
- $configuration = new Configuration([]);
+ $isLoaded = function_exists('pcntl_signal_dispatch');
+
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
+ 'default' => [
+ 'transport' => [],
+ ],
]]);
- $this->assertArraySubset([
- 'job' => false,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'signal_extension' => $isLoaded,
+ ],
+ ],
], $config);
}
- public function testCouldEnableJob()
+ public function testSignalExtensionCouldBeDisabled()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
- 'job' => true,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'signal_extension' => false,
+ ],
+ ],
]]);
- $this->assertArraySubset([
- 'job' => true,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'signal_extension' => false,
+ ],
+ ],
], $config);
}
- public function testDoctrinePingConnectionExtensionShouldBeDisabledByDefault()
+ public function testReplyExtensionShouldBeEnabledByDefault()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
+ 'default' => [
+ 'transport' => [],
+ ],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'doctrine_ping_connection_extension' => false,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'reply_extension' => true,
+ ],
],
], $config);
}
- public function testDoctrinePingConnectionExtensionCouldBeEnabled()
+ public function testReplyExtensionCouldBeDisabled()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_ping_connection_extension' => true,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reply_extension' => false,
+ ],
],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'doctrine_ping_connection_extension' => true,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'extensions' => [
+ 'reply_extension' => false,
+ ],
],
], $config);
}
- public function testDoctrineClearIdentityMapExtensionShouldBeDisabledByDefault()
+ public function testShouldDisableAsyncEventsByDefault()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
+ 'default' => [
+ 'transport' => [],
+ ],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'doctrine_clear_identity_map_extension' => false,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'async_events' => [
+ 'enabled' => false,
+ ],
],
], $config);
}
- public function testDoctrineClearIdentityMapExtensionCouldBeEnabled()
+ public function testShouldAllowEnableAsyncEvents()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
+
+ $config = $processor->processConfiguration($configuration, [[
+ 'default' => [
+ 'transport' => [],
+ 'async_events' => true,
+ ],
+ ]]);
+
+ Assert::assertArraySubset([
+ 'default' => [
+ 'async_events' => [
+ 'enabled' => true,
+ ],
+ ],
+ ], $config);
+
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_clear_identity_map_extension' => true,
+ 'default' => [
+ 'transport' => [],
+ 'async_events' => [
+ 'enabled' => true,
+ ],
],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'doctrine_clear_identity_map_extension' => true,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'async_events' => [
+ 'enabled' => true,
+ ],
],
], $config);
}
- public function testSignalExtensionShouldBeEnabledByDefault()
+ public function testShouldSetDefaultConfigurationForConsumption()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
+ 'default' => [
+ 'transport' => [],
+ ],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'signal_extension' => true,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'consumption' => [
+ 'receive_timeout' => 10000,
+ ],
],
], $config);
}
- public function testSignalExtensionCouldBeDisabled()
+ public function testShouldAllowConfigureConsumption()
{
- $configuration = new Configuration([]);
+ $configuration = new Configuration(true);
$processor = new Processor();
$config = $processor->processConfiguration($configuration, [[
- 'transport' => [],
- 'extensions' => [
- 'signal_extension' => false,
+ 'default' => [
+ 'transport' => [],
+ 'consumption' => [
+ 'receive_timeout' => 456,
+ ],
],
]]);
- $this->assertArraySubset([
- 'extensions' => [
- 'signal_extension' => false,
+ Assert::assertArraySubset([
+ 'default' => [
+ 'consumption' => [
+ 'receive_timeout' => 456,
+ ],
],
], $config);
}
+
+ private function assertConfigEquals(array $expected, array $actual): void
+ {
+ Assert::assertArraySubset($expected, $actual, false, var_export($actual, true));
+ }
}
diff --git a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/EnqueueExtensionTest.php b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/EnqueueExtensionTest.php
index 525ed914e..6358bd24d 100644
--- a/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/EnqueueExtensionTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/DependencyInjection/EnqueueExtensionTest.php
@@ -4,345 +4,321 @@
use Enqueue\Bundle\DependencyInjection\Configuration;
use Enqueue\Bundle\DependencyInjection\EnqueueExtension;
-use Enqueue\Bundle\Tests\Unit\Mocks\FooTransportFactory;
-use Enqueue\Client\MessageProducer;
-use Enqueue\Client\TraceableMessageProducer;
-use Enqueue\Symfony\DefaultTransportFactory;
-use Enqueue\Symfony\NullTransportFactory;
+use Enqueue\Client\CommandSubscriberInterface;
+use Enqueue\Client\Producer;
+use Enqueue\Client\ProducerInterface;
+use Enqueue\Client\TopicSubscriberInterface;
+use Enqueue\Client\TraceableProducer;
+use Enqueue\JobQueue\JobRunner;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullContext;
+use PHPUnit\Framework\TestCase;
use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Extension\Extension;
use Symfony\Component\DependencyInjection\Reference;
-use Symfony\Component\HttpKernel\DependencyInjection\Extension;
-class EnqueueExtensionTest extends \PHPUnit_Framework_TestCase
+class EnqueueExtensionTest extends TestCase
{
use ClassExtensionTrait;
public function testShouldImplementConfigurationInterface()
{
- self::assertClassExtends(Extension::class, EnqueueExtension::class);
+ $this->assertClassExtends(Extension::class, EnqueueExtension::class);
}
- public function testCouldBeConstructedWithoutAnyArguments()
+ public function testShouldBeFinal()
{
- new EnqueueExtension();
+ $this->assertClassFinal(EnqueueExtension::class);
}
- public function testThrowIfTransportFactoryNameEmpty()
+ public function testShouldRegisterConnectionFactory()
{
- $extension = new EnqueueExtension();
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Transport factory name cannot be empty');
-
- $extension->addTransportFactory(new FooTransportFactory(null));
- }
+ $container = $this->getContainerBuilder(true);
- public function testThrowIfTransportFactoryWithSameNameAlreadyAdded()
- {
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new FooTransportFactory('foo'));
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Transport factory with such name already added. Name foo');
+ $extension->load([[
+ 'default' => [
+ 'transport' => null,
+ ],
+ ]], $container);
- $extension->addTransportFactory(new FooTransportFactory('foo'));
+ self::assertTrue($container->hasDefinition('enqueue.transport.default.connection_factory'));
+ self::assertNotEmpty($container->getDefinition('enqueue.transport.default.connection_factory')->getFactory());
}
- public function testShouldConfigureNullTransport()
+ public function testShouldRegisterContext()
{
- $container = new ContainerBuilder();
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new NullTransportFactory());
$extension->load([[
- 'transport' => [
- 'null' => true,
+ 'default' => [
+ 'transport' => null,
],
]], $container);
- self::assertTrue($container->hasDefinition('enqueue.transport.null.context'));
- $context = $container->getDefinition('enqueue.transport.null.context');
- self::assertEquals(NullContext::class, $context->getClass());
+ self::assertTrue($container->hasDefinition('enqueue.transport.default.context'));
+ self::assertNotEmpty($container->getDefinition('enqueue.transport.default.context')->getFactory());
}
- public function testShouldUseNullTransportAsDefault()
+ public function testShouldRegisterClientDriver()
{
- $container = new ContainerBuilder();
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new NullTransportFactory());
- $extension->addTransportFactory(new DefaultTransportFactory());
$extension->load([[
- 'transport' => [
- 'default' => 'null',
- 'null' => true,
+ 'default' => [
+ 'transport' => null,
+ 'client' => true,
],
]], $container);
- self::assertEquals(
- 'enqueue.transport.default.context',
- (string) $container->getAlias('enqueue.transport.context')
- );
- self::assertEquals(
- 'enqueue.transport.null.context',
- (string) $container->getAlias('enqueue.transport.default.context')
- );
+ self::assertTrue($container->hasDefinition('enqueue.client.default.driver'));
+ self::assertNotEmpty($container->getDefinition('enqueue.client.default.driver')->getFactory());
}
- public function testShouldConfigureFooTransport()
+ public function testShouldLoadClientServicesWhenEnabled()
{
- $container = new ContainerBuilder();
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'transport' => [
- 'foo' => ['foo_param' => 'aParam'],
+ 'default' => [
+ 'client' => null,
+ 'transport' => 'null:',
],
]], $container);
- self::assertTrue($container->hasDefinition('foo.context'));
- $context = $container->getDefinition('foo.context');
- self::assertEquals(\stdClass::class, $context->getClass());
- self::assertEquals([['foo_param' => 'aParam']], $context->getArguments());
+ self::assertTrue($container->hasDefinition('enqueue.client.default.driver'));
+ self::assertTrue($container->hasDefinition('enqueue.client.default.config'));
+ self::assertTrue($container->hasAlias(ProducerInterface::class));
}
- public function testShouldUseFooTransportAsDefault()
+ public function testShouldUseProducerByDefault()
{
- $container = new ContainerBuilder();
+ $container = $this->getContainerBuilder(false);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new FooTransportFactory());
- $extension->addTransportFactory(new DefaultTransportFactory());
$extension->load([[
- 'transport' => [
- 'default' => 'foo',
- 'foo' => ['foo_param' => 'aParam'],
+ 'default' => [
+ 'client' => null,
+ 'transport' => 'null',
],
]], $container);
- self::assertEquals(
- 'enqueue.transport.default.context',
- (string) $container->getAlias('enqueue.transport.context')
- );
- self::assertEquals(
- 'enqueue.transport.foo.context',
- (string) $container->getAlias('enqueue.transport.default.context')
- );
+ $producer = $container->getDefinition('enqueue.client.default.producer');
+ self::assertEquals(Producer::class, $producer->getClass());
}
- public function testShouldLoadClientServicesWhenEnabled()
+ public function testShouldUseMessageProducerIfTraceableProducerOptionSetToFalseExplicitly()
{
- $container = new ContainerBuilder();
+ $container = $this->getContainerBuilder(false);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'client' => null,
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
+ 'default' => [
+ 'client' => [
+ 'traceable_producer' => false,
],
+ 'transport' => 'null:',
],
]], $container);
- self::assertTrue($container->hasDefinition('enqueue.client.config'));
- self::assertTrue($container->hasDefinition('enqueue.client.message_producer'));
+ $producer = $container->getDefinition('enqueue.client.default.producer');
+ self::assertEquals(Producer::class, $producer->getClass());
}
- public function testShouldUseMessageProducerByDefault()
+ public function testShouldUseTraceableMessageProducerIfDebugEnabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', false);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'client' => null,
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
- ],
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => null,
],
]], $container);
- $messageProducer = $container->getDefinition('enqueue.client.message_producer');
- self::assertEquals(MessageProducer::class, $messageProducer->getClass());
+ $producer = $container->getDefinition('enqueue.client.default.traceable_producer');
+ self::assertEquals(TraceableProducer::class, $producer->getClass());
+ self::assertEquals(
+ ['enqueue.client.default.producer', null, 0],
+ $producer->getDecoratedService()
+ );
+
+ self::assertInstanceOf(Reference::class, $producer->getArgument(0));
+
+ $innerServiceName = 'enqueue.client.default.traceable_producer.inner';
+
+ self::assertEquals(
+ $innerServiceName,
+ (string) $producer->getArgument(0)
+ );
}
- public function testShouldUseMessageProducerIfTraceableProducerOptionSetToFalseExplicitly()
+ public function testShouldNotUseTraceableMessageProducerIfDebugDisabledAndNotSetExplicitly()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', false);
+ $container = $this->getContainerBuilder(false);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'client' => [
- 'traceable_producer' => false,
- ],
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
- ],
+ 'default' => [
+ 'transport' => 'null:',
],
]], $container);
- $messageProducer = $container->getDefinition('enqueue.client.message_producer');
- self::assertEquals(MessageProducer::class, $messageProducer->getClass());
+ $this->assertFalse($container->hasDefinition('enqueue.client.default.traceable_producer'));
}
- public function testShouldUseTraceableMessageProducerIfTraceableProducerOptionSetToTrueExplicitly()
+ public function testShouldUseTraceableMessageProducerIfDebugDisabledButTraceableProducerOptionSetToTrueExplicitly()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(false);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'client' => [
- 'traceable_producer' => true,
- ],
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
+ 'default' => [
+ 'client' => [
+ 'traceable_producer' => true,
],
+ 'transport' => 'null:',
],
]], $container);
- $messageProducer = $container->getDefinition('enqueue.client.traceable_message_producer');
- self::assertEquals(TraceableMessageProducer::class, $messageProducer->getClass());
+ $producer = $container->getDefinition('enqueue.client.default.traceable_producer');
+ self::assertEquals(TraceableProducer::class, $producer->getClass());
self::assertEquals(
- ['enqueue.client.message_producer', null, 0],
- $messageProducer->getDecoratedService()
+ ['enqueue.client.default.producer', null, 0],
+ $producer->getDecoratedService()
);
- self::assertInstanceOf(Reference::class, $messageProducer->getArgument(0));
+ self::assertInstanceOf(Reference::class, $producer->getArgument(0));
+
+ $innerServiceName = 'enqueue.client.default.traceable_producer.inner';
+
self::assertEquals(
- 'enqueue.client.traceable_message_producer.inner',
- (string) $messageProducer->getArgument(0)
+ $innerServiceName,
+ (string) $producer->getArgument(0)
);
}
public function testShouldLoadDelayRedeliveredMessageExtensionIfRedeliveredDelayTimeGreaterThenZero()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => [
+ 'redelivered_delay_time' => 12345,
],
],
- 'client' => [
- 'redelivered_delay_time' => 12345,
- ],
]], $container);
- $extension = $container->getDefinition('enqueue.client.delay_redelivered_message_extension');
+ $extension = $container->getDefinition('enqueue.client.default.delay_redelivered_message_extension');
self::assertEquals(12345, $extension->getArgument(1));
}
public function testShouldNotLoadDelayRedeliveredMessageExtensionIfRedeliveredDelayTimeIsZero()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
- $extension->addTransportFactory(new DefaultTransportFactory());
- $extension->addTransportFactory(new FooTransportFactory());
$extension->load([[
- 'transport' => [
- 'default' => 'foo',
- 'foo' => [
- 'foo_param' => true,
+ 'default' => [
+ 'transport' => 'null:',
+ 'client' => [
+ 'redelivered_delay_time' => 0,
],
],
- 'client' => [
- 'redelivered_delay_time' => 0,
- ],
]], $container);
- $this->assertFalse($container->hasDefinition('enqueue.client.delay_redelivered_message_extension'));
+ $this->assertFalse($container->hasDefinition('enqueue.client.default.delay_redelivered_message_extension'));
}
public function testShouldLoadJobServicesIfEnabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'job' => true,
+ 'default' => [
+ 'transport' => [],
+ 'client' => null,
+ 'job' => true,
+ ],
]], $container);
- self::assertTrue($container->hasDefinition('enqueue.job.runner'));
+ self::assertTrue($container->hasDefinition(JobRunner::class));
+ }
+
+ public function testShouldThrowExceptionIfClientIsNotEnabledOnJobLoad()
+ {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Client is required for job-queue.');
+
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'job' => true,
+ ],
+ ]], $container);
}
public function testShouldNotLoadJobServicesIfDisabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'job' => false,
+ 'default' => [
+ 'transport' => [],
+ 'job' => false,
+ ],
]], $container);
- self::assertFalse($container->hasDefinition('enqueue.job.runner'));
+ self::assertFalse($container->hasDefinition(JobRunner::class));
}
public function testShouldAllowGetConfiguration()
{
$extension = new EnqueueExtension();
- $configuration = $extension->getConfiguration([], new ContainerBuilder());
+ $configuration = $extension->getConfiguration([], $this->getContainerBuilder(true));
self::assertInstanceOf(Configuration::class, $configuration);
}
public function testShouldLoadDoctrinePingConnectionExtensionServiceIfEnabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_ping_connection_extension' => true,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_ping_connection_extension' => true,
+ ],
],
]], $container);
@@ -351,15 +327,16 @@ public function testShouldLoadDoctrinePingConnectionExtensionServiceIfEnabled()
public function testShouldNotLoadDoctrinePingConnectionExtensionServiceIfDisabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_ping_connection_extension' => false,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_ping_connection_extension' => false,
+ ],
],
]], $container);
@@ -368,15 +345,16 @@ public function testShouldNotLoadDoctrinePingConnectionExtensionServiceIfDisable
public function testShouldLoadDoctrineClearIdentityMapExtensionServiceIfEnabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_clear_identity_map_extension' => true,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_clear_identity_map_extension' => true,
+ ],
],
]], $container);
@@ -385,32 +363,142 @@ public function testShouldLoadDoctrineClearIdentityMapExtensionServiceIfEnabled(
public function testShouldNotLoadDoctrineClearIdentityMapExtensionServiceIfDisabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'doctrine_clear_identity_map_extension' => false,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_clear_identity_map_extension' => false,
+ ],
],
]], $container);
self::assertFalse($container->hasDefinition('enqueue.consumption.doctrine_clear_identity_map_extension'));
}
+ public function testShouldLoadDoctrineOdmClearIdentityMapExtensionServiceIfEnabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_odm_clear_identity_map_extension' => true,
+ ],
+ ],
+ ]], $container);
+
+ self::assertTrue($container->hasDefinition('enqueue.consumption.doctrine_odm_clear_identity_map_extension'));
+ }
+
+ public function testShouldNotLoadDoctrineOdmClearIdentityMapExtensionServiceIfDisabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_odm_clear_identity_map_extension' => false,
+ ],
+ ],
+ ]], $container);
+
+ self::assertFalse($container->hasDefinition('enqueue.consumption.doctrine_odm_clear_identity_map_extension'));
+ }
+
+ public function testShouldLoadDoctrineClosedEntityManagerExtensionServiceIfEnabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_closed_entity_manager_extension' => true,
+ ],
+ ],
+ ]], $container);
+
+ self::assertTrue($container->hasDefinition('enqueue.consumption.doctrine_closed_entity_manager_extension'));
+ }
+
+ public function testShouldNotLoadDoctrineClosedEntityManagerExtensionServiceIfDisabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'doctrine_closed_entity_manager_extension' => false,
+ ],
+ ],
+ ]], $container);
+
+ self::assertFalse($container->hasDefinition('enqueue.consumption.doctrine_closed_entity_manager_extension'));
+ }
+
+ public function testShouldLoadResetServicesExtensionServiceIfEnabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reset_services_extension' => true,
+ ],
+ ],
+ ]], $container);
+
+ self::assertTrue($container->hasDefinition('enqueue.consumption.reset_services_extension'));
+ }
+
+ public function testShouldNotLoadResetServicesExtensionServiceIfDisabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reset_services_extension' => false,
+ ],
+ ],
+ ]], $container);
+
+ self::assertFalse($container->hasDefinition('enqueue.consumption.reset_services_extension'));
+ }
+
public function testShouldLoadSignalExtensionServiceIfEnabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'signal_extension' => true,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'signal_extension' => true,
+ ],
],
]], $container);
@@ -419,18 +507,191 @@ public function testShouldLoadSignalExtensionServiceIfEnabled()
public function testShouldNotLoadSignalExtensionServiceIfDisabled()
{
- $container = new ContainerBuilder();
- $container->setParameter('kernel.debug', true);
+ $container = $this->getContainerBuilder(true);
$extension = new EnqueueExtension();
$extension->load([[
- 'transport' => [],
- 'extensions' => [
- 'signal_extension' => false,
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'signal_extension' => false,
+ ],
],
]], $container);
self::assertFalse($container->hasDefinition('enqueue.consumption.signal_extension'));
}
+
+ public function testShouldLoadReplyExtensionServiceIfEnabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reply_extension' => true,
+ ],
+ ],
+ ]], $container);
+
+ self::assertTrue($container->hasDefinition('enqueue.consumption.reply_extension'));
+ }
+
+ public function testShouldNotLoadReplyExtensionServiceIfDisabled()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'transport' => [],
+ 'extensions' => [
+ 'reply_extension' => false,
+ ],
+ ],
+ ]], $container);
+
+ self::assertFalse($container->hasDefinition('enqueue.consumption.reply_extension'));
+ }
+
+ public function testShouldAddJobQueueEntityMapping()
+ {
+ $container = $this->getContainerBuilder(true);
+ $container->setParameter('kernel.bundles', ['DoctrineBundle' => true]);
+ $container->prependExtensionConfig('doctrine', ['dbal' => true]);
+
+ $extension = new EnqueueExtension();
+
+ $extension->prepend($container);
+
+ $config = $container->getExtensionConfig('doctrine');
+
+ $this->assertSame(['dbal' => true], $config[1]);
+ $this->assertNotEmpty($config[0]['orm']['mappings']['enqueue_job_queue']);
+ }
+
+ public function testShouldNotAddJobQueueEntityMappingIfDoctrineBundleIsNotRegistered()
+ {
+ $container = $this->getContainerBuilder(true);
+ $container->setParameter('kernel.bundles', []);
+
+ $extension = new EnqueueExtension();
+
+ $extension->prepend($container);
+
+ $this->assertSame([], $container->getExtensionConfig('doctrine'));
+ }
+
+ public function testShouldConfigureQueueConsumer()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+ $extension->load([[
+ 'default' => [
+ 'client' => [],
+ 'transport' => [
+ ],
+ 'consumption' => [
+ 'receive_timeout' => 456,
+ ],
+ ],
+ ]], $container);
+
+ $def = $container->getDefinition('enqueue.transport.default.queue_consumer');
+ $this->assertSame('%enqueue.transport.default.receive_timeout%', $def->getArgument(4));
+
+ $this->assertSame(456, $container->getParameter('enqueue.transport.default.receive_timeout'));
+
+ $def = $container->getDefinition('enqueue.client.default.queue_consumer');
+ $this->assertSame(456, $def->getArgument(4));
+ }
+
+ public function testShouldSetPropertyWithAllConfiguredTransports()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+ $extension->load([[
+ 'default' => [
+ 'transport' => 'default:',
+ 'client' => [],
+ ],
+ 'foo' => [
+ 'transport' => 'foo:',
+ 'client' => [],
+ ],
+ 'bar' => [
+ 'transport' => 'bar:',
+ 'client' => [],
+ ],
+ ]], $container);
+
+ $this->assertTrue($container->hasParameter('enqueue.transports'));
+ $this->assertEquals(['default', 'foo', 'bar'], $container->getParameter('enqueue.transports'));
+ }
+
+ public function testShouldSetPropertyWithAllConfiguredClients()
+ {
+ $container = $this->getContainerBuilder(true);
+
+ $extension = new EnqueueExtension();
+ $extension->load([[
+ 'default' => [
+ 'transport' => 'default:',
+ 'client' => [],
+ ],
+ 'foo' => [
+ 'transport' => 'foo:',
+ ],
+ 'bar' => [
+ 'transport' => 'bar:',
+ 'client' => [],
+ ],
+ ]], $container);
+
+ $this->assertTrue($container->hasParameter('enqueue.clients'));
+ $this->assertEquals(['default', 'bar'], $container->getParameter('enqueue.clients'));
+ }
+
+ public function testShouldLoadProcessAutoconfigureChildDefinition()
+ {
+ $container = $this->getContainerBuilder(true);
+ $extension = new EnqueueExtension();
+
+ $extension->load([[
+ 'default' => [
+ 'client' => [],
+ 'transport' => [],
+ ],
+ ]], $container);
+
+ $autoconfigured = $container->getAutoconfiguredInstanceof();
+
+ self::assertArrayHasKey(CommandSubscriberInterface::class, $autoconfigured);
+ self::assertTrue($autoconfigured[CommandSubscriberInterface::class]->hasTag('enqueue.command_subscriber'));
+ self::assertTrue($autoconfigured[CommandSubscriberInterface::class]->isPublic());
+
+ self::assertArrayHasKey(TopicSubscriberInterface::class, $autoconfigured);
+ self::assertTrue($autoconfigured[TopicSubscriberInterface::class]->hasTag('enqueue.topic_subscriber'));
+ self::assertTrue($autoconfigured[TopicSubscriberInterface::class]->isPublic());
+ }
+
+ /**
+ * @param bool $debug
+ *
+ * @return ContainerBuilder
+ */
+ private function getContainerBuilder($debug)
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('kernel.debug', $debug);
+
+ return $container;
+ }
}
diff --git a/pkg/enqueue-bundle/Tests/Unit/EnqueueBundleTest.php b/pkg/enqueue-bundle/Tests/Unit/EnqueueBundleTest.php
index 72d931360..7d5b0232b 100644
--- a/pkg/enqueue-bundle/Tests/Unit/EnqueueBundleTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/EnqueueBundleTest.php
@@ -2,24 +2,12 @@
namespace Enqueue\Bundle\Tests\Unit;
-use Enqueue\AmqpExt\Symfony\AmqpTransportFactory;
-use Enqueue\AmqpExt\Symfony\RabbitMqTransportFactory;
-use Enqueue\Bundle\DependencyInjection\Compiler\BuildClientRoutingPass;
-use Enqueue\Bundle\DependencyInjection\Compiler\BuildExtensionsPass;
-use Enqueue\Bundle\DependencyInjection\Compiler\BuildProcessorRegistryPass;
-use Enqueue\Bundle\DependencyInjection\Compiler\BuildQueueMetaRegistryPass;
-use Enqueue\Bundle\DependencyInjection\Compiler\BuildTopicMetaSubscribersPass;
-use Enqueue\Bundle\DependencyInjection\EnqueueExtension;
use Enqueue\Bundle\EnqueueBundle;
-use Enqueue\Stomp\Symfony\RabbitMqStompTransportFactory;
-use Enqueue\Stomp\Symfony\StompTransportFactory;
-use Enqueue\Symfony\DefaultTransportFactory;
-use Enqueue\Symfony\NullTransportFactory;
use Enqueue\Test\ClassExtensionTrait;
-use Symfony\Component\DependencyInjection\ContainerBuilder;
+use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpKernel\Bundle\Bundle;
-class EnqueueBundleTest extends \PHPUnit_Framework_TestCase
+class EnqueueBundleTest extends TestCase
{
use ClassExtensionTrait;
@@ -27,135 +15,4 @@ public function testShouldExtendBundleClass()
{
$this->assertClassExtends(Bundle::class, EnqueueBundle::class);
}
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new EnqueueBundle();
- }
-
- public function testShouldRegisterExpectedCompilerPasses()
- {
- $extensionMock = $this->createMock(EnqueueExtension::class);
-
- $container = $this->createMock(ContainerBuilder::class);
- $container
- ->expects($this->at(0))
- ->method('addCompilerPass')
- ->with($this->isInstanceOf(BuildExtensionsPass::class))
- ;
- $container
- ->expects($this->at(1))
- ->method('addCompilerPass')
- ->with($this->isInstanceOf(BuildClientRoutingPass::class))
- ;
- $container
- ->expects($this->at(2))
- ->method('addCompilerPass')
- ->with($this->isInstanceOf(BuildProcessorRegistryPass::class))
- ;
- $container
- ->expects($this->at(3))
- ->method('addCompilerPass')
- ->with($this->isInstanceOf(BuildTopicMetaSubscribersPass::class))
- ;
- $container
- ->expects($this->at(4))
- ->method('addCompilerPass')
- ->with($this->isInstanceOf(BuildQueueMetaRegistryPass::class))
- ;
- $container
- ->expects($this->at(5))
- ->method('getExtension')
- ->willReturn($extensionMock)
- ;
-
- $bundle = new EnqueueBundle();
- $bundle->build($container);
- }
-
- public function testShouldRegisterDefaultAndNullTransportFactories()
- {
- $extensionMock = $this->createEnqueueExtensionMock();
-
- $container = new ContainerBuilder();
- $container->registerExtension($extensionMock);
-
- $extensionMock
- ->expects($this->at(0))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(DefaultTransportFactory::class))
- ;
- $extensionMock
- ->expects($this->at(1))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(NullTransportFactory::class))
- ;
-
- $bundle = new EnqueueBundle();
- $bundle->build($container);
- }
-
- public function testShouldRegisterStompAndRabbitMqStompTransportFactories()
- {
- $extensionMock = $this->createEnqueueExtensionMock();
-
- $container = new ContainerBuilder();
- $container->registerExtension($extensionMock);
-
- $extensionMock
- ->expects($this->at(2))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(StompTransportFactory::class))
- ;
- $extensionMock
- ->expects($this->at(3))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(RabbitMqStompTransportFactory::class))
- ;
-
- $bundle = new EnqueueBundle();
- $bundle->build($container);
- }
-
- public function testShouldRegisterAmqpAndRabbitMqAmqpTransportFactories()
- {
- $extensionMock = $this->createEnqueueExtensionMock();
-
- $container = new ContainerBuilder();
- $container->registerExtension($extensionMock);
-
- $extensionMock
- ->expects($this->at(4))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(AmqpTransportFactory::class))
- ;
- $extensionMock
- ->expects($this->at(5))
- ->method('addTransportFactory')
- ->with($this->isInstanceOf(RabbitMqTransportFactory::class))
- ;
-
- $bundle = new EnqueueBundle();
- $bundle->build($container);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|EnqueueExtension
- */
- private function createEnqueueExtensionMock()
- {
- $extensionMock = $this->createMock(EnqueueExtension::class);
- $extensionMock
- ->expects($this->once())
- ->method('getAlias')
- ->willReturn('enqueue')
- ;
- $extensionMock
- ->expects($this->once())
- ->method('getNamespace')
- ->willReturn(false)
- ;
-
- return $extensionMock;
- }
}
diff --git a/pkg/enqueue-bundle/Tests/Unit/Mocks/FooTransportFactory.php b/pkg/enqueue-bundle/Tests/Unit/Mocks/FooTransportFactory.php
deleted file mode 100644
index 89ee65995..000000000
--- a/pkg/enqueue-bundle/Tests/Unit/Mocks/FooTransportFactory.php
+++ /dev/null
@@ -1,64 +0,0 @@
-name = $name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function addConfiguration(ArrayNodeDefinition $builder)
- {
- $builder
- ->children()
- ->scalarNode('foo_param')->isRequired()->cannotBeEmpty()->end()
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- public function createContext(ContainerBuilder $container, array $config)
- {
- $connectionId = 'foo.context';
-
- $container->setDefinition($connectionId, new Definition(\stdClass::class, [$config]));
-
- return $connectionId;
- }
-
- public function createDriver(ContainerBuilder $container, array $config)
- {
- $driverId = 'foo.driver';
-
- $container->setDefinition($driverId, new Definition(\stdClass::class, [$config]));
-
- return $driverId;
- }
-
- /**
- * {@inheritdoc}
- */
- public function getName()
- {
- return $this->name;
- }
-}
diff --git a/pkg/enqueue-bundle/Tests/Unit/Profiler/MessageQueueCollectorTest.php b/pkg/enqueue-bundle/Tests/Unit/Profiler/MessageQueueCollectorTest.php
index 032ca6179..d6d638d75 100644
--- a/pkg/enqueue-bundle/Tests/Unit/Profiler/MessageQueueCollectorTest.php
+++ b/pkg/enqueue-bundle/Tests/Unit/Profiler/MessageQueueCollectorTest.php
@@ -2,16 +2,19 @@
namespace Enqueue\Bundle\Tests\Unit\Profiler;
+use DMS\PHPUnitExtensions\ArraySubset\Assert;
use Enqueue\Bundle\Profiler\MessageQueueCollector;
use Enqueue\Client\MessagePriority;
-use Enqueue\Client\MessageProducerInterface;
-use Enqueue\Client\TraceableMessageProducer;
+use Enqueue\Client\ProducerInterface;
+use Enqueue\Client\TraceableProducer;
use Enqueue\Test\ClassExtensionTrait;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
-class MessageQueueCollectorTest extends \PHPUnit_Framework_TestCase
+class MessageQueueCollectorTest extends TestCase
{
use ClassExtensionTrait;
@@ -20,86 +23,130 @@ public function testShouldExtendDataCollectorClass()
$this->assertClassExtends(DataCollector::class, MessageQueueCollector::class);
}
- public function testCouldBeConstructedWithMessageProducerAsFirstArgument()
- {
- new MessageQueueCollector($this->createMessageProducerMock());
- }
-
public function testShouldReturnExpectedName()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
+ $collector = new MessageQueueCollector();
$this->assertEquals('enqueue.message_queue', $collector->getName());
}
- public function testShouldReturnEmptySentMessageArrayIfNotTraceableMessageProducer()
+ public function testShouldReturnEmptySentMessageArrayIfNotTraceableProducer()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
+ $collector = new MessageQueueCollector();
+ $collector->addProducer('default', $this->createProducerMock());
$collector->collect(new Request(), new Response());
$this->assertSame([], $collector->getSentMessages());
}
- public function testShouldReturnSentMessageArrayTakenFromTraceableMessageProducer()
+ public function testShouldReturnSentMessageArrayTakenFromTraceableProducers()
{
- $producerMock = $this->createTraceableMessageProducerMock();
- $producerMock
- ->expects($this->once())
- ->method('getTraces')
- ->willReturn([['foo'], ['bar']]);
+ $producer1 = new TraceableProducer($this->createProducerMock());
+ $producer1->sendEvent('fooTopic1', 'fooMessage');
+ $producer1->sendCommand('barCommand1', 'barMessage');
- $collector = new MessageQueueCollector($producerMock);
+ $producer2 = new TraceableProducer($this->createProducerMock());
+ $producer2->sendEvent('fooTopic2', 'fooMessage');
+
+ $collector = new MessageQueueCollector();
+ $collector->addProducer('foo', $producer1);
+ $collector->addProducer('bar', $producer2);
$collector->collect(new Request(), new Response());
- $this->assertSame([['foo'], ['bar']], $collector->getSentMessages());
+ Assert::assertArraySubset(
+ [
+ 'foo' => [
+ [
+ 'topic' => 'fooTopic1',
+ 'command' => null,
+ 'body' => 'fooMessage',
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ [
+ 'topic' => null,
+ 'command' => 'barCommand1',
+ 'body' => 'barMessage',
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ],
+ 'bar' => [
+ [
+ 'topic' => 'fooTopic2',
+ 'command' => null,
+ 'body' => 'fooMessage',
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ],
+ ],
+ $collector->getSentMessages()
+ );
}
public function testShouldPrettyPrintKnownPriority()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
+ $collector = new MessageQueueCollector();
$this->assertEquals('normal', $collector->prettyPrintPriority(MessagePriority::NORMAL));
}
public function testShouldPrettyPrintUnknownPriority()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
+ $collector = new MessageQueueCollector();
$this->assertEquals('unknownPriority', $collector->prettyPrintPriority('unknownPriority'));
}
- public function testShouldPrettyPrintScalarMessage()
+ public function testShouldEnsureStringKeepStringSame()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
+ $collector = new MessageQueueCollector();
- $this->assertEquals('foo', $collector->prettyPrintMessage('foo'));
- $this->assertEquals('<p>', $collector->prettyPrintMessage(''));
+ $this->assertEquals('foo', $collector->ensureString('foo'));
+ $this->assertEquals('bar baz', $collector->ensureString('bar baz'));
}
- public function testShouldPrettyPrintArrayMessage()
+ public function testShouldEnsureStringEncodeArrayToJson()
{
- $collector = new MessageQueueCollector($this->createMessageProducerMock());
-
- $expected = "[\n "foo",\n "bar"\n]";
+ $collector = new MessageQueueCollector();
- $this->assertEquals($expected, $collector->prettyPrintMessage(['foo', 'bar']));
+ $this->assertEquals('["foo","bar"]', $collector->ensureString(['foo', 'bar']));
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|MessageProducerInterface
+ * @return MockObject|ProducerInterface
*/
- protected function createMessageProducerMock()
+ protected function createProducerMock()
{
- return $this->createMock(MessageProducerInterface::class);
+ return $this->createMock(ProducerInterface::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|TraceableMessageProducer
+ * @return MockObject|TraceableProducer
*/
- protected function createTraceableMessageProducerMock()
+ protected function createTraceableProducerMock()
{
- return $this->createMock(TraceableMessageProducer::class);
+ return $this->createMock(TraceableProducer::class);
}
}
diff --git a/pkg/enqueue-bundle/Tests/fix_composer_json.php b/pkg/enqueue-bundle/Tests/fix_composer_json.php
new file mode 100644
index 000000000..5c80237ea
--- /dev/null
+++ b/pkg/enqueue-bundle/Tests/fix_composer_json.php
@@ -0,0 +1,10 @@
+=5.6",
- "symfony/framework-bundle": "^2.8|^3",
- "enqueue/enqueue": "^0.2"
+ "php": "^8.1",
+ "symfony/framework-bundle": "^6.2|^7.0",
+ "queue-interop/amqp-interop": "^0.8.2",
+ "queue-interop/queue-interop": "^0.8",
+ "enqueue/enqueue": "^0.10",
+ "enqueue/null": "^0.10"
+ },
+ "support": {
+ "email": "opensource@forma-pro.com",
+ "issues": "https://github.com/php-enqueue/enqueue-dev/issues",
+ "forum": "https://gitter.im/php-enqueue/Lobby",
+ "source": "https://github.com/php-enqueue/enqueue-dev",
+ "docs": "https://github.com/php-enqueue/enqueue-dev/blob/master/docs/index.md"
},
"require-dev": {
- "phpunit/phpunit": "~5.5",
- "enqueue/stomp": "^0.2",
- "enqueue/amqp-ext": "^0.2",
- "enqueue/job-queue": "^0.2",
- "enqueue/test": "^0.2",
- "doctrine/doctrine-bundle": "~1.2",
- "symfony/monolog-bundle": "^2.8|^3",
- "symfony/browser-kit": "^2.8|^3",
- "symfony/expression-language": "^2.8|^3"
+ "phpunit/phpunit": "^9.5",
+ "enqueue/stomp": "0.10.x-dev",
+ "enqueue/amqp-ext": "0.10.x-dev",
+ "enqueue/amqp-lib": "0.10.x-dev",
+ "enqueue/amqp-bunny": "0.10.x-dev",
+ "enqueue/job-queue": "0.10.x-dev",
+ "enqueue/fs": "0.10.x-dev",
+ "enqueue/redis": "0.10.x-dev",
+ "enqueue/dbal": "0.10.x-dev",
+ "enqueue/sqs": "0.10.x-dev",
+ "enqueue/gps": "0.10.x-dev",
+ "enqueue/test": "0.10.x-dev",
+ "enqueue/async-event-dispatcher": "0.10.x-dev",
+ "enqueue/async-command": "0.10.x-dev",
+ "php-amqplib/php-amqplib": "^3.0",
+ "doctrine/doctrine-bundle": "^2.3.2",
+ "doctrine/mongodb-odm-bundle": "^3.5|^4.3|^5.0",
+ "alcaeus/mongo-php-adapter": "^1.0",
+ "symfony/browser-kit": "^6.2|^7.0",
+ "symfony/expression-language": "^6.2|^7.0",
+ "symfony/validator": "^6.2|^7.0",
+ "symfony/yaml": "^6.2|^7.0"
},
"suggest": {
- "enqueue/amqp-ext": "Message queue AMQP transport",
- "enqueue/stomp": "Message queue STOMP transport"
+ "enqueue/async-command": "If want to run Symfony command via message queue",
+ "enqueue/async-event-dispatcher": "If you want dispatch and process events asynchronously"
},
"autoload": {
"psr-4": { "Enqueue\\Bundle\\": "" },
@@ -36,10 +54,14 @@
"/Tests/"
]
},
- "minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-master": "0.2.x-dev"
+ "dev-master": "0.10.x-dev"
}
+ },
+ "config": {
+ "allow-plugins": {
+ "php-http/discovery": true
+ }
}
}
diff --git a/pkg/enqueue-bundle/phpunit.xml.dist b/pkg/enqueue-bundle/phpunit.xml.dist
index ac0770ea9..974d2c3f5 100644
--- a/pkg/enqueue-bundle/phpunit.xml.dist
+++ b/pkg/enqueue-bundle/phpunit.xml.dist
@@ -1,16 +1,11 @@
-
+
diff --git a/pkg/enqueue/.gitattributes b/pkg/enqueue/.gitattributes
new file mode 100644
index 000000000..bdf2dcb14
--- /dev/null
+++ b/pkg/enqueue/.gitattributes
@@ -0,0 +1,5 @@
+/Tests export-ignore
+.gitattributes export-ignore
+.gitignore export-ignore
+.travis.yml export-ignore
+phpunit.xml.dist export-ignore
diff --git a/pkg/enqueue/.github/workflows/ci.yml b/pkg/enqueue/.github/workflows/ci.yml
new file mode 100644
index 000000000..28a46e908
--- /dev/null
+++ b/pkg/enqueue/.github/workflows/ci.yml
@@ -0,0 +1,30 @@
+name: CI
+on:
+ pull_request:
+ push:
+ branches:
+ - master
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ php: ['7.4', '8.0', '8.1', '8.2']
+
+ name: PHP ${{ matrix.php }} tests
+
+ steps:
+ - uses: actions/checkout@v2
+
+ - uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ extensions: mongodb
+
+ - run: php Tests/fix_composer_json.php
+
+ - uses: "ramsey/composer-install@v1"
+
+ - run: vendor/bin/phpunit --exclude-group=functional
diff --git a/pkg/enqueue/.travis.yml b/pkg/enqueue/.travis.yml
deleted file mode 100644
index 42374ddc7..000000000
--- a/pkg/enqueue/.travis.yml
+++ /dev/null
@@ -1,21 +0,0 @@
-sudo: false
-
-git:
- depth: 1
-
-language: php
-
-php:
- - '5.6'
- - '7.0'
-
-cache:
- directories:
- - $HOME/.composer/cache
-
-install:
- - composer self-update
- - composer install --prefer-source
-
-script:
- - vendor/bin/phpunit --exclude-group=functional
diff --git a/pkg/enqueue/ArrayProcessorRegistry.php b/pkg/enqueue/ArrayProcessorRegistry.php
new file mode 100644
index 000000000..592908c51
--- /dev/null
+++ b/pkg/enqueue/ArrayProcessorRegistry.php
@@ -0,0 +1,38 @@
+processors = [];
+ array_walk($processors, function (Processor $processor, string $key) {
+ $this->processors[$key] = $processor;
+ });
+ }
+
+ public function add(string $name, Processor $processor): void
+ {
+ $this->processors[$name] = $processor;
+ }
+
+ public function get(string $processorName): Processor
+ {
+ if (false == isset($this->processors[$processorName])) {
+ throw new \LogicException(sprintf('Processor was not found. processorName: "%s"', $processorName));
+ }
+
+ return $this->processors[$processorName];
+ }
+}
diff --git a/pkg/enqueue/Client/ArrayProcessorRegistry.php b/pkg/enqueue/Client/ArrayProcessorRegistry.php
deleted file mode 100644
index ab8278893..000000000
--- a/pkg/enqueue/Client/ArrayProcessorRegistry.php
+++ /dev/null
@@ -1,42 +0,0 @@
-processors = $processors;
- }
-
- /**
- * @param string $name
- * @param Processor $processor
- */
- public function add($name, Processor $processor)
- {
- $this->processors[$name] = $processor;
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($processorName)
- {
- if (false == isset($this->processors[$processorName])) {
- throw new \LogicException(sprintf('Processor was not found. processorName: "%s"', $processorName));
- }
-
- return $this->processors[$processorName];
- }
-}
diff --git a/pkg/enqueue/Client/ChainExtension.php b/pkg/enqueue/Client/ChainExtension.php
new file mode 100644
index 000000000..655b75f6a
--- /dev/null
+++ b/pkg/enqueue/Client/ChainExtension.php
@@ -0,0 +1,102 @@
+preSendEventExtensions = [];
+ $this->preSendCommandExtensions = [];
+ $this->driverPreSendExtensions = [];
+ $this->postSendExtensions = [];
+
+ array_walk($extensions, function ($extension) {
+ if ($extension instanceof ExtensionInterface) {
+ $this->preSendEventExtensions[] = $extension;
+ $this->preSendCommandExtensions[] = $extension;
+ $this->driverPreSendExtensions[] = $extension;
+ $this->postSendExtensions[] = $extension;
+
+ return;
+ }
+
+ $extensionValid = false;
+ if ($extension instanceof PreSendEventExtensionInterface) {
+ $this->preSendEventExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PreSendCommandExtensionInterface) {
+ $this->preSendCommandExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof DriverPreSendExtensionInterface) {
+ $this->driverPreSendExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PostSendExtensionInterface) {
+ $this->postSendExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if (false == $extensionValid) {
+ throw new \LogicException(sprintf('Invalid extension given %s', $extension::class));
+ }
+ });
+ }
+
+ public function onPreSendEvent(PreSend $context): void
+ {
+ foreach ($this->preSendEventExtensions as $extension) {
+ $extension->onPreSendEvent($context);
+ }
+ }
+
+ public function onPreSendCommand(PreSend $context): void
+ {
+ foreach ($this->preSendCommandExtensions as $extension) {
+ $extension->onPreSendCommand($context);
+ }
+ }
+
+ public function onDriverPreSend(DriverPreSend $context): void
+ {
+ foreach ($this->driverPreSendExtensions as $extension) {
+ $extension->onDriverPreSend($context);
+ }
+ }
+
+ public function onPostSend(PostSend $context): void
+ {
+ foreach ($this->postSendExtensions as $extension) {
+ $extension->onPostSend($context);
+ }
+ }
+}
diff --git a/pkg/enqueue/Client/CommandSubscriberInterface.php b/pkg/enqueue/Client/CommandSubscriberInterface.php
new file mode 100644
index 000000000..d7b06daaf
--- /dev/null
+++ b/pkg/enqueue/Client/CommandSubscriberInterface.php
@@ -0,0 +1,60 @@
+ 'aSubscribedCommand',
+ * 'processor' => 'aProcessorName',
+ * 'queue' => 'a_client_queue_name',
+ * 'prefix_queue' => true,
+ * 'exclusive' => true,
+ * ]
+ *
+ * or
+ *
+ * [
+ * [
+ * 'command' => 'aSubscribedCommand',
+ * 'processor' => 'aProcessorName',
+ * 'queue' => 'a_client_queue_name',
+ * 'prefix_queue' => true,
+ * 'exclusive' => true,
+ * ],
+ * [
+ * 'command' => 'aSubscribedCommand',
+ * 'processor' => 'aProcessorName',
+ * 'queue' => 'a_client_queue_name',
+ * 'prefix_queue' => true,
+ * 'exclusive' => true,
+ * ]
+ * ]
+ *
+ * queue, processor, prefix_queue, and exclusive are optional.
+ * It is possible to pass other options, they could be accessible on a route instance through options.
+ *
+ * Note: If you set "prefix_queue" to true then the "queue" is used as is and therefor the driver is not used to create a transport queue name.
+ *
+ * @return string|array
+ *
+ * @phpstan-return string|CommandConfig|array
+ */
+ public static function getSubscribedCommand();
+}
diff --git a/pkg/enqueue/Client/Config.php b/pkg/enqueue/Client/Config.php
index 66fc07596..8210dff68 100644
--- a/pkg/enqueue/Client/Config.php
+++ b/pkg/enqueue/Client/Config.php
@@ -4,10 +4,13 @@
class Config
{
- const PARAMETER_TOPIC_NAME = 'enqueue.topic_name';
- const PARAMETER_PROCESSOR_NAME = 'enqueue.processor_name';
- const PARAMETER_PROCESSOR_QUEUE_NAME = 'enqueue.processor_queue_name';
- const DEFAULT_PROCESSOR_QUEUE_NAME = 'default';
+ public const TOPIC = 'enqueue.topic';
+ public const COMMAND = 'enqueue.command';
+ public const PROCESSOR = 'enqueue.processor';
+ public const EXPIRE = 'enqueue.expire';
+ public const PRIORITY = 'enqueue.priority';
+ public const DELAY = 'enqueue.delay';
+ public const CONTENT_TYPE = 'enqueue.content_type';
/**
* @var string
@@ -17,27 +20,32 @@ class Config
/**
* @var string
*/
- private $appName;
+ private $separator;
/**
* @var string
*/
- private $routerTopicName;
+ private $app;
/**
* @var string
*/
- private $routerQueueName;
+ private $routerTopic;
/**
* @var string
*/
- private $defaultProcessorQueueName;
+ private $routerQueue;
/**
* @var string
*/
- private $routerProcessorName;
+ private $defaultQueue;
+
+ /**
+ * @var string
+ */
+ private $routerProcessor;
/**
* @var array
@@ -45,113 +53,126 @@ class Config
private $transportConfig;
/**
- * @param string $prefix
- * @param string $appName
- * @param string $routerTopicName
- * @param string $routerQueueName
- * @param string $defaultProcessorQueueName
- * @param string $routerProcessorName
- * @param array $transportConfig
+ * @var array
*/
- public function __construct($prefix, $appName, $routerTopicName, $routerQueueName, $defaultProcessorQueueName, $routerProcessorName, array $transportConfig = [])
- {
- $this->prefix = $prefix;
- $this->appName = $appName;
- $this->routerTopicName = $routerTopicName;
- $this->routerQueueName = $routerQueueName;
- $this->defaultProcessorQueueName = $defaultProcessorQueueName;
- $this->routerProcessorName = $routerProcessorName;
+ private $driverConfig;
+
+ public function __construct(
+ string $prefix,
+ string $separator,
+ string $app,
+ string $routerTopic,
+ string $routerQueue,
+ string $defaultQueue,
+ string $routerProcessor,
+ array $transportConfig,
+ array $driverConfig,
+ ) {
+ $this->prefix = trim($prefix);
+ $this->app = trim($app);
+
+ $this->routerTopic = trim($routerTopic);
+ if (empty($this->routerTopic)) {
+ throw new \InvalidArgumentException('Router topic is empty.');
+ }
+
+ $this->routerQueue = trim($routerQueue);
+ if (empty($this->routerQueue)) {
+ throw new \InvalidArgumentException('Router queue is empty.');
+ }
+
+ $this->defaultQueue = trim($defaultQueue);
+ if (empty($this->defaultQueue)) {
+ throw new \InvalidArgumentException('Default processor queue name is empty.');
+ }
+
+ $this->routerProcessor = trim($routerProcessor);
+ if (empty($this->routerProcessor)) {
+ throw new \InvalidArgumentException('Router processor name is empty.');
+ }
+
$this->transportConfig = $transportConfig;
+ $this->driverConfig = $driverConfig;
+
+ $this->separator = $separator;
}
- /**
- * @return string
- */
- public function getRouterTopicName()
+ public function getPrefix(): string
{
- return $this->routerTopicName;
+ return $this->prefix;
}
- /**
- * @return string
- */
- public function getRouterQueueName()
+ public function getSeparator(): string
{
- return $this->routerQueueName;
+ return $this->separator;
}
- /**
- * @return string
- */
- public function getDefaultProcessorQueueName()
+ public function getApp(): string
{
- return $this->defaultProcessorQueueName;
+ return $this->app;
}
- /**
- * @return string
- */
- public function getRouterProcessorName()
+ public function getRouterTopic(): string
{
- return $this->routerProcessorName;
+ return $this->routerTopic;
}
- /**
- * @param string $name
- *
- * @return string
- */
- public function createTransportRouterTopicName($name)
+ public function getRouterQueue(): string
{
- return trim(strtolower(trim($this->prefix).'.'.trim($name)), '.');
+ return $this->routerQueue;
}
- /**
- * @param string $name
- *
- * @return string
- */
- public function createTransportQueueName($name)
+ public function getDefaultQueue(): string
{
- return trim(strtolower(trim($this->prefix).'.'.trim($this->appName).'.'.trim($name)), '.');
+ return $this->defaultQueue;
}
- /**
- * @param string $name
- * @param mixed|null $default
- *
- * @return array
- */
- public function getTransportOption($name, $default = null)
+ public function getRouterProcessor(): string
+ {
+ return $this->routerProcessor;
+ }
+
+ public function getTransportOption(string $name, $default = null)
{
return array_key_exists($name, $this->transportConfig) ? $this->transportConfig[$name] : $default;
}
- /**
- * @param string|null $prefix
- * @param string|null $appName
- * @param string|null $routerTopicName
- * @param string|null $routerQueueName
- * @param string|null $defaultProcessorQueueName
- * @param string|null $routerProcessorName
- *
- * @return static
- */
+ public function getTransportOptions(): array
+ {
+ return $this->transportConfig;
+ }
+
+ public function getDriverOption(string $name, $default = null)
+ {
+ return array_key_exists($name, $this->driverConfig) ? $this->driverConfig[$name] : $default;
+ }
+
+ public function getDriverOptions(): array
+ {
+ return $this->driverConfig;
+ }
+
public static function create(
- $prefix = null,
- $appName = null,
- $routerTopicName = null,
- $routerQueueName = null,
- $defaultProcessorQueueName = null,
- $routerProcessorName = null
- ) {
- return new static(
+ ?string $prefix = null,
+ ?string $separator = null,
+ ?string $app = null,
+ ?string $routerTopic = null,
+ ?string $routerQueue = null,
+ ?string $defaultQueue = null,
+ ?string $routerProcessor = null,
+ array $transportConfig = [],
+ array $driverConfig = [],
+ ): self {
+ return new self(
$prefix ?: '',
- $appName ?: '',
- $routerTopicName ?: 'router',
- $routerQueueName ?: 'default',
- $defaultProcessorQueueName ?: 'default',
- $routerProcessorName ?: 'router'
+ $separator ?: '.',
+ $app ?: '',
+ $routerTopic ?: 'router',
+ $routerQueue ?: 'default',
+ $defaultQueue ?: 'default',
+ $routerProcessor ?: 'router',
+ $transportConfig,
+ $driverConfig
);
}
}
diff --git a/pkg/enqueue/Client/ConsumptionExtension/DelayRedeliveredMessageExtension.php b/pkg/enqueue/Client/ConsumptionExtension/DelayRedeliveredMessageExtension.php
index 7f1b49d79..475e2cf5b 100644
--- a/pkg/enqueue/Client/ConsumptionExtension/DelayRedeliveredMessageExtension.php
+++ b/pkg/enqueue/Client/ConsumptionExtension/DelayRedeliveredMessageExtension.php
@@ -3,16 +3,13 @@
namespace Enqueue\Client\ConsumptionExtension;
use Enqueue\Client\DriverInterface;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\MessageReceivedExtensionInterface;
use Enqueue\Consumption\Result;
-class DelayRedeliveredMessageExtension implements ExtensionInterface
+class DelayRedeliveredMessageExtension implements MessageReceivedExtensionInterface
{
- use EmptyExtensionTrait;
-
- const PROPERTY_REDELIVER_COUNT = 'enqueue.redelivery_count';
+ public const PROPERTY_REDELIVER_COUNT = 'enqueue.redelivery_count';
/**
* @var DriverInterface
@@ -27,8 +24,7 @@ class DelayRedeliveredMessageExtension implements ExtensionInterface
private $delay;
/**
- * @param DriverInterface $driver
- * @param int $delay The number of seconds the message should be delayed
+ * @param int $delay The number of seconds the message should be delayed
*/
public function __construct(DriverInterface $driver, $delay)
{
@@ -36,15 +32,15 @@ public function __construct(DriverInterface $driver, $delay)
$this->delay = $delay;
}
- /**
- * {@inheritdoc}
- */
- public function onPreReceived(Context $context)
+ public function onMessageReceived(MessageReceived $context): void
{
- $message = $context->getPsrMessage();
+ $message = $context->getMessage();
if (false == $message->isRedelivered()) {
return;
}
+ if (false != $context->getResult()) {
+ return;
+ }
$delayedMessage = $this->driver->createClientMessage($message);
@@ -57,7 +53,7 @@ public function onPreReceived(Context $context)
$this->driver->sendToProcessor($delayedMessage);
$context->getLogger()->debug('[DelayRedeliveredMessageExtension] Send delayed message');
- $context->setResult(Result::REJECT);
+ $context->setResult(Result::reject('A new copy of the message was sent with a delay. The original message is rejected'));
$context->getLogger()->debug(
'[DelayRedeliveredMessageExtension] '.
'Reject redelivered original message by setting reject status to context.'
diff --git a/pkg/enqueue/Client/ConsumptionExtension/ExclusiveCommandExtension.php b/pkg/enqueue/Client/ConsumptionExtension/ExclusiveCommandExtension.php
new file mode 100644
index 000000000..7ab88ae0f
--- /dev/null
+++ b/pkg/enqueue/Client/ConsumptionExtension/ExclusiveCommandExtension.php
@@ -0,0 +1,77 @@
+driver = $driver;
+ }
+
+ public function onMessageReceived(MessageReceived $context): void
+ {
+ $message = $context->getMessage();
+ if ($message->getProperty(Config::TOPIC)) {
+ return;
+ }
+ if ($message->getProperty(Config::COMMAND)) {
+ return;
+ }
+ if ($message->getProperty(Config::PROCESSOR)) {
+ return;
+ }
+
+ if (null === $this->queueToRouteMap) {
+ $this->queueToRouteMap = $this->buildMap();
+ }
+
+ $queue = $context->getConsumer()->getQueue();
+ if (array_key_exists($queue->getQueueName(), $this->queueToRouteMap)) {
+ $context->getLogger()->debug('[ExclusiveCommandExtension] This is a exclusive command queue and client\'s properties are not set. Setting them');
+
+ $route = $this->queueToRouteMap[$queue->getQueueName()];
+ $message->setProperty(Config::PROCESSOR, $route->getProcessor());
+ $message->setProperty(Config::COMMAND, $route->getSource());
+ }
+ }
+
+ private function buildMap(): array
+ {
+ $map = [];
+ foreach ($this->driver->getRouteCollection()->all() as $route) {
+ if (false == $route->isCommand()) {
+ continue;
+ }
+
+ if (false == $route->isProcessorExclusive()) {
+ continue;
+ }
+
+ $queueName = $this->driver->createRouteQueue($route)->getQueueName();
+ if (array_key_exists($queueName, $map)) {
+ throw new \LogicException('The queue name has been already bound by another exclusive command processor');
+ }
+
+ $map[$queueName] = $route;
+ }
+
+ return $map;
+ }
+}
diff --git a/pkg/enqueue/Client/ConsumptionExtension/FlushSpoolProducerExtension.php b/pkg/enqueue/Client/ConsumptionExtension/FlushSpoolProducerExtension.php
new file mode 100644
index 000000000..6682cad8e
--- /dev/null
+++ b/pkg/enqueue/Client/ConsumptionExtension/FlushSpoolProducerExtension.php
@@ -0,0 +1,32 @@
+producer = $producer;
+ }
+
+ public function onPostMessageReceived(PostMessageReceived $context): void
+ {
+ $this->producer->flush();
+ }
+
+ public function onEnd(End $context): void
+ {
+ $this->producer->flush();
+ }
+}
diff --git a/pkg/enqueue/Client/ConsumptionExtension/LogExtension.php b/pkg/enqueue/Client/ConsumptionExtension/LogExtension.php
new file mode 100644
index 000000000..693be2035
--- /dev/null
+++ b/pkg/enqueue/Client/ConsumptionExtension/LogExtension.php
@@ -0,0 +1,69 @@
+getResult();
+ $message = $context->getMessage();
+
+ $logLevel = Result::REJECT == ((string) $result) ? LogLevel::ERROR : LogLevel::INFO;
+
+ if ($command = $message->getProperty(Config::COMMAND)) {
+ $reason = '';
+ $logMessage = "[client] Processed {command}\t{body}\t{result}";
+ if ($result instanceof Result && $result->getReason()) {
+ $reason = $result->getReason();
+
+ $logMessage .= ' {reason}';
+ }
+
+ $context->getLogger()->log($logLevel, $logMessage, [
+ 'result' => str_replace('enqueue.', '', $result),
+ 'reason' => $reason,
+ 'command' => $command,
+ 'queueName' => $context->getConsumer()->getQueue()->getQueueName(),
+ 'body' => Stringify::that($message->getBody()),
+ 'properties' => Stringify::that($message->getProperties()),
+ 'headers' => Stringify::that($message->getHeaders()),
+ ]);
+
+ return;
+ }
+
+ $topic = $message->getProperty(Config::TOPIC);
+ $processor = $message->getProperty(Config::PROCESSOR);
+ if ($topic && $processor) {
+ $reason = '';
+ $logMessage = "[client] Processed {topic} -> {processor}\t{body}\t{result}";
+ if ($result instanceof Result && $result->getReason()) {
+ $reason = $result->getReason();
+
+ $logMessage .= ' {reason}';
+ }
+
+ $context->getLogger()->log($logLevel, $logMessage, [
+ 'result' => str_replace('enqueue.', '', $result),
+ 'reason' => $reason,
+ 'topic' => $topic,
+ 'processor' => $processor,
+ 'queueName' => $context->getConsumer()->getQueue()->getQueueName(),
+ 'body' => Stringify::that($message->getBody()),
+ 'properties' => Stringify::that($message->getProperties()),
+ 'headers' => Stringify::that($message->getHeaders()),
+ ]);
+
+ return;
+ }
+
+ parent::onPostMessageReceived($context);
+ }
+}
diff --git a/pkg/enqueue/Client/ConsumptionExtension/SetRouterPropertiesExtension.php b/pkg/enqueue/Client/ConsumptionExtension/SetRouterPropertiesExtension.php
index 565d887bf..0d2278349 100644
--- a/pkg/enqueue/Client/ConsumptionExtension/SetRouterPropertiesExtension.php
+++ b/pkg/enqueue/Client/ConsumptionExtension/SetRouterPropertiesExtension.php
@@ -4,39 +4,43 @@
use Enqueue\Client\Config;
use Enqueue\Client\DriverInterface;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\MessageReceivedExtensionInterface;
-class SetRouterPropertiesExtension implements ExtensionInterface
+class SetRouterPropertiesExtension implements MessageReceivedExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var DriverInterface
*/
private $driver;
- /**
- * @param DriverInterface $driver
- */
public function __construct(DriverInterface $driver)
{
$this->driver = $driver;
}
- /**
- * {@inheritdoc}
- */
- public function onPreReceived(Context $context)
+ public function onMessageReceived(MessageReceived $context): void
{
- $message = $context->getPsrMessage();
- if ($message->getProperty(Config::PARAMETER_PROCESSOR_NAME)) {
+ $message = $context->getMessage();
+ if (false == $message->getProperty(Config::TOPIC)) {
+ return;
+ }
+ if ($message->getProperty(Config::PROCESSOR)) {
+ return;
+ }
+
+ $config = $this->driver->getConfig();
+ $queue = $this->driver->createQueue($config->getRouterQueue());
+ if ($context->getConsumer()->getQueue()->getQueueName() != $queue->getQueueName()) {
return;
}
// RouterProcessor is our default message processor when that header is not set
- $message->setProperty(Config::PARAMETER_PROCESSOR_NAME, $this->driver->getConfig()->getRouterProcessorName());
- $message->setProperty(Config::PARAMETER_PROCESSOR_QUEUE_NAME, $this->driver->getConfig()->getRouterQueueName());
+ $message->setProperty(Config::PROCESSOR, $config->getRouterProcessor());
+
+ $context->getLogger()->debug(
+ '[SetRouterPropertiesExtension] '.
+ sprintf('Set router processor "%s"', $config->getRouterProcessor())
+ );
}
}
diff --git a/pkg/enqueue/Client/ConsumptionExtension/SetupBrokerExtension.php b/pkg/enqueue/Client/ConsumptionExtension/SetupBrokerExtension.php
index 8b6aecbc1..44d610fb9 100644
--- a/pkg/enqueue/Client/ConsumptionExtension/SetupBrokerExtension.php
+++ b/pkg/enqueue/Client/ConsumptionExtension/SetupBrokerExtension.php
@@ -3,14 +3,11 @@
namespace Enqueue\Client\ConsumptionExtension;
use Enqueue\Client\DriverInterface;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\Start;
+use Enqueue\Consumption\StartExtensionInterface;
-class SetupBrokerExtension implements ExtensionInterface
+class SetupBrokerExtension implements StartExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var DriverInterface
*/
@@ -21,19 +18,13 @@ class SetupBrokerExtension implements ExtensionInterface
*/
private $isDone;
- /**
- * @param DriverInterface $driver
- */
public function __construct(DriverInterface $driver)
{
$this->driver = $driver;
$this->isDone = false;
}
- /**
- * {@inheritdoc}
- */
- public function onStart(Context $context)
+ public function onStart(Start $context): void
{
if (false == $this->isDone) {
$this->isDone = true;
diff --git a/pkg/enqueue/Client/DelegateProcessor.php b/pkg/enqueue/Client/DelegateProcessor.php
index 910a9e504..7582c52dc 100644
--- a/pkg/enqueue/Client/DelegateProcessor.php
+++ b/pkg/enqueue/Client/DelegateProcessor.php
@@ -2,9 +2,10 @@
namespace Enqueue\Client;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Message as PsrMessage;
-use Enqueue\Psr\Processor;
+use Enqueue\ProcessorRegistryInterface;
+use Interop\Queue\Context;
+use Interop\Queue\Message as InteropMessage;
+use Interop\Queue\Processor;
class DelegateProcessor implements Processor
{
@@ -13,25 +14,19 @@ class DelegateProcessor implements Processor
*/
private $registry;
- /**
- * @param ProcessorRegistryInterface $registry
- */
public function __construct(ProcessorRegistryInterface $registry)
{
$this->registry = $registry;
}
/**
- * {@inheritdoc}
+ * @return string|object
*/
- public function process(PsrMessage $message, Context $context)
+ public function process(InteropMessage $message, Context $context)
{
- $processorName = $message->getProperty(Config::PARAMETER_PROCESSOR_NAME);
+ $processorName = $message->getProperty(Config::PROCESSOR);
if (false == $processorName) {
- throw new \LogicException(sprintf(
- 'Got message without required parameter: "%s"',
- Config::PARAMETER_PROCESSOR_NAME
- ));
+ throw new \LogicException(sprintf('Got message without required parameter: "%s"', Config::PROCESSOR));
}
return $this->registry->get($processorName)->process($message, $context);
diff --git a/pkg/enqueue/Client/Driver/AmqpDriver.php b/pkg/enqueue/Client/Driver/AmqpDriver.php
new file mode 100644
index 000000000..1def3fb23
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/AmqpDriver.php
@@ -0,0 +1,132 @@
+setDeliveryMode(AmqpMessage::DELIVERY_MODE_PERSISTENT);
+ $transportMessage->setContentType($clientMessage->getContentType());
+
+ if ($clientMessage->getExpire()) {
+ $transportMessage->setExpiration($clientMessage->getExpire() * 1000);
+ }
+
+ $priorityMap = $this->getPriorityMap();
+ if ($priority = $clientMessage->getPriority()) {
+ if (false == array_key_exists($priority, $priorityMap)) {
+ throw new \InvalidArgumentException(sprintf('Cant convert client priority "%s" to transport one. Could be one of "%s"', $priority, implode('", "', array_keys($priorityMap))));
+ }
+
+ $transportMessage->setPriority($priorityMap[$priority]);
+ }
+
+ return $transportMessage;
+ }
+
+ public function setupBroker(?LoggerInterface $logger = null): void
+ {
+ $logger = $logger ?: new NullLogger();
+ $log = function ($text, ...$args) use ($logger) {
+ $logger->debug(sprintf('[AmqpDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerTopic = $this->createRouterTopic();
+ $log('Declare router exchange: %s', $routerTopic->getTopicName());
+ $this->getContext()->declareTopic($routerTopic);
+
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+ $log('Declare router queue: %s', $routerQueue->getQueueName());
+ $this->getContext()->declareQueue($routerQueue);
+
+ $log('Bind router queue to exchange: %s -> %s', $routerQueue->getQueueName(), $routerTopic->getTopicName());
+ $this->getContext()->bind(new AmqpBind($routerTopic, $routerQueue, $routerQueue->getQueueName()));
+
+ // setup queues
+ $declaredQueues = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ /** @var AmqpQueue $queue */
+ $queue = $this->createRouteQueue($route);
+ if (array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ continue;
+ }
+
+ $log('Declare processor queue: %s', $queue->getQueueName());
+ $this->getContext()->declareQueue($queue);
+
+ $declaredQueues[$queue->getQueueName()] = true;
+ }
+ }
+
+ /**
+ * @return AmqpTopic
+ */
+ protected function createRouterTopic(): Destination
+ {
+ $topic = $this->doCreateTopic(
+ $this->createTransportRouterTopicName($this->getConfig()->getRouterTopic(), true)
+ );
+ $topic->setType(AmqpTopic::TYPE_FANOUT);
+ $topic->addFlag(AmqpTopic::FLAG_DURABLE);
+
+ return $topic;
+ }
+
+ /**
+ * @return AmqpQueue
+ */
+ protected function doCreateQueue(string $transportQueueName): InteropQueue
+ {
+ /** @var AmqpQueue $queue */
+ $queue = parent::doCreateQueue($transportQueueName);
+ $queue->addFlag(AmqpQueue::FLAG_DURABLE);
+
+ return $queue;
+ }
+
+ /**
+ * @param AmqpProducer $producer
+ * @param AmqpTopic $topic
+ * @param AmqpMessage $transportMessage
+ */
+ protected function doSendToRouter(InteropProducer $producer, Destination $topic, InteropMessage $transportMessage): void
+ {
+ // We should not handle priority, expiration, and delay at this stage.
+ // The router will take care of it while re-sending the message to the final destinations.
+ $transportMessage->setPriority(null);
+ $transportMessage->setExpiration(null);
+
+ $producer->send($topic, $transportMessage);
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/DbalDriver.php b/pkg/enqueue/Client/Driver/DbalDriver.php
new file mode 100644
index 000000000..34875eff7
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/DbalDriver.php
@@ -0,0 +1,29 @@
+debug(sprintf('[DbalDriver] '.$text, ...$args));
+ };
+
+ $log('Creating database table: "%s"', $this->getContext()->getTableName());
+ $this->getContext()->createDataBaseTable();
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/FsDriver.php b/pkg/enqueue/Client/Driver/FsDriver.php
new file mode 100644
index 000000000..f578b172d
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/FsDriver.php
@@ -0,0 +1,49 @@
+debug(sprintf('[FsDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+
+ $log('Declare router queue "%s" file: %s', $routerQueue->getQueueName(), $routerQueue->getFileInfo());
+ $this->getContext()->declareDestination($routerQueue);
+
+ // setup queues
+ $declaredQueues = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ /** @var FsDestination $queue */
+ $queue = $this->createRouteQueue($route);
+ if (array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ continue;
+ }
+
+ $log('Declare processor queue "%s" file: %s', $queue->getQueueName(), $queue->getFileInfo());
+ $this->getContext()->declareDestination($queue);
+
+ $declaredQueues[$queue->getQueueName()] = true;
+ }
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/GenericDriver.php b/pkg/enqueue/Client/Driver/GenericDriver.php
new file mode 100644
index 000000000..d509677df
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/GenericDriver.php
@@ -0,0 +1,278 @@
+context = $context;
+ $this->config = $config;
+ $this->routeCollection = $routeCollection;
+ }
+
+ public function sendToRouter(Message $message): DriverSendResult
+ {
+ if ($message->getProperty(Config::COMMAND)) {
+ throw new \LogicException('Command must not be send to router but go directly to its processor.');
+ }
+ if (false == $message->getProperty(Config::TOPIC)) {
+ throw new \LogicException('Topic name parameter is required but is not set');
+ }
+
+ $topic = $this->createRouterTopic();
+ $transportMessage = $this->createTransportMessage($message);
+ $producer = $this->getContext()->createProducer();
+
+ $this->doSendToRouter($producer, $topic, $transportMessage);
+
+ return new DriverSendResult($topic, $transportMessage);
+ }
+
+ public function sendToProcessor(Message $message): DriverSendResult
+ {
+ $topic = $message->getProperty(Config::TOPIC);
+ $command = $message->getProperty(Config::COMMAND);
+
+ /** @var InteropQueue $queue */
+ $queue = null;
+ $routerProcessor = $this->config->getRouterProcessor();
+ $processor = $message->getProperty(Config::PROCESSOR);
+ if ($topic && $processor && $processor !== $routerProcessor) {
+ $route = $this->routeCollection->topicAndProcessor($topic, $processor);
+ if (false == $route) {
+ throw new \LogicException(sprintf('There is no route for topic "%s" and processor "%s"', $topic, $processor));
+ }
+
+ $message->setProperty(Config::PROCESSOR, $route->getProcessor());
+ $queue = $this->createRouteQueue($route);
+ } elseif ($topic && (false == $processor || $processor === $routerProcessor)) {
+ $message->setProperty(Config::PROCESSOR, $routerProcessor);
+
+ $queue = $this->createQueue($this->config->getRouterQueue());
+ } elseif ($command) {
+ $route = $this->routeCollection->command($command);
+ if (false == $route) {
+ throw new \LogicException(sprintf('There is no route for command "%s".', $command));
+ }
+
+ $message->setProperty(Config::PROCESSOR, $route->getProcessor());
+ $queue = $this->createRouteQueue($route);
+ } else {
+ throw new \LogicException('Either topic or command parameter must be set.');
+ }
+
+ $transportMessage = $this->createTransportMessage($message);
+
+ $producer = $this->context->createProducer();
+
+ if (null !== $delay = $transportMessage->getProperty(Config::DELAY)) {
+ $producer->setDeliveryDelay($delay * 1000);
+ }
+
+ if (null !== $expire = $transportMessage->getProperty(Config::EXPIRE)) {
+ $producer->setTimeToLive($expire * 1000);
+ }
+
+ if (null !== $priority = $transportMessage->getProperty(Config::PRIORITY)) {
+ $priorityMap = $this->getPriorityMap();
+
+ $producer->setPriority($priorityMap[$priority]);
+ }
+
+ $this->doSendToProcessor($producer, $queue, $transportMessage);
+
+ return new DriverSendResult($queue, $transportMessage);
+ }
+
+ public function setupBroker(?LoggerInterface $logger = null): void
+ {
+ }
+
+ public function createQueue(string $clientQueueName, bool $prefix = true): InteropQueue
+ {
+ $transportName = $this->createTransportQueueName($clientQueueName, $prefix);
+
+ return $this->doCreateQueue($transportName);
+ }
+
+ public function createRouteQueue(Route $route): InteropQueue
+ {
+ $transportName = $this->createTransportQueueName(
+ $route->getQueue() ?: $this->config->getDefaultQueue(),
+ $route->isPrefixQueue()
+ );
+
+ return $this->doCreateQueue($transportName);
+ }
+
+ public function createTransportMessage(Message $clientMessage): InteropMessage
+ {
+ $headers = $clientMessage->getHeaders();
+ $properties = $clientMessage->getProperties();
+
+ $transportMessage = $this->context->createMessage();
+ $transportMessage->setBody($clientMessage->getBody());
+ $transportMessage->setHeaders($headers);
+ $transportMessage->setProperties($properties);
+ $transportMessage->setMessageId($clientMessage->getMessageId());
+ $transportMessage->setTimestamp($clientMessage->getTimestamp());
+ $transportMessage->setReplyTo($clientMessage->getReplyTo());
+ $transportMessage->setCorrelationId($clientMessage->getCorrelationId());
+
+ if ($contentType = $clientMessage->getContentType()) {
+ $transportMessage->setProperty(Config::CONTENT_TYPE, $contentType);
+ }
+
+ if ($priority = $clientMessage->getPriority()) {
+ $transportMessage->setProperty(Config::PRIORITY, $priority);
+ }
+
+ if ($expire = $clientMessage->getExpire()) {
+ $transportMessage->setProperty(Config::EXPIRE, $expire);
+ }
+
+ if ($delay = $clientMessage->getDelay()) {
+ $transportMessage->setProperty(Config::DELAY, $delay);
+ }
+
+ return $transportMessage;
+ }
+
+ public function createClientMessage(InteropMessage $transportMessage): Message
+ {
+ $clientMessage = new Message();
+
+ $clientMessage->setBody($transportMessage->getBody());
+ $clientMessage->setHeaders($transportMessage->getHeaders());
+ $clientMessage->setProperties($transportMessage->getProperties());
+ $clientMessage->setMessageId($transportMessage->getMessageId());
+ $clientMessage->setTimestamp($transportMessage->getTimestamp());
+ $clientMessage->setReplyTo($transportMessage->getReplyTo());
+ $clientMessage->setCorrelationId($transportMessage->getCorrelationId());
+
+ if ($contentType = $transportMessage->getProperty(Config::CONTENT_TYPE)) {
+ $clientMessage->setContentType($contentType);
+ }
+
+ if ($priority = $transportMessage->getProperty(Config::PRIORITY)) {
+ $clientMessage->setPriority($priority);
+ }
+
+ if ($delay = $transportMessage->getProperty(Config::DELAY)) {
+ $clientMessage->setDelay((int) $delay);
+ }
+
+ if ($expire = $transportMessage->getProperty(Config::EXPIRE)) {
+ $clientMessage->setExpire((int) $expire);
+ }
+
+ return $clientMessage;
+ }
+
+ public function getConfig(): Config
+ {
+ return $this->config;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getRouteCollection(): RouteCollection
+ {
+ return $this->routeCollection;
+ }
+
+ protected function doSendToRouter(InteropProducer $producer, Destination $topic, InteropMessage $transportMessage): void
+ {
+ $producer->send($topic, $transportMessage);
+ }
+
+ protected function doSendToProcessor(InteropProducer $producer, InteropQueue $queue, InteropMessage $transportMessage): void
+ {
+ $producer->send($queue, $transportMessage);
+ }
+
+ protected function createRouterTopic(): Destination
+ {
+ return $this->createQueue($this->getConfig()->getRouterQueue());
+ }
+
+ protected function createTransportRouterTopicName(string $name, bool $prefix): string
+ {
+ $clientPrefix = $prefix ? $this->config->getPrefix() : '';
+
+ return strtolower(implode($this->config->getSeparator(), array_filter([$clientPrefix, $name])));
+ }
+
+ protected function createTransportQueueName(string $name, bool $prefix): string
+ {
+ $clientPrefix = $prefix ? $this->config->getPrefix() : '';
+ $clientAppName = $prefix ? $this->config->getApp() : '';
+
+ return strtolower(implode($this->config->getSeparator(), array_filter([$clientPrefix, $clientAppName, $name])));
+ }
+
+ protected function doCreateQueue(string $transportQueueName): InteropQueue
+ {
+ return $this->context->createQueue($transportQueueName);
+ }
+
+ protected function doCreateTopic(string $transportTopicName): InteropTopic
+ {
+ return $this->context->createTopic($transportTopicName);
+ }
+
+ /**
+ * [client message priority => transport message priority].
+ *
+ * @return int[]
+ */
+ protected function getPriorityMap(): array
+ {
+ return [
+ MessagePriority::VERY_LOW => 0,
+ MessagePriority::LOW => 1,
+ MessagePriority::NORMAL => 2,
+ MessagePriority::HIGH => 3,
+ MessagePriority::VERY_HIGH => 4,
+ ];
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/GpsDriver.php b/pkg/enqueue/Client/Driver/GpsDriver.php
new file mode 100644
index 000000000..32d14f721
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/GpsDriver.php
@@ -0,0 +1,64 @@
+debug(sprintf('[GpsDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerTopic = $this->createRouterTopic();
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+
+ $log('Subscribe router topic to queue: %s -> %s', $routerTopic->getTopicName(), $routerQueue->getQueueName());
+ $this->getContext()->subscribe($routerTopic, $routerQueue);
+
+ // setup queues
+ $declaredQueues = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ /** @var GpsQueue $queue */
+ $queue = $this->createRouteQueue($route);
+ if (array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ continue;
+ }
+
+ $topic = $this->getContext()->createTopic($queue->getQueueName());
+
+ $log('Subscribe processor topic to queue: %s -> %s', $topic->getTopicName(), $queue->getQueueName());
+ $this->getContext()->subscribe($topic, $queue);
+
+ $declaredQueues[$queue->getQueueName()] = true;
+ }
+ }
+
+ /**
+ * @return GpsTopic
+ */
+ protected function createRouterTopic(): Destination
+ {
+ return $this->doCreateTopic(
+ $this->createTransportRouterTopicName($this->getConfig()->getRouterTopic(), true)
+ );
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/MongodbDriver.php b/pkg/enqueue/Client/Driver/MongodbDriver.php
new file mode 100644
index 000000000..1c9cff4bc
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/MongodbDriver.php
@@ -0,0 +1,30 @@
+debug(sprintf('[MongodbDriver] '.$text, ...$args));
+ };
+
+ $contextConfig = $this->getContext()->getConfig();
+ $log('Creating database and collection: "%s" "%s"', $contextConfig['dbname'], $contextConfig['collection_name']);
+ $this->getContext()->createCollection();
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/RabbitMqDriver.php b/pkg/enqueue/Client/Driver/RabbitMqDriver.php
new file mode 100644
index 000000000..f215d555e
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/RabbitMqDriver.php
@@ -0,0 +1,20 @@
+setArguments(['x-max-priority' => 4]);
+
+ return $queue;
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/RabbitMqStompDriver.php b/pkg/enqueue/Client/Driver/RabbitMqStompDriver.php
new file mode 100644
index 000000000..7af2db850
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/RabbitMqStompDriver.php
@@ -0,0 +1,191 @@
+management = $management;
+ }
+
+ /**
+ * @return StompMessage
+ */
+ public function createTransportMessage(Message $message): InteropMessage
+ {
+ $transportMessage = parent::createTransportMessage($message);
+
+ if ($message->getExpire()) {
+ $transportMessage->setHeader('expiration', (string) ($message->getExpire() * 1000));
+ }
+
+ if ($priority = $message->getPriority()) {
+ $priorityMap = $this->getPriorityMap();
+
+ if (false == array_key_exists($priority, $priorityMap)) {
+ throw new \LogicException(sprintf('Cant convert client priority to transport: "%s"', $priority));
+ }
+
+ $transportMessage->setHeader('priority', $priorityMap[$priority]);
+ }
+
+ if ($message->getDelay()) {
+ if (false == $this->getConfig()->getTransportOption('delay_plugin_installed', false)) {
+ throw new \LogicException('The message delaying is not supported. In order to use delay feature install RabbitMQ delay plugin.');
+ }
+
+ $transportMessage->setHeader('x-delay', (string) ($message->getDelay() * 1000));
+ }
+
+ return $transportMessage;
+ }
+
+ public function setupBroker(?LoggerInterface $logger = null): void
+ {
+ $logger = $logger ?: new NullLogger();
+ $log = function ($text, ...$args) use ($logger) {
+ $logger->debug(sprintf('[RabbitMqStompDriver] '.$text, ...$args));
+ };
+
+ if (false == $this->getConfig()->getTransportOption('management_plugin_installed', false)) {
+ $log('Could not setup broker. The option `management_plugin_installed` is not enabled. Please enable that option and install rabbit management plugin');
+
+ return;
+ }
+
+ // setup router
+ $routerExchange = $this->createTransportRouterTopicName($this->getConfig()->getRouterTopic(), true);
+ $log('Declare router exchange: %s', $routerExchange);
+ $this->management->declareExchange($routerExchange, [
+ 'type' => 'fanout',
+ 'durable' => true,
+ 'auto_delete' => false,
+ ]);
+
+ $routerQueue = $this->createTransportQueueName($this->getConfig()->getRouterQueue(), true);
+ $log('Declare router queue: %s', $routerQueue);
+ $this->management->declareQueue($routerQueue, [
+ 'auto_delete' => false,
+ 'durable' => true,
+ 'arguments' => [
+ 'x-max-priority' => 4,
+ ],
+ ]);
+
+ $log('Bind router queue to exchange: %s -> %s', $routerQueue, $routerExchange);
+ $this->management->bind($routerExchange, $routerQueue, $routerQueue);
+
+ // setup queues
+ foreach ($this->getRouteCollection()->all() as $route) {
+ $queue = $this->createRouteQueue($route);
+
+ $log('Declare processor queue: %s', $queue->getStompName());
+ $this->management->declareQueue($queue->getStompName(), [
+ 'auto_delete' => false,
+ 'durable' => true,
+ 'arguments' => [
+ 'x-max-priority' => 4,
+ ],
+ ]);
+ }
+
+ // setup delay exchanges
+ if ($this->getConfig()->getTransportOption('delay_plugin_installed', false)) {
+ foreach ($this->getRouteCollection()->all() as $route) {
+ $queue = $this->createRouteQueue($route);
+ $delayExchange = $queue->getStompName().'.delayed';
+
+ $log('Declare delay exchange: %s', $delayExchange);
+ $this->management->declareExchange($delayExchange, [
+ 'type' => 'x-delayed-message',
+ 'durable' => true,
+ 'auto_delete' => false,
+ 'arguments' => [
+ 'x-delayed-type' => 'direct',
+ ],
+ ]);
+
+ $log('Bind processor queue to delay exchange: %s -> %s', $queue->getStompName(), $delayExchange);
+ $this->management->bind($delayExchange, $queue->getStompName(), $queue->getStompName());
+ }
+ } else {
+ $log('Delay exchange and bindings are not setup. if you\'d like to use delays please install delay rabbitmq plugin and set delay_plugin_installed option to true');
+ }
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function doCreateQueue(string $transportQueueName): InteropQueue
+ {
+ $queue = parent::doCreateQueue($transportQueueName);
+ $queue->setHeader('x-max-priority', 4);
+
+ return $queue;
+ }
+
+ /**
+ * @param StompProducer $producer
+ * @param StompDestination $topic
+ * @param StompMessage $transportMessage
+ */
+ protected function doSendToRouter(InteropProducer $producer, Destination $topic, InteropMessage $transportMessage): void
+ {
+ // We should not handle priority, expiration, and delay at this stage.
+ // The router will take care of it while re-sending the message to the final destinations.
+ $transportMessage->setHeader('expiration', null);
+ $transportMessage->setHeader('priority', null);
+ $transportMessage->setHeader('x-delay', null);
+
+ $producer->send($topic, $transportMessage);
+ }
+
+ /**
+ * @param StompProducer $producer
+ * @param StompDestination $destination
+ * @param StompMessage $transportMessage
+ */
+ protected function doSendToProcessor(InteropProducer $producer, InteropQueue $destination, InteropMessage $transportMessage): void
+ {
+ if ($delay = $transportMessage->getProperty(Config::DELAY)) {
+ $producer->setDeliveryDelay(null);
+ $destination = $this->createDelayedTopic($destination);
+ }
+
+ $producer->send($destination, $transportMessage);
+ }
+
+ private function createDelayedTopic(StompDestination $queue): StompDestination
+ {
+ // in order to use delay feature make sure the rabbitmq_delayed_message_exchange plugin is installed.
+ $destination = $this->getContext()->createTopic($queue->getStompName().'.delayed');
+ $destination->setType(StompDestination::TYPE_EXCHANGE);
+ $destination->setDurable(true);
+ $destination->setAutoDelete(false);
+ $destination->setRoutingKey($queue->getStompName());
+
+ return $destination;
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/RdKafkaDriver.php b/pkg/enqueue/Client/Driver/RdKafkaDriver.php
new file mode 100644
index 000000000..2609e3f91
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/RdKafkaDriver.php
@@ -0,0 +1,48 @@
+debug('[RdKafkaDriver] setup broker');
+ $log = function ($text, ...$args) use ($logger) {
+ $logger->debug(sprintf('[RdKafkaDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+ $log('Create router queue: %s', $routerQueue->getQueueName());
+ $this->getContext()->createConsumer($routerQueue);
+
+ // setup queues
+ $declaredQueues = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ /** @var RdKafkaTopic $queue */
+ $queue = $this->createRouteQueue($route);
+ if (array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ continue;
+ }
+
+ $log('Create processor queue: %s', $queue->getQueueName());
+ $this->getContext()->createConsumer($queue);
+ }
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/RedisDriver.php b/pkg/enqueue/Client/Driver/RedisDriver.php
new file mode 100644
index 000000000..493cb7c96
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/RedisDriver.php
@@ -0,0 +1,20 @@
+debug(sprintf('[SqsQsDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerTopic = $this->createRouterTopic();
+ $log('Declare router topic: %s', $routerTopic->getTopicName());
+ $this->getContext()->declareTopic($routerTopic);
+
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+ $log('Declare router queue: %s', $routerQueue->getQueueName());
+ $this->getContext()->declareQueue($routerQueue);
+
+ $log('Bind router queue to topic: %s -> %s', $routerQueue->getQueueName(), $routerTopic->getTopicName());
+ $this->getContext()->bind($routerTopic, $routerQueue);
+
+ // setup queues
+ $declaredQueues = [];
+ $declaredTopics = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ $queue = $this->createRouteQueue($route);
+ if (false === array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ $log('Declare processor queue: %s', $queue->getQueueName());
+ $this->getContext()->declareQueue($queue);
+
+ $declaredQueues[$queue->getQueueName()] = true;
+ }
+
+ if ($route->isCommand()) {
+ continue;
+ }
+
+ $topic = $this->doCreateTopic($this->createTransportQueueName($route->getSource(), true));
+ if (false === array_key_exists($topic->getTopicName(), $declaredTopics)) {
+ $log('Declare processor topic: %s', $topic->getTopicName());
+ $this->getContext()->declareTopic($topic);
+
+ $declaredTopics[$topic->getTopicName()] = true;
+ }
+
+ $log('Bind processor queue to topic: %s -> %s', $queue->getQueueName(), $topic->getTopicName());
+ $this->getContext()->bind($topic, $queue);
+ }
+ }
+
+ protected function createRouterTopic(): Destination
+ {
+ return $this->doCreateTopic(
+ $this->createTransportRouterTopicName($this->getConfig()->getRouterTopic(), true)
+ );
+ }
+
+ protected function createTransportRouterTopicName(string $name, bool $prefix): string
+ {
+ $name = parent::createTransportRouterTopicName($name, $prefix);
+
+ return str_replace('.', '_dot_', $name);
+ }
+
+ protected function createTransportQueueName(string $name, bool $prefix): string
+ {
+ $name = parent::createTransportQueueName($name, $prefix);
+
+ return str_replace('.', '_dot_', $name);
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/SqsDriver.php b/pkg/enqueue/Client/Driver/SqsDriver.php
new file mode 100644
index 000000000..49b696aae
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/SqsDriver.php
@@ -0,0 +1,62 @@
+debug(sprintf('[SqsDriver] '.$text, ...$args));
+ };
+
+ // setup router
+ $routerQueue = $this->createQueue($this->getConfig()->getRouterQueue());
+ $log('Declare router queue: %s', $routerQueue->getQueueName());
+ $this->getContext()->declareQueue($routerQueue);
+
+ // setup queues
+ $declaredQueues = [];
+ foreach ($this->getRouteCollection()->all() as $route) {
+ /** @var SqsDestination $queue */
+ $queue = $this->createRouteQueue($route);
+ if (array_key_exists($queue->getQueueName(), $declaredQueues)) {
+ continue;
+ }
+
+ $log('Declare processor queue: %s', $queue->getQueueName());
+ $this->getContext()->declareQueue($queue);
+
+ $declaredQueues[$queue->getQueueName()] = true;
+ }
+ }
+
+ protected function createTransportRouterTopicName(string $name, bool $prefix): string
+ {
+ $name = parent::createTransportRouterTopicName($name, $prefix);
+
+ return str_replace('.', '_dot_', $name);
+ }
+
+ protected function createTransportQueueName(string $name, bool $prefix): string
+ {
+ $name = parent::createTransportQueueName($name, $prefix);
+
+ return str_replace('.', '_dot_', $name);
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/StompDriver.php b/pkg/enqueue/Client/Driver/StompDriver.php
new file mode 100644
index 000000000..811ad76e7
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/StompDriver.php
@@ -0,0 +1,71 @@
+debug('[StompDriver] Stomp protocol does not support broker configuration');
+ }
+
+ /**
+ * @return StompMessage
+ */
+ public function createTransportMessage(Message $message): InteropMessage
+ {
+ /** @var StompMessage $transportMessage */
+ $transportMessage = parent::createTransportMessage($message);
+ $transportMessage->setPersistent(true);
+
+ return $transportMessage;
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function doCreateQueue(string $transportQueueName): InteropQueue
+ {
+ /** @var StompDestination $queue */
+ $queue = parent::doCreateQueue($transportQueueName);
+ $queue->setDurable(true);
+ $queue->setAutoDelete(false);
+ $queue->setExclusive(false);
+
+ return $queue;
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function createRouterTopic(): Destination
+ {
+ /** @var StompDestination $topic */
+ $topic = $this->doCreateTopic(
+ $this->createTransportRouterTopicName($this->getConfig()->getRouterTopic(), true)
+ );
+ $topic->setDurable(true);
+ $topic->setAutoDelete(false);
+
+ return $topic;
+ }
+}
diff --git a/pkg/enqueue/Client/Driver/StompManagementClient.php b/pkg/enqueue/Client/Driver/StompManagementClient.php
new file mode 100644
index 000000000..0d64450dd
--- /dev/null
+++ b/pkg/enqueue/Client/Driver/StompManagementClient.php
@@ -0,0 +1,44 @@
+client = $client;
+ $this->vhost = $vhost;
+ }
+
+ public static function create(string $vhost = '/', string $host = 'localhost', int $port = 15672, string $login = 'guest', string $password = 'guest'): self
+ {
+ return new self(new Client(null, 'http://'.$host.':'.$port, $login, $password), $vhost);
+ }
+
+ public function declareQueue(string $name, array $options)
+ {
+ return $this->client->queues()->create($this->vhost, $name, $options);
+ }
+
+ public function declareExchange(string $name, array $options)
+ {
+ return $this->client->exchanges()->create($this->vhost, $name, $options);
+ }
+
+ public function bind(string $exchange, string $queue, ?string $routingKey = null, $arguments = null)
+ {
+ return $this->client->bindings()->create($this->vhost, $exchange, $queue, $routingKey, $arguments);
+ }
+}
diff --git a/pkg/enqueue/Client/DriverFactory.php b/pkg/enqueue/Client/DriverFactory.php
new file mode 100644
index 000000000..5c827e7e7
--- /dev/null
+++ b/pkg/enqueue/Client/DriverFactory.php
@@ -0,0 +1,91 @@
+getTransportOption('dsn');
+
+ if (empty($dsn)) {
+ throw new \LogicException('This driver factory relies on dsn option from transport config. The option is empty or not set.');
+ }
+
+ $dsn = Dsn::parseFirst($dsn);
+
+ if ($driverInfo = $this->findDriverInfo($dsn, Resources::getAvailableDrivers())) {
+ $driverClass = $driverInfo['driverClass'];
+
+ if (RabbitMqStompDriver::class === $driverClass) {
+ return $this->createRabbitMqStompDriver($factory, $dsn, $config, $collection);
+ }
+
+ return new $driverClass($factory->createContext(), $config, $collection);
+ }
+
+ $knownDrivers = Resources::getKnownDrivers();
+ if ($driverInfo = $this->findDriverInfo($dsn, $knownDrivers)) {
+ throw new \LogicException(sprintf('To use given scheme "%s" a package has to be installed. Run "composer req %s" to add it.', $dsn->getScheme(), implode(' ', $driverInfo['packages'])));
+ }
+
+ throw new \LogicException(sprintf('A given scheme "%s" is not supported. Maybe it is a custom driver, make sure you registered it with "%s::addDriver".', $dsn->getScheme(), Resources::class));
+ }
+
+ private function findDriverInfo(Dsn $dsn, array $factories): ?array
+ {
+ $protocol = $dsn->getSchemeProtocol();
+
+ if ($dsn->getSchemeExtensions()) {
+ foreach ($factories as $info) {
+ if (empty($info['requiredSchemeExtensions'])) {
+ continue;
+ }
+
+ if (false == in_array($protocol, $info['schemes'], true)) {
+ continue;
+ }
+
+ $diff = array_diff($dsn->getSchemeExtensions(), $info['requiredSchemeExtensions']);
+ if (empty($diff)) {
+ return $info;
+ }
+ }
+ }
+
+ foreach ($factories as $driverClass => $info) {
+ if (false == empty($info['requiredSchemeExtensions'])) {
+ continue;
+ }
+
+ if (false == in_array($protocol, $info['schemes'], true)) {
+ continue;
+ }
+
+ return $info;
+ }
+
+ return null;
+ }
+
+ private function createRabbitMqStompDriver(ConnectionFactory $factory, Dsn $dsn, Config $config, RouteCollection $collection): RabbitMqStompDriver
+ {
+ $defaultManagementHost = $dsn->getHost() ?: $config->getTransportOption('host', 'localhost');
+ $managementVast = ltrim($dsn->getPath() ?? '', '/') ?: $config->getTransportOption('vhost', '/');
+
+ $managementClient = StompManagementClient::create(
+ urldecode($managementVast),
+ $config->getDriverOption('rabbitmq_management_host', $defaultManagementHost),
+ $config->getDriverOption('rabbitmq_management_port', 15672),
+ (string) $dsn->getUser() ?: $config->getTransportOption('user', 'guest'),
+ (string) $dsn->getPassword() ?: $config->getTransportOption('pass', 'guest')
+ );
+
+ return new RabbitMqStompDriver($factory->createContext(), $config, $collection, $managementClient);
+ }
+}
diff --git a/pkg/enqueue/Client/DriverFactoryInterface.php b/pkg/enqueue/Client/DriverFactoryInterface.php
new file mode 100644
index 000000000..698ad05a4
--- /dev/null
+++ b/pkg/enqueue/Client/DriverFactoryInterface.php
@@ -0,0 +1,10 @@
+message = $message;
+ $this->producer = $producer;
+ $this->driver = $driver;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getProducer(): ProducerInterface
+ {
+ return $this->producer;
+ }
+
+ public function getDriver(): DriverInterface
+ {
+ return $this->driver;
+ }
+
+ public function isEvent(): bool
+ {
+ return (bool) $this->message->getProperty(Config::TOPIC);
+ }
+
+ public function isCommand(): bool
+ {
+ return (bool) $this->message->getProperty(Config::COMMAND);
+ }
+
+ public function getCommand(): string
+ {
+ return $this->message->getProperty(Config::COMMAND);
+ }
+
+ public function getTopic(): string
+ {
+ return $this->message->getProperty(Config::TOPIC);
+ }
+}
diff --git a/pkg/enqueue/Client/DriverPreSendExtensionInterface.php b/pkg/enqueue/Client/DriverPreSendExtensionInterface.php
new file mode 100644
index 000000000..fd95c9328
--- /dev/null
+++ b/pkg/enqueue/Client/DriverPreSendExtensionInterface.php
@@ -0,0 +1,8 @@
+transportDestination = $transportDestination;
+ $this->transportMessage = $transportMessage;
+ }
+
+ public function getTransportDestination(): Destination
+ {
+ return $this->transportDestination;
+ }
+
+ public function getTransportMessage(): TransportMessage
+ {
+ return $this->transportMessage;
+ }
+}
diff --git a/pkg/enqueue/Client/Extension/PrepareBodyExtension.php b/pkg/enqueue/Client/Extension/PrepareBodyExtension.php
new file mode 100644
index 000000000..e7924548c
--- /dev/null
+++ b/pkg/enqueue/Client/Extension/PrepareBodyExtension.php
@@ -0,0 +1,51 @@
+prepareBody($context->getMessage());
+ }
+
+ public function onPreSendCommand(PreSend $context): void
+ {
+ $this->prepareBody($context->getMessage());
+ }
+
+ private function prepareBody(Message $message): void
+ {
+ $body = $message->getBody();
+ $contentType = $message->getContentType();
+
+ if (is_scalar($body) || null === $body) {
+ $contentType = $contentType ?: 'text/plain';
+ $body = (string) $body;
+ } elseif (is_array($body)) {
+ // only array of scalars is allowed.
+ array_walk_recursive($body, function ($value) {
+ if (!is_scalar($value) && null !== $value) {
+ throw new \LogicException(sprintf('The message\'s body must be an array of scalars. Found not scalar in the array: %s', is_object($value) ? $value::class : gettype($value)));
+ }
+ });
+
+ $contentType = $contentType ?: 'application/json';
+ $body = JSON::encode($body);
+ } elseif ($body instanceof \JsonSerializable) {
+ $contentType = $contentType ?: 'application/json';
+ $body = JSON::encode($body);
+ } else {
+ throw new \InvalidArgumentException(sprintf('The message\'s body must be either null, scalar, array or object (implements \JsonSerializable). Got: %s', is_object($body) ? $body::class : gettype($body)));
+ }
+
+ $message->setContentType($contentType);
+ $message->setBody($body);
+ }
+}
diff --git a/pkg/enqueue/Client/ExtensionInterface.php b/pkg/enqueue/Client/ExtensionInterface.php
new file mode 100644
index 000000000..596b1b9af
--- /dev/null
+++ b/pkg/enqueue/Client/ExtensionInterface.php
@@ -0,0 +1,7 @@
+headers = [];
- $this->properties = [];
+ $this->body = $body;
+ $this->headers = $headers;
+ $this->properties = $properties;
+
+ $this->scope = static::SCOPE_MESSAGE_BUS;
}
/**
- * @return null|string
+ * @return string|null
*/
public function getBody()
{
@@ -68,7 +96,7 @@ public function getBody()
}
/**
- * @param null|string $body
+ * @param string|int|float|array|\JsonSerializable|null $body
*/
public function setBody($body)
{
@@ -177,6 +205,48 @@ public function setDelay($delay)
$this->delay = $delay;
}
+ public function setScope(string $scope): void
+ {
+ $this->scope = $scope;
+ }
+
+ public function getScope(): string
+ {
+ return $this->scope;
+ }
+
+ /**
+ * @return string
+ */
+ public function getReplyTo()
+ {
+ return $this->replyTo;
+ }
+
+ /**
+ * @param string $replyTo
+ */
+ public function setReplyTo($replyTo)
+ {
+ $this->replyTo = $replyTo;
+ }
+
+ /**
+ * @return string
+ */
+ public function getCorrelationId()
+ {
+ return $this->correlationId;
+ }
+
+ /**
+ * @param string $correlationId
+ */
+ public function setCorrelationId($correlationId)
+ {
+ $this->correlationId = $correlationId;
+ }
+
/**
* @return array
*/
@@ -186,10 +256,8 @@ public function getHeaders()
}
/**
- * @param string $name
- * @param mixed $default
- *
- * @return mixed
+ * @param string $name
+ * @param mixed|null $default
*/
public function getHeader($name, $default = null)
{
@@ -198,16 +266,12 @@ public function getHeader($name, $default = null)
/**
* @param string $name
- * @param mixed $value
*/
public function setHeader($name, $value)
{
$this->headers[$name] = $value;
}
- /**
- * @param array $headers
- */
public function setHeaders(array $headers)
{
$this->headers = $headers;
@@ -221,19 +285,14 @@ public function getProperties()
return $this->properties;
}
- /**
- * @param array $properties
- */
public function setProperties(array $properties)
{
$this->properties = $properties;
}
/**
- * @param string $name
- * @param mixed $default
- *
- * @return mixed
+ * @param string $name
+ * @param mixed|null $default
*/
public function getProperty($name, $default = null)
{
@@ -242,7 +301,6 @@ public function getProperty($name, $default = null)
/**
* @param string $name
- * @param mixed $value
*/
public function setProperty($name, $value)
{
diff --git a/pkg/enqueue/Client/MessagePriority.php b/pkg/enqueue/Client/MessagePriority.php
index efa658c14..e14be9a7d 100644
--- a/pkg/enqueue/Client/MessagePriority.php
+++ b/pkg/enqueue/Client/MessagePriority.php
@@ -4,9 +4,9 @@
class MessagePriority
{
- const VERY_LOW = 'enqueue.message_queue.client.very_low_message_priority';
- const LOW = 'enqueue.message_queue.client.low_message_priority';
- const NORMAL = 'enqueue.message_queue.client.normal_message_priority';
- const HIGH = 'enqueue.message_queue.client.high_message_priority';
- const VERY_HIGH = 'enqueue.message_queue.client.very_high_message_priority';
+ public const VERY_LOW = 'enqueue.message_queue.client.very_low_message_priority';
+ public const LOW = 'enqueue.message_queue.client.low_message_priority';
+ public const NORMAL = 'enqueue.message_queue.client.normal_message_priority';
+ public const HIGH = 'enqueue.message_queue.client.high_message_priority';
+ public const VERY_HIGH = 'enqueue.message_queue.client.very_high_message_priority';
}
diff --git a/pkg/enqueue/Client/MessageProducer.php b/pkg/enqueue/Client/MessageProducer.php
deleted file mode 100644
index 820ae0d2d..000000000
--- a/pkg/enqueue/Client/MessageProducer.php
+++ /dev/null
@@ -1,94 +0,0 @@
-driver = $driver;
- }
-
- /**
- * {@inheritdoc}
- */
- public function send($topic, $message)
- {
- if (false == $message instanceof Message) {
- $body = $message;
- $message = new Message();
- $message->setBody($body);
- }
-
- $this->prepareBody($message);
-
- $message->setProperty(Config::PARAMETER_TOPIC_NAME, $topic);
-
- if (!$message->getMessageId()) {
- $message->setMessageId(UUID::generate());
- }
-
- if (!$message->getTimestamp()) {
- $message->setTimestamp(time());
- }
-
- if (!$message->getPriority()) {
- $message->setPriority(MessagePriority::NORMAL);
- }
-
- $this->driver->sendToRouter($message);
- }
-
- /**
- * @param Message $message
- */
- private function prepareBody(Message $message)
- {
- $body = $message->getBody();
- $contentType = $message->getContentType();
-
- if (is_scalar($body) || is_null($body)) {
- $contentType = $contentType ?: 'text/plain';
- $body = (string) $body;
- } elseif (is_array($body)) {
- $body = $message->getBody();
- $contentType = $message->getContentType();
-
- if ($contentType && $contentType !== 'application/json') {
- throw new \LogicException(sprintf('Content type "application/json" only allowed when body is array'));
- }
-
- // only array of scalars is allowed.
- array_walk_recursive($body, function ($value) {
- if (!is_scalar($value) && !is_null($value)) {
- throw new \LogicException(sprintf(
- 'The message\'s body must be an array of scalars. Found not scalar in the array: %s',
- is_object($value) ? get_class($value) : gettype($value)
- ));
- }
- });
-
- $contentType = 'application/json';
- $body = JSON::encode($body);
- } else {
- throw new \InvalidArgumentException(sprintf(
- 'The message\'s body must be either null, scalar or array. Got: %s',
- is_object($body) ? get_class($body) : gettype($body)
- ));
- }
-
- $message->setContentType($contentType);
- $message->setBody($body);
- }
-}
diff --git a/pkg/enqueue/Client/MessageProducerInterface.php b/pkg/enqueue/Client/MessageProducerInterface.php
deleted file mode 100644
index c3d1bf8bd..000000000
--- a/pkg/enqueue/Client/MessageProducerInterface.php
+++ /dev/null
@@ -1,17 +0,0 @@
-clientName = $clientName;
- $this->transportName = $transportName;
- $this->processors = $processors;
- }
-
- /**
- * @return string
- */
- public function getClientName()
- {
- return $this->clientName;
- }
-
- /**
- * @return string
- */
- public function getTransportName()
- {
- return $this->transportName;
- }
-
- /**
- * @return string[]
- */
- public function getProcessors()
- {
- return $this->processors;
- }
-}
diff --git a/pkg/enqueue/Client/Meta/QueueMetaRegistry.php b/pkg/enqueue/Client/Meta/QueueMetaRegistry.php
deleted file mode 100644
index 71a2c0342..000000000
--- a/pkg/enqueue/Client/Meta/QueueMetaRegistry.php
+++ /dev/null
@@ -1,95 +0,0 @@
- [
- * 'transportName' => 'aTransportQueueName',
- * 'processors' => ['aFooProcessorName', 'aBarProcessorName'],
- * ]
- * ].
- *
- *
- * @param Config $config
- * @param array $meta
- */
- public function __construct(Config $config, array $meta)
- {
- $this->config = $config;
- $this->meta = $meta;
- }
-
- /**
- * @param string $queueName
- * @param string|null $transportName
- */
- public function add($queueName, $transportName = null)
- {
- $this->meta[$queueName] = [
- 'transportName' => $transportName,
- 'processors' => [],
- ];
- }
-
- /**
- * @param string $queueName
- * @param string $processorName
- */
- public function addProcessor($queueName, $processorName)
- {
- if (false == array_key_exists($queueName, $this->meta)) {
- $this->add($queueName);
- }
-
- $this->meta[$queueName]['processors'][] = $processorName;
- }
-
- /**
- * @param string $queueName
- *
- * @return QueueMeta
- */
- public function getQueueMeta($queueName)
- {
- if (false == array_key_exists($queueName, $this->meta)) {
- throw new \InvalidArgumentException(sprintf(
- 'The queue meta not found. Requested name `%s`',
- $queueName
- ));
- }
-
- $transportName = $this->config->createTransportQueueName($queueName);
-
- $meta = array_replace([
- 'processors' => [],
- 'transportName' => $transportName,
- ], $this->meta[$queueName]);
-
- return new QueueMeta($queueName, $meta['transportName'], $meta['processors']);
- }
-
- /**
- * @return \Generator|QueueMeta[]
- */
- public function getQueuesMeta()
- {
- foreach (array_keys($this->meta) as $queueName) {
- yield $this->getQueueMeta($queueName);
- }
- }
-}
diff --git a/pkg/enqueue/Client/Meta/TopicMeta.php b/pkg/enqueue/Client/Meta/TopicMeta.php
deleted file mode 100644
index abb0e33ed..000000000
--- a/pkg/enqueue/Client/Meta/TopicMeta.php
+++ /dev/null
@@ -1,57 +0,0 @@
-name = $name;
- $this->description = $description;
- $this->processors = $processors;
- }
-
- /**
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-
- /**
- * @return string
- */
- public function getDescription()
- {
- return $this->description;
- }
-
- /**
- * @return string[]
- */
- public function getProcessors()
- {
- return $this->processors;
- }
-}
diff --git a/pkg/enqueue/Client/Meta/TopicMetaRegistry.php b/pkg/enqueue/Client/Meta/TopicMetaRegistry.php
deleted file mode 100644
index efceb9a11..000000000
--- a/pkg/enqueue/Client/Meta/TopicMetaRegistry.php
+++ /dev/null
@@ -1,80 +0,0 @@
- [
- * 'description' => 'A desc',
- * 'processors' => ['aProcessorNameFoo', 'aProcessorNameBar],
- * ],
- * ].
- *
- * @param array $meta
- */
- public function __construct(array $meta)
- {
- $this->meta = $meta;
- }
-
- /**
- * @param string $topicName
- * @param string $description
- */
- public function add($topicName, $description = null)
- {
- $this->meta[$topicName] = [
- 'description' => $description,
- 'processors' => [],
- ];
- }
-
- /**
- * @param string $topicName
- * @param string $processorName
- */
- public function addProcessor($topicName, $processorName)
- {
- if (false == array_key_exists($topicName, $this->meta)) {
- $this->add($topicName);
- }
-
- $this->meta[$topicName]['processors'][] = $processorName;
- }
-
- /**
- * @param string $topicName
- *
- * @return TopicMeta
- */
- public function getTopicMeta($topicName)
- {
- if (false == array_key_exists($topicName, $this->meta)) {
- throw new \InvalidArgumentException(sprintf('The topic meta not found. Requested name `%s`', $topicName));
- }
-
- $topic = array_replace([
- 'description' => '',
- 'processors' => [],
- ], $this->meta[$topicName]);
-
- return new TopicMeta($topicName, $topic['description'], $topic['processors']);
- }
-
- /**
- * @return \Generator|TopicMeta[]
- */
- public function getTopicsMeta()
- {
- foreach (array_keys($this->meta) as $topicName) {
- yield $this->getTopicMeta($topicName);
- }
- }
-}
diff --git a/pkg/enqueue/Client/NullDriver.php b/pkg/enqueue/Client/NullDriver.php
deleted file mode 100644
index dc2f74014..000000000
--- a/pkg/enqueue/Client/NullDriver.php
+++ /dev/null
@@ -1,143 +0,0 @@
-context = $session;
- $this->config = $config;
- }
-
- /**
- * {@inheritdoc}
- *
- * @return NullMessage
- */
- public function createTransportMessage(Message $message)
- {
- $headers = $message->getHeaders();
- $headers['content_type'] = $message->getContentType();
- $headers['expiration'] = $message->getExpire();
- $headers['delay'] = $message->getDelay();
- $headers['priority'] = $message->getPriority();
-
- $transportMessage = $this->context->createMessage();
- $transportMessage->setBody($message->getBody());
- $transportMessage->setHeaders($headers);
- $transportMessage->setProperties($message->getProperties());
- $transportMessage->setTimestamp($message->getTimestamp());
- $transportMessage->setMessageId($message->getMessageId());
-
- return $transportMessage;
- }
-
- /**
- * {@inheritdoc}
- *
- * @param NullMessage $message
- */
- public function createClientMessage(TransportMessage $message)
- {
- $clientMessage = new Message();
- $clientMessage->setBody($message->getBody());
- $clientMessage->setHeaders($message->getHeaders());
- $clientMessage->setProperties($message->getProperties());
- $clientMessage->setTimestamp($message->getTimestamp());
- $clientMessage->setMessageId($message->getMessageId());
-
- if ($contentType = $message->getHeader('content_type')) {
- $clientMessage->setContentType($contentType);
- }
-
- if ($expiration = $message->getHeader('expiration')) {
- $clientMessage->setExpire($expiration);
- }
-
- if ($delay = $message->getHeader('delay')) {
- $clientMessage->setDelay($delay);
- }
-
- if ($priority = $message->getHeader('priority')) {
- $clientMessage->setPriority($priority);
- }
-
- return $clientMessage;
- }
-
- /**
- * {@inheritdoc}
- */
- public function createQueue($queueName)
- {
- return $this->context->createQueue($queueName);
- }
-
- /**
- * {@inheritdoc}
- */
- public function getConfig()
- {
- return $this->config;
- }
-
- /**
- * {@inheritdoc}
- */
- public function sendToRouter(Message $message)
- {
- $transportMessage = $this->createTransportMessage($message);
- $topic = $this->context->createTopic(
- $this->config->createTransportRouterTopicName(
- $this->config->getRouterTopicName()
- )
- );
-
- $this->context->createProducer()->send($topic, $transportMessage);
- }
-
- /**
- * {@inheritdoc}
- */
- public function sendToProcessor(Message $message)
- {
- $transportMessage = $this->createTransportMessage($message);
- $queue = $this->context->createQueue(
- $this->config->createTransportQueueName(
- $this->config->getRouterQueueName()
- )
- );
-
- $this->context->createProducer()->send($queue, $transportMessage);
- }
-
- /**
- * {@inheritdoc}
- */
- public function setupBroker(LoggerInterface $logger = null)
- {
- $logger ?: new NullLogger();
- $logger->debug('[NullDriver] setup broker');
- }
-}
diff --git a/pkg/enqueue/Client/PostSend.php b/pkg/enqueue/Client/PostSend.php
new file mode 100644
index 000000000..5d9526ea4
--- /dev/null
+++ b/pkg/enqueue/Client/PostSend.php
@@ -0,0 +1,78 @@
+message = $message;
+ $this->producer = $producer;
+ $this->driver = $driver;
+ $this->transportDestination = $transportDestination;
+ $this->transportMessage = $transportMessage;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getProducer(): ProducerInterface
+ {
+ return $this->producer;
+ }
+
+ public function getDriver(): DriverInterface
+ {
+ return $this->driver;
+ }
+
+ public function getTransportDestination(): Destination
+ {
+ return $this->transportDestination;
+ }
+
+ public function getTransportMessage(): TransportMessage
+ {
+ return $this->transportMessage;
+ }
+
+ public function isEvent(): bool
+ {
+ return (bool) $this->message->getProperty(Config::TOPIC);
+ }
+
+ public function isCommand(): bool
+ {
+ return (bool) $this->message->getProperty(Config::COMMAND);
+ }
+
+ public function getCommand(): string
+ {
+ return $this->message->getProperty(Config::COMMAND);
+ }
+
+ public function getTopic(): string
+ {
+ return $this->message->getProperty(Config::TOPIC);
+ }
+}
diff --git a/pkg/enqueue/Client/PostSendExtensionInterface.php b/pkg/enqueue/Client/PostSendExtensionInterface.php
new file mode 100644
index 000000000..dd3ca8b71
--- /dev/null
+++ b/pkg/enqueue/Client/PostSendExtensionInterface.php
@@ -0,0 +1,8 @@
+message = $message;
+ $this->commandOrTopic = $commandOrTopic;
+ $this->producer = $producer;
+ $this->driver = $driver;
+
+ $this->originalMessage = clone $message;
+ }
+
+ public function getCommand(): string
+ {
+ return $this->commandOrTopic;
+ }
+
+ public function getTopic(): string
+ {
+ return $this->commandOrTopic;
+ }
+
+ public function changeCommand(string $newCommand): void
+ {
+ $this->commandOrTopic = $newCommand;
+ }
+
+ public function changeTopic(string $newTopic): void
+ {
+ $this->commandOrTopic = $newTopic;
+ }
+
+ public function changeBody($body, ?string $contentType = null): void
+ {
+ $this->message->setBody($body);
+
+ if (null !== $contentType) {
+ $this->message->setContentType($contentType);
+ }
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getOriginalMessage(): Message
+ {
+ return $this->originalMessage;
+ }
+
+ public function getProducer(): ProducerInterface
+ {
+ return $this->producer;
+ }
+
+ public function getDriver(): DriverInterface
+ {
+ return $this->driver;
+ }
+}
diff --git a/pkg/enqueue/Client/PreSendCommandExtensionInterface.php b/pkg/enqueue/Client/PreSendCommandExtensionInterface.php
new file mode 100644
index 000000000..cefec097f
--- /dev/null
+++ b/pkg/enqueue/Client/PreSendCommandExtensionInterface.php
@@ -0,0 +1,11 @@
+driver = $driver;
+ $this->rpcFactory = $rpcFactory;
+
+ $this->extension = $extension ?
+ new ChainExtension([$extension, new PrepareBodyExtension()]) :
+ new ChainExtension([new PrepareBodyExtension()])
+ ;
+ }
+
+ public function sendEvent(string $topic, $message): void
+ {
+ if (false == $message instanceof Message) {
+ $message = new Message($message);
+ }
+
+ $preSend = new PreSend($topic, $message, $this, $this->driver);
+ $this->extension->onPreSendEvent($preSend);
+
+ $message = $preSend->getMessage();
+ $message->setProperty(Config::TOPIC, $preSend->getTopic());
+
+ $this->doSend($message);
+ }
+
+ public function sendCommand(string $command, $message, bool $needReply = false): ?Promise
+ {
+ if (false == $message instanceof Message) {
+ $message = new Message($message);
+ }
+
+ $preSend = new PreSend($command, $message, $this, $this->driver);
+ $this->extension->onPreSendCommand($preSend);
+
+ $command = $preSend->getCommand();
+ $message = $preSend->getMessage();
+
+ $deleteReplyQueue = false;
+ $replyTo = $message->getReplyTo();
+
+ if ($needReply) {
+ if (false == $replyTo) {
+ $message->setReplyTo($replyTo = $this->rpcFactory->createReplyTo());
+ $deleteReplyQueue = true;
+ }
+
+ if (false == $message->getCorrelationId()) {
+ $message->setCorrelationId(UUID::generate());
+ }
+ }
+
+ $message->setProperty(Config::COMMAND, $command);
+ $message->setScope(Message::SCOPE_APP);
+
+ $this->doSend($message);
+
+ if ($needReply) {
+ $promise = $this->rpcFactory->createPromise($replyTo, $message->getCorrelationId(), 60000);
+ $promise->setDeleteReplyQueue($deleteReplyQueue);
+
+ return $promise;
+ }
+
+ return null;
+ }
+
+ private function doSend(Message $message): void
+ {
+ if (false === is_string($message->getBody())) {
+ throw new \LogicException(sprintf('The message body must be string at this stage, got "%s". Make sure you passed string as message or there is an extension that converts custom input to string.', is_object($message->getBody()) ? get_class($message->getBody()) : gettype($message->getBody())));
+ }
+
+ if ($message->getProperty(Config::PROCESSOR)) {
+ throw new \LogicException(sprintf('The %s property must not be set.', Config::PROCESSOR));
+ }
+
+ if (!$message->getMessageId()) {
+ $message->setMessageId(UUID::generate());
+ }
+
+ if (!$message->getTimestamp()) {
+ $message->setTimestamp(time());
+ }
+
+ $this->extension->onDriverPreSend(new DriverPreSend($message, $this, $this->driver));
+
+ if (Message::SCOPE_MESSAGE_BUS == $message->getScope()) {
+ $result = $this->driver->sendToRouter($message);
+ } elseif (Message::SCOPE_APP == $message->getScope()) {
+ $result = $this->driver->sendToProcessor($message);
+ } else {
+ throw new \LogicException(sprintf('The message scope "%s" is not supported.', $message->getScope()));
+ }
+
+ $this->extension->onPostSend(new PostSend($message, $this, $this->driver, $result->getTransportDestination(), $result->getTransportMessage()));
+ }
+}
diff --git a/pkg/enqueue/Client/ProducerInterface.php b/pkg/enqueue/Client/ProducerInterface.php
new file mode 100644
index 000000000..3c884808a
--- /dev/null
+++ b/pkg/enqueue/Client/ProducerInterface.php
@@ -0,0 +1,27 @@
+ [
+ * schemes => [schemes strings],
+ * package => package name,
+ * ].
+ *
+ * @var array
+ */
+ private static $knownDrivers;
+
+ private function __construct()
+ {
+ }
+
+ public static function getAvailableDrivers(): array
+ {
+ $map = self::getKnownDrivers();
+
+ $availableMap = [];
+ foreach ($map as $item) {
+ if (class_exists($item['driverClass'])) {
+ $availableMap[] = $item;
+ }
+ }
+
+ return $availableMap;
+ }
+
+ public static function getKnownDrivers(): array
+ {
+ if (null === self::$knownDrivers) {
+ $map = [];
+
+ $map[] = [
+ 'schemes' => ['amqp', 'amqps'],
+ 'driverClass' => AmqpDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/amqp-bunny'],
+ ];
+ $map[] = [
+ 'schemes' => ['amqp', 'amqps'],
+ 'driverClass' => RabbitMqDriver::class,
+ 'requiredSchemeExtensions' => ['rabbitmq'],
+ 'packages' => ['enqueue/enqueue', 'enqueue/amqp-bunny'],
+ ];
+ $map[] = [
+ 'schemes' => ['file'],
+ 'driverClass' => FsDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/fs'],
+ ];
+ $map[] = [
+ 'schemes' => ['null'],
+ 'driverClass' => GenericDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/null'],
+ ];
+ $map[] = [
+ 'schemes' => ['gps'],
+ 'driverClass' => GpsDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/gps'],
+ ];
+ $map[] = [
+ 'schemes' => ['redis', 'rediss'],
+ 'driverClass' => RedisDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/redis'],
+ ];
+ $map[] = [
+ 'schemes' => ['sqs'],
+ 'driverClass' => SqsDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/sqs'],
+ ];
+ $map[] = [
+ 'schemes' => ['sns'],
+ 'driverClass' => GenericDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/sns'],
+ ];
+ $map[] = [
+ 'schemes' => ['snsqs'],
+ 'driverClass' => SnsQsDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/sqs', 'enqueue/sns', 'enqueue/snsqs'],
+ ];
+ $map[] = [
+ 'schemes' => ['stomp'],
+ 'driverClass' => StompDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/stomp'],
+ ];
+ $map[] = [
+ 'schemes' => ['stomp'],
+ 'driverClass' => RabbitMqStompDriver::class,
+ 'requiredSchemeExtensions' => ['rabbitmq'],
+ 'packages' => ['enqueue/enqueue', 'enqueue/stomp'],
+ ];
+ $map[] = [
+ 'schemes' => ['kafka', 'rdkafka'],
+ 'driverClass' => RdKafkaDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/rdkafka'],
+ ];
+ $map[] = [
+ 'schemes' => ['mongodb'],
+ 'driverClass' => MongodbDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'packages' => ['enqueue/enqueue', 'enqueue/mongodb'],
+ ];
+ $map[] = [
+ 'schemes' => [
+ 'db2',
+ 'ibm-db2',
+ 'mssql',
+ 'sqlsrv',
+ 'mysql',
+ 'mysql2',
+ 'mysql',
+ 'pgsql',
+ 'postgres',
+ 'pgsql',
+ 'sqlite',
+ 'sqlite3',
+ 'sqlite',
+ ],
+ 'driverClass' => DbalDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'package' => ['enqueue/enqueue', 'enqueue/dbal'],
+ ];
+ $map[] = [
+ 'schemes' => ['gearman'],
+ 'driverClass' => GenericDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'package' => ['enqueue/enqueue', 'enqueue/gearman'],
+ ];
+ $map[] = [
+ 'schemes' => ['beanstalk'],
+ 'driverClass' => GenericDriver::class,
+ 'requiredSchemeExtensions' => [],
+ 'package' => ['enqueue/enqueue', 'enqueue/pheanstalk'],
+ ];
+
+ self::$knownDrivers = $map;
+ }
+
+ return self::$knownDrivers;
+ }
+
+ public static function addDriver(string $driverClass, array $schemes, array $requiredExtensions, array $packages): void
+ {
+ if (class_exists($driverClass)) {
+ if (false == (new \ReflectionClass($driverClass))->implementsInterface(DriverInterface::class)) {
+ throw new \InvalidArgumentException(sprintf('The driver class "%s" must implement "%s" interface.', $driverClass, DriverInterface::class));
+ }
+ }
+
+ if (empty($schemes)) {
+ throw new \InvalidArgumentException('Schemes could not be empty.');
+ }
+ if (empty($packages)) {
+ throw new \InvalidArgumentException('Packages could not be empty.');
+ }
+
+ self::getKnownDrivers();
+ self::$knownDrivers[] = [
+ 'schemes' => $schemes,
+ 'driverClass' => $driverClass,
+ 'requiredSchemeExtensions' => $requiredExtensions,
+ 'packages' => $packages,
+ ];
+ }
+}
diff --git a/pkg/enqueue/Client/Route.php b/pkg/enqueue/Client/Route.php
new file mode 100644
index 000000000..8b9e31e36
--- /dev/null
+++ b/pkg/enqueue/Client/Route.php
@@ -0,0 +1,114 @@
+source = $source;
+ $this->sourceType = $sourceType;
+ $this->processor = $processor;
+ $this->options = $options;
+ }
+
+ public function getSource(): string
+ {
+ return $this->source;
+ }
+
+ public function isCommand(): bool
+ {
+ return self::COMMAND === $this->sourceType;
+ }
+
+ public function isTopic(): bool
+ {
+ return self::TOPIC === $this->sourceType;
+ }
+
+ public function getProcessor(): string
+ {
+ return $this->processor;
+ }
+
+ public function isProcessorExclusive(): bool
+ {
+ return (bool) $this->getOption('exclusive', false);
+ }
+
+ public function isProcessorExternal(): bool
+ {
+ return (bool) $this->getOption('external', false);
+ }
+
+ public function getQueue(): ?string
+ {
+ return $this->getOption('queue');
+ }
+
+ public function isPrefixQueue(): bool
+ {
+ return (bool) $this->getOption('prefix_queue', true);
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ public function getOption(string $name, $default = null)
+ {
+ return array_key_exists($name, $this->options) ? $this->options[$name] : $default;
+ }
+
+ public function toArray(): array
+ {
+ return array_replace($this->options, [
+ 'source' => $this->source,
+ 'source_type' => $this->sourceType,
+ 'processor' => $this->processor,
+ ]);
+ }
+
+ public static function fromArray(array $route): self
+ {
+ list(
+ 'source' => $source,
+ 'source_type' => $sourceType,
+ 'processor' => $processor) = $route;
+
+ unset($route['source'], $route['source_type'], $route['processor']);
+ $options = $route;
+
+ return new self($source, $sourceType, $processor, $options);
+ }
+}
diff --git a/pkg/enqueue/Client/RouteCollection.php b/pkg/enqueue/Client/RouteCollection.php
new file mode 100644
index 000000000..76bcbe451
--- /dev/null
+++ b/pkg/enqueue/Client/RouteCollection.php
@@ -0,0 +1,114 @@
+routes = $routes;
+ }
+
+ public function add(Route $route): void
+ {
+ $this->routes[] = $route;
+ $this->topicRoutes = null;
+ $this->commandRoutes = null;
+ }
+
+ /**
+ * @return Route[]
+ */
+ public function all(): array
+ {
+ return $this->routes;
+ }
+
+ /**
+ * @return Route[]
+ */
+ public function command(string $command): ?Route
+ {
+ if (null === $this->commandRoutes) {
+ $commandRoutes = [];
+ foreach ($this->routes as $route) {
+ if ($route->isCommand()) {
+ $commandRoutes[$route->getSource()] = $route;
+ }
+ }
+
+ $this->commandRoutes = $commandRoutes;
+ }
+
+ return array_key_exists($command, $this->commandRoutes) ? $this->commandRoutes[$command] : null;
+ }
+
+ /**
+ * @return Route[]
+ */
+ public function topic(string $topic): array
+ {
+ if (null === $this->topicRoutes) {
+ $topicRoutes = [];
+ foreach ($this->routes as $route) {
+ if ($route->isTopic()) {
+ $topicRoutes[$route->getSource()][$route->getProcessor()] = $route;
+ }
+ }
+
+ $this->topicRoutes = $topicRoutes;
+ }
+
+ return array_key_exists($topic, $this->topicRoutes) ? $this->topicRoutes[$topic] : [];
+ }
+
+ public function topicAndProcessor(string $topic, string $processor): ?Route
+ {
+ $routes = $this->topic($topic);
+ foreach ($routes as $route) {
+ if ($route->getProcessor() === $processor) {
+ return $route;
+ }
+ }
+
+ return null;
+ }
+
+ public function toArray(): array
+ {
+ $rawRoutes = [];
+ foreach ($this->routes as $route) {
+ $rawRoutes[] = $route->toArray();
+ }
+
+ return $rawRoutes;
+ }
+
+ public static function fromArray(array $rawRoutes): self
+ {
+ $routes = [];
+ foreach ($rawRoutes as $rawRoute) {
+ $routes[] = Route::fromArray($rawRoute);
+ }
+
+ return new self($routes);
+ }
+}
diff --git a/pkg/enqueue/Client/RouterProcessor.php b/pkg/enqueue/Client/RouterProcessor.php
index 09f53d1b9..c441ceb8b 100644
--- a/pkg/enqueue/Client/RouterProcessor.php
+++ b/pkg/enqueue/Client/RouterProcessor.php
@@ -2,65 +2,63 @@
namespace Enqueue\Client;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Message as PsrMessage;
-use Enqueue\Psr\Processor;
+use Enqueue\Consumption\Result;
+use Interop\Queue\Context;
+use Interop\Queue\Message as InteropMessage;
+use Interop\Queue\Processor;
-class RouterProcessor implements Processor
+final class RouterProcessor implements Processor
{
/**
- * @var DriverInterface
+ * compatibility with 0.8x.
*/
- private $driver;
+ private const COMMAND_TOPIC_08X = '__command__';
/**
- * @var array
+ * @var DriverInterface
*/
- private $routes;
+ private $driver;
- /**
- * @param DriverInterface $driver
- * @param array $routes
- */
- public function __construct(DriverInterface $driver, array $routes = [])
+ public function __construct(DriverInterface $driver)
{
$this->driver = $driver;
- $this->routes = $routes;
}
- /**
- * @param string $topicName
- * @param string $queueName
- * @param string $processorName
- */
- public function add($topicName, $queueName, $processorName)
+ public function process(InteropMessage $message, Context $context): Result
{
- $this->routes[$topicName][] = [$processorName, $queueName];
- }
+ // compatibility with 0.8x
+ if (self::COMMAND_TOPIC_08X === $message->getProperty(Config::TOPIC)) {
+ $clientMessage = $this->driver->createClientMessage($message);
+ $clientMessage->setProperty(Config::TOPIC, null);
- /**
- * {@inheritdoc}
- */
- public function process(PsrMessage $message, PsrContext $context)
- {
- $topicName = $message->getProperty(Config::PARAMETER_TOPIC_NAME);
- if (false == $topicName) {
- throw new \LogicException(sprintf(
- 'Got message without required parameter: "%s"',
- Config::PARAMETER_TOPIC_NAME
+ $this->driver->sendToProcessor($clientMessage);
+
+ return Result::ack('Legacy 0.8x message routed to processor');
+ }
+ // compatibility with 0.8x
+
+ if ($message->getProperty(Config::COMMAND)) {
+ return Result::reject(sprintf(
+ 'Unexpected command "%s" got. Command must not go to the router.',
+ $message->getProperty(Config::COMMAND)
));
}
- if (array_key_exists($topicName, $this->routes)) {
- foreach ($this->routes[$topicName] as $route) {
- $processorMessage = clone $message;
- $processorMessage->setProperty(Config::PARAMETER_PROCESSOR_NAME, $route[0]);
- $processorMessage->setProperty(Config::PARAMETER_PROCESSOR_QUEUE_NAME, $route[1]);
+ $topic = $message->getProperty(Config::TOPIC);
+ if (false == $topic) {
+ return Result::reject(sprintf('Topic property "%s" is required but not set or empty.', Config::TOPIC));
+ }
+
+ $count = 0;
+ foreach ($this->driver->getRouteCollection()->topic($topic) as $route) {
+ $clientMessage = $this->driver->createClientMessage($message);
+ $clientMessage->setProperty(Config::PROCESSOR, $route->getProcessor());
+
+ $this->driver->sendToProcessor($clientMessage);
- $this->driver->sendToProcessor($this->driver->createClientMessage($processorMessage));
- }
+ ++$count;
}
- return self::ACK;
+ return Result::ack(sprintf('Routed to "%d" event subscribers', $count));
}
}
diff --git a/pkg/enqueue/Client/SimpleClient.php b/pkg/enqueue/Client/SimpleClient.php
deleted file mode 100644
index 12ceac7f0..000000000
--- a/pkg/enqueue/Client/SimpleClient.php
+++ /dev/null
@@ -1,134 +0,0 @@
-context = $context;
- $this->config = $config ?: Config::create();
-
- $this->queueMetaRegistry = new QueueMetaRegistry($this->config, []);
- $this->queueMetaRegistry->add($this->config->getDefaultProcessorQueueName());
- $this->queueMetaRegistry->add($this->config->getRouterQueueName());
-
- $this->topicsMetaRegistry = new TopicMetaRegistry([]);
- $this->processorsRegistry = new ArrayProcessorRegistry();
-
- $this->driver = new AmqpDriver($context, $this->config, $this->queueMetaRegistry);
- $this->routerProcessor = new RouterProcessor($this->driver, []);
-
- $this->processorsRegistry->add($this->config->getRouterProcessorName(), $this->routerProcessor);
- $this->queueMetaRegistry->addProcessor($this->config->getRouterQueueName(), $this->routerProcessor);
- }
-
- /**
- * @param string $topic
- * @param callback
- */
- public function bind($topic, callable $processor)
- {
- $processorName = uniqid('', true);
- $queueName = $this->config->getDefaultProcessorQueueName();
-
- $this->topicsMetaRegistry->addProcessor($topic, $processorName);
- $this->queueMetaRegistry->addProcessor($queueName, $processorName);
- $this->processorsRegistry->add($processorName, new CallbackProcessor($processor));
-
- $this->routerProcessor->add($topic, $queueName, $processorName);
- }
-
- public function send($topic, $message)
- {
- $this->getProducer()->send($topic, $message);
- }
-
- public function consume(ExtensionInterface $runtimeExtension = null)
- {
- $this->driver->setupBroker();
-
- $processor = $this->getProcessor();
-
- $queueConsumer = new QueueConsumer($this->context, new ChainExtension([
- new SetRouterPropertiesExtension($this->driver),
- ]));
-
- $defaultQueueName = $this->config->getDefaultProcessorQueueName();
- $defaultTransportQueueName = $this->config->createTransportQueueName($defaultQueueName);
-
- $queueConsumer->bind($defaultTransportQueueName, $processor);
- if ($this->config->getRouterQueueName() != $defaultQueueName) {
- $routerTransportQueueName = $this->config->createTransportQueueName($this->config->getRouterQueueName());
-
- $queueConsumer->bind($routerTransportQueueName, $processor);
- }
-
- $queueConsumer->consume($runtimeExtension);
- }
-
- /**
- * @return MessageProducerInterface
- */
- private function getProducer()
- {
- $this->driver->setupBroker();
-
- return new MessageProducer($this->driver);
- }
-
- /**
- * @return DelegateProcessor
- */
- private function getProcessor()
- {
- return new DelegateProcessor($this->processorsRegistry);
- }
-}
diff --git a/pkg/enqueue/Client/SpoolProducer.php b/pkg/enqueue/Client/SpoolProducer.php
new file mode 100644
index 000000000..8ad0940f5
--- /dev/null
+++ b/pkg/enqueue/Client/SpoolProducer.php
@@ -0,0 +1,65 @@
+realProducer = $realProducer;
+
+ $this->events = new \SplQueue();
+ $this->commands = new \SplQueue();
+ }
+
+ public function sendCommand(string $command, $message, bool $needReply = false): ?Promise
+ {
+ if ($needReply) {
+ return $this->realProducer->sendCommand($command, $message, $needReply);
+ }
+
+ $this->commands->enqueue([$command, $message]);
+
+ return null;
+ }
+
+ public function sendEvent(string $topic, $message): void
+ {
+ $this->events->enqueue([$topic, $message]);
+ }
+
+ /**
+ * When it is called it sends all previously queued messages.
+ */
+ public function flush(): void
+ {
+ while (false == $this->events->isEmpty()) {
+ list($topic, $message) = $this->events->dequeue();
+
+ $this->realProducer->sendEvent($topic, $message);
+ }
+
+ while (false == $this->commands->isEmpty()) {
+ list($command, $message) = $this->commands->dequeue();
+
+ $this->realProducer->sendCommand($command, $message);
+ }
+ }
+}
diff --git a/pkg/enqueue/Client/TopicSubscriberInterface.php b/pkg/enqueue/Client/TopicSubscriberInterface.php
index 9d66d2cca..849a7827f 100644
--- a/pkg/enqueue/Client/TopicSubscriberInterface.php
+++ b/pkg/enqueue/Client/TopicSubscriberInterface.php
@@ -5,11 +5,37 @@
interface TopicSubscriberInterface
{
/**
- * * ['topicName']
- * * ['topicName' => ['processorName' => 'processor', 'destinationName' => 'destination']]
- * processorName, destinationName - optional.
+ * The result maybe either:.
*
- * @return array
+ * 'aTopicName'
+ *
+ * or
+ *
+ * ['aTopicName', 'anotherTopicName']
+ *
+ * or
+ *
+ * [
+ * [
+ * 'topic' => 'aTopicName',
+ * 'processor' => 'fooProcessor',
+ * 'queue' => 'a_client_queue_name',
+ *
+ * 'aCustomOption' => 'aVal',
+ * ],
+ * [
+ * 'topic' => 'anotherTopicName',
+ * 'processor' => 'barProcessor',
+ * 'queue' => 'a_client_queue_name',
+ *
+ * 'aCustomOption' => 'aVal',
+ * ],
+ * ]
+ *
+ * Note: If you set prefix_queue to true then the queue is used as is and therefor the driver is not used to prepare a transport queue name.
+ * It is possible to pass other options, they could be accessible on a route instance through options.
+ *
+ * @return string|array
*/
public static function getSubscribedTopics();
}
diff --git a/pkg/enqueue/Client/TraceableMessageProducer.php b/pkg/enqueue/Client/TraceableMessageProducer.php
deleted file mode 100644
index 7fa60b56a..000000000
--- a/pkg/enqueue/Client/TraceableMessageProducer.php
+++ /dev/null
@@ -1,87 +0,0 @@
-messageProducer = $messageProducer;
- }
-
- /**
- * {@inheritdoc}
- */
- public function send($topic, $message)
- {
- $this->messageProducer->send($topic, $message);
-
- $trace = [
- 'topic' => $topic,
- 'body' => $message,
- 'headers' => [],
- 'properties' => [],
- 'priority' => null,
- 'expire' => null,
- 'delay' => null,
- 'timestamp' => null,
- 'contentType' => null,
- 'messageId' => null,
- ];
- if ($message instanceof Message) {
- $trace['body'] = $message->getBody();
- $trace['headers'] = $message->getHeaders();
- $trace['properties'] = $message->getProperties();
- $trace['priority'] = $message->getPriority();
- $trace['expire'] = $message->getExpire();
- $trace['delay'] = $message->getDelay();
- $trace['timestamp'] = $message->getTimestamp();
- $trace['contentType'] = $message->getContentType();
- $trace['messageId'] = $message->getMessageId();
- }
-
- $this->traces[] = $trace;
- }
-
- /**
- * @param string $topic
- *
- * @return array
- */
- public function getTopicTraces($topic)
- {
- $topicTraces = [];
- foreach ($this->traces as $trace) {
- if ($topic == $trace['topic']) {
- $topicTraces[] = $trace;
- }
- }
-
- return $topicTraces;
- }
-
- /**
- * @return array
- */
- public function getTraces()
- {
- return $this->traces;
- }
-
- public function clearTraces()
- {
- $this->traces = [];
- }
-}
diff --git a/pkg/enqueue/Client/TraceableProducer.php b/pkg/enqueue/Client/TraceableProducer.php
new file mode 100644
index 000000000..b0bd613c3
--- /dev/null
+++ b/pkg/enqueue/Client/TraceableProducer.php
@@ -0,0 +1,105 @@
+producer = $producer;
+ }
+
+ public function sendEvent(string $topic, $message): void
+ {
+ $this->producer->sendEvent($topic, $message);
+
+ $this->collectTrace($topic, null, $message);
+ }
+
+ public function sendCommand(string $command, $message, bool $needReply = false): ?Promise
+ {
+ $result = $this->producer->sendCommand($command, $message, $needReply);
+
+ $this->collectTrace(null, $command, $message);
+
+ return $result;
+ }
+
+ public function getTopicTraces(string $topic): array
+ {
+ $topicTraces = [];
+ foreach ($this->traces as $trace) {
+ if ($topic == $trace['topic']) {
+ $topicTraces[] = $trace;
+ }
+ }
+
+ return $topicTraces;
+ }
+
+ public function getCommandTraces(string $command): array
+ {
+ $commandTraces = [];
+ foreach ($this->traces as $trace) {
+ if ($command == $trace['command']) {
+ $commandTraces[] = $trace;
+ }
+ }
+
+ return $commandTraces;
+ }
+
+ public function getTraces(): array
+ {
+ return $this->traces;
+ }
+
+ public function clearTraces(): void
+ {
+ $this->traces = [];
+ }
+
+ private function collectTrace(?string $topic, ?string $command, $message): void
+ {
+ $trace = [
+ 'topic' => $topic,
+ 'command' => $command,
+ 'body' => $message,
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ 'sentAt' => (new \DateTime())->format('Y-m-d H:i:s.u'),
+ ];
+
+ if ($message instanceof Message) {
+ $trace['body'] = $message->getBody();
+ $trace['headers'] = $message->getHeaders();
+ $trace['properties'] = $message->getProperties();
+ $trace['priority'] = $message->getPriority();
+ $trace['expire'] = $message->getExpire();
+ $trace['delay'] = $message->getDelay();
+ $trace['timestamp'] = $message->getTimestamp();
+ $trace['contentType'] = $message->getContentType();
+ $trace['messageId'] = $message->getMessageId();
+ }
+
+ $this->traces[] = $trace;
+ }
+}
diff --git a/pkg/enqueue/ConnectionFactoryFactory.php b/pkg/enqueue/ConnectionFactoryFactory.php
new file mode 100644
index 000000000..d23518c1b
--- /dev/null
+++ b/pkg/enqueue/ConnectionFactoryFactory.php
@@ -0,0 +1,69 @@
+ $config];
+ }
+
+ if (false == is_array($config)) {
+ throw new \InvalidArgumentException('The config must be either array or DSN string.');
+ }
+
+ if (false == array_key_exists('dsn', $config)) {
+ throw new \InvalidArgumentException('The config must have dsn key set.');
+ }
+
+ $dsn = Dsn::parseFirst($config['dsn']);
+
+ if ($factoryClass = $this->findFactoryClass($dsn, Resources::getAvailableConnections())) {
+ return new $factoryClass(1 === count($config) ? $config['dsn'] : $config);
+ }
+
+ $knownConnections = Resources::getKnownConnections();
+ if ($factoryClass = $this->findFactoryClass($dsn, $knownConnections)) {
+ throw new \LogicException(sprintf('To use given scheme "%s" a package has to be installed. Run "composer req %s" to add it.', $dsn->getScheme(), $knownConnections[$factoryClass]['package']));
+ }
+
+ throw new \LogicException(sprintf('A given scheme "%s" is not supported. Maybe it is a custom connection, make sure you registered it with "%s::addConnection".', $dsn->getScheme(), Resources::class));
+ }
+
+ private function findFactoryClass(Dsn $dsn, array $factories): ?string
+ {
+ $protocol = $dsn->getSchemeProtocol();
+
+ if ($dsn->getSchemeExtensions()) {
+ foreach ($factories as $connectionClass => $info) {
+ if (empty($info['supportedSchemeExtensions'])) {
+ continue;
+ }
+
+ if (false == in_array($protocol, $info['schemes'], true)) {
+ continue;
+ }
+
+ $diff = array_diff($info['supportedSchemeExtensions'], $dsn->getSchemeExtensions());
+ if (empty($diff)) {
+ return $connectionClass;
+ }
+ }
+ }
+
+ foreach ($factories as $driverClass => $info) {
+ if (false == in_array($protocol, $info['schemes'], true)) {
+ continue;
+ }
+
+ return $driverClass;
+ }
+
+ return null;
+ }
+}
diff --git a/pkg/enqueue/ConnectionFactoryFactoryInterface.php b/pkg/enqueue/ConnectionFactoryFactoryInterface.php
new file mode 100644
index 000000000..f4ca4a6d3
--- /dev/null
+++ b/pkg/enqueue/ConnectionFactoryFactoryInterface.php
@@ -0,0 +1,21 @@
+queue = $queue;
+ $this->processor = $processor;
+ }
+
+ public function getQueue(): Queue
+ {
+ return $this->queue;
+ }
+
+ public function getProcessor(): Processor
+ {
+ return $this->processor;
+ }
+}
diff --git a/pkg/enqueue/Consumption/CallbackProcessor.php b/pkg/enqueue/Consumption/CallbackProcessor.php
index d9ef19d73..d15978fcb 100644
--- a/pkg/enqueue/Consumption/CallbackProcessor.php
+++ b/pkg/enqueue/Consumption/CallbackProcessor.php
@@ -2,9 +2,9 @@
namespace Enqueue\Consumption;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Message;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Context;
+use Interop\Queue\Message as InteropMessage;
+use Interop\Queue\Processor;
class CallbackProcessor implements Processor
{
@@ -13,18 +13,12 @@ class CallbackProcessor implements Processor
*/
private $callback;
- /**
- * @param callable $callback
- */
public function __construct(callable $callback)
{
$this->callback = $callback;
}
- /**
- * {@inheritdoc}
- */
- public function process(Message $message, PsrContext $context)
+ public function process(InteropMessage $message, Context $context)
{
return call_user_func($this->callback, $message, $context);
}
diff --git a/pkg/enqueue/Consumption/ChainExtension.php b/pkg/enqueue/Consumption/ChainExtension.php
index 17f5db659..83b4eba3a 100644
--- a/pkg/enqueue/Consumption/ChainExtension.php
+++ b/pkg/enqueue/Consumption/ChainExtension.php
@@ -2,80 +2,193 @@
namespace Enqueue\Consumption;
+use Enqueue\Consumption\Context\End;
+use Enqueue\Consumption\Context\InitLogger;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\Context\MessageResult;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\Context\PreSubscribe;
+use Enqueue\Consumption\Context\ProcessorException;
+use Enqueue\Consumption\Context\Start;
+
class ChainExtension implements ExtensionInterface
{
- use EmptyExtensionTrait;
-
- /**
- * @var ExtensionInterface[]
- */
- private $extensions;
+ private $startExtensions;
+ private $initLoggerExtensions;
+ private $preSubscribeExtensions;
+ private $preConsumeExtensions;
+ private $messageReceivedExtensions;
+ private $messageResultExtensions;
+ private $postMessageReceivedExtensions;
+ private $processorExceptionExtensions;
+ private $postConsumeExtensions;
+ private $endExtensions;
- /**
- * @param ExtensionInterface[] $extensions
- */
public function __construct(array $extensions)
{
- $this->extensions = $extensions;
+ $this->startExtensions = [];
+ $this->initLoggerExtensions = [];
+ $this->preSubscribeExtensions = [];
+ $this->preConsumeExtensions = [];
+ $this->messageReceivedExtensions = [];
+ $this->messageResultExtensions = [];
+ $this->postMessageReceivedExtensions = [];
+ $this->processorExceptionExtensions = [];
+ $this->postConsumeExtensions = [];
+ $this->endExtensions = [];
+
+ array_walk($extensions, function ($extension) {
+ if ($extension instanceof ExtensionInterface) {
+ $this->startExtensions[] = $extension;
+ $this->initLoggerExtensions[] = $extension;
+ $this->preSubscribeExtensions[] = $extension;
+ $this->preConsumeExtensions[] = $extension;
+ $this->messageReceivedExtensions[] = $extension;
+ $this->messageResultExtensions[] = $extension;
+ $this->postMessageReceivedExtensions[] = $extension;
+ $this->processorExceptionExtensions[] = $extension;
+ $this->postConsumeExtensions[] = $extension;
+ $this->endExtensions[] = $extension;
+
+ return;
+ }
+
+ $extensionValid = false;
+ if ($extension instanceof StartExtensionInterface) {
+ $this->startExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof InitLoggerExtensionInterface) {
+ $this->initLoggerExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PreSubscribeExtensionInterface) {
+ $this->preSubscribeExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PreConsumeExtensionInterface) {
+ $this->preConsumeExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof MessageReceivedExtensionInterface) {
+ $this->messageReceivedExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof MessageResultExtensionInterface) {
+ $this->messageResultExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof ProcessorExceptionExtensionInterface) {
+ $this->processorExceptionExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PostMessageReceivedExtensionInterface) {
+ $this->postMessageReceivedExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof PostConsumeExtensionInterface) {
+ $this->postConsumeExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if ($extension instanceof EndExtensionInterface) {
+ $this->endExtensions[] = $extension;
+
+ $extensionValid = true;
+ }
+
+ if (false == $extensionValid) {
+ throw new \LogicException(sprintf('Invalid extension given %s', $extension::class));
+ }
+ });
+ }
+
+ public function onInitLogger(InitLogger $context): void
+ {
+ foreach ($this->initLoggerExtensions as $extension) {
+ $extension->onInitLogger($context);
+ }
}
- /**
- * @param Context $context
- */
- public function onStart(Context $context)
+ public function onStart(Start $context): void
{
- foreach ($this->extensions as $extension) {
+ foreach ($this->startExtensions as $extension) {
$extension->onStart($context);
}
}
- /**
- * @param Context $context
- */
- public function onBeforeReceive(Context $context)
+ public function onPreSubscribe(PreSubscribe $context): void
+ {
+ foreach ($this->preSubscribeExtensions as $extension) {
+ $extension->onPreSubscribe($context);
+ }
+ }
+
+ public function onPreConsume(PreConsume $context): void
+ {
+ foreach ($this->preConsumeExtensions as $extension) {
+ $extension->onPreConsume($context);
+ }
+ }
+
+ public function onMessageReceived(MessageReceived $context): void
+ {
+ foreach ($this->messageReceivedExtensions as $extension) {
+ $extension->onMessageReceived($context);
+ }
+ }
+
+ public function onResult(MessageResult $context): void
{
- foreach ($this->extensions as $extension) {
- $extension->onBeforeReceive($context);
+ foreach ($this->messageResultExtensions as $extension) {
+ $extension->onResult($context);
}
}
- /**
- * @param Context $context
- */
- public function onPreReceived(Context $context)
+ public function onProcessorException(ProcessorException $context): void
{
- foreach ($this->extensions as $extension) {
- $extension->onPreReceived($context);
+ foreach ($this->processorExceptionExtensions as $extension) {
+ $extension->onProcessorException($context);
}
}
- /**
- * @param Context $context
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
- foreach ($this->extensions as $extension) {
- $extension->onPostReceived($context);
+ foreach ($this->postMessageReceivedExtensions as $extension) {
+ $extension->onPostMessageReceived($context);
}
}
- /**
- * @param Context $context
- */
- public function onIdle(Context $context)
+ public function onPostConsume(PostConsume $context): void
{
- foreach ($this->extensions as $extension) {
- $extension->onIdle($context);
+ foreach ($this->postConsumeExtensions as $extension) {
+ $extension->onPostConsume($context);
}
}
- /**
- * @param Context $context
- */
- public function onInterrupted(Context $context)
+ public function onEnd(End $context): void
{
- foreach ($this->extensions as $extension) {
- $extension->onInterrupted($context);
+ foreach ($this->endExtensions as $extension) {
+ $extension->onEnd($context);
}
}
}
diff --git a/pkg/enqueue/Consumption/Context.php b/pkg/enqueue/Consumption/Context.php
deleted file mode 100644
index d753ff806..000000000
--- a/pkg/enqueue/Consumption/Context.php
+++ /dev/null
@@ -1,233 +0,0 @@
-psrContext = $psrContext;
-
- $this->executionInterrupted = false;
- }
-
- /**
- * @return \Enqueue\Psr\Message
- */
- public function getPsrMessage()
- {
- return $this->psrMessage;
- }
-
- /**
- * @param Message $psrMessage
- */
- public function setPsrMessage(Message $psrMessage)
- {
- if ($this->psrMessage) {
- throw new IllegalContextModificationException('The message could be set once');
- }
-
- $this->psrMessage = $psrMessage;
- }
-
- /**
- * @return PsrContext
- */
- public function getPsrContext()
- {
- return $this->psrContext;
- }
-
- /**
- * @return Consumer
- */
- public function getPsrConsumer()
- {
- return $this->psrConsumer;
- }
-
- /**
- * @param Consumer $psrConsumer
- */
- public function setPsrConsumer(Consumer $psrConsumer)
- {
- if ($this->psrConsumer) {
- throw new IllegalContextModificationException('The message consumer could be set once');
- }
-
- $this->psrConsumer = $psrConsumer;
- }
-
- /**
- * @return Processor
- */
- public function getPsrProcessor()
- {
- return $this->psrProcessor;
- }
-
- /**
- * @param Processor $psrProcessor
- */
- public function setPsrProcessor(Processor $psrProcessor)
- {
- if ($this->psrProcessor) {
- throw new IllegalContextModificationException('The message processor could be set once');
- }
-
- $this->psrProcessor = $psrProcessor;
- }
-
- /**
- * @return \Exception
- */
- public function getException()
- {
- return $this->exception;
- }
-
- /**
- * @param \Exception $exception
- */
- public function setException(\Exception $exception)
- {
- $this->exception = $exception;
- }
-
- /**
- * @return Result|string
- */
- public function getResult()
- {
- return $this->result;
- }
-
- /**
- * @param Result|string $result
- */
- public function setResult($result)
- {
- if ($this->result) {
- throw new IllegalContextModificationException('The result modification is not allowed');
- }
-
- $this->result = $result;
- }
-
- /**
- * @return bool
- */
- public function isExecutionInterrupted()
- {
- return $this->executionInterrupted;
- }
-
- /**
- * @param bool $executionInterrupted
- */
- public function setExecutionInterrupted($executionInterrupted)
- {
- if (false == $executionInterrupted && $this->executionInterrupted) {
- throw new IllegalContextModificationException('The execution once interrupted could not be roll backed');
- }
-
- $this->executionInterrupted = $executionInterrupted;
- }
-
- /**
- * @return LoggerInterface
- */
- public function getLogger()
- {
- return $this->logger;
- }
-
- /**
- * @param LoggerInterface $logger
- */
- public function setLogger(LoggerInterface $logger)
- {
- if ($this->logger) {
- throw new IllegalContextModificationException('The logger modification is not allowed');
- }
-
- $this->logger = $logger;
- }
-
- /**
- * @return \Enqueue\Psr\Queue
- */
- public function getPsrQueue()
- {
- return $this->psrQueue;
- }
-
- /**
- * @param \Enqueue\Psr\Queue $psrQueue
- */
- public function setPsrQueue(Queue $psrQueue)
- {
- if ($this->psrQueue) {
- throw new IllegalContextModificationException('The queue modification is not allowed');
- }
-
- $this->psrQueue = $psrQueue;
- }
-}
diff --git a/pkg/enqueue/Consumption/Context/End.php b/pkg/enqueue/Consumption/Context/End.php
new file mode 100644
index 000000000..07853b3d3
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/End.php
@@ -0,0 +1,79 @@
+context = $context;
+ $this->logger = $logger;
+ $this->startTime = $startTime;
+ $this->endTime = $endTime;
+ $this->exitStatus = $exitStatus;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ /**
+ * In milliseconds.
+ */
+ public function getStartTime(): int
+ {
+ return $this->startTime;
+ }
+
+ /**
+ * In milliseconds.
+ */
+ public function getEndTime(): int
+ {
+ return $this->startTime;
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/InitLogger.php b/pkg/enqueue/Consumption/Context/InitLogger.php
new file mode 100644
index 000000000..c48057268
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/InitLogger.php
@@ -0,0 +1,28 @@
+logger = $logger;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function changeLogger(LoggerInterface $logger): void
+ {
+ $this->logger = $logger;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/MessageReceived.php b/pkg/enqueue/Consumption/Context/MessageReceived.php
new file mode 100644
index 000000000..35abf1ca8
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/MessageReceived.php
@@ -0,0 +1,109 @@
+context = $context;
+ $this->consumer = $consumer;
+ $this->message = $message;
+ $this->processor = $processor;
+ $this->receivedAt = $receivedAt;
+ $this->logger = $logger;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getConsumer(): Consumer
+ {
+ return $this->consumer;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getProcessor(): Processor
+ {
+ return $this->processor;
+ }
+
+ public function changeProcessor(Processor $processor): void
+ {
+ $this->processor = $processor;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getReceivedAt(): int
+ {
+ return $this->receivedAt;
+ }
+
+ public function getResult(): ?Result
+ {
+ return $this->result;
+ }
+
+ public function setResult(Result $result): void
+ {
+ $this->result = $result;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/MessageResult.php b/pkg/enqueue/Consumption/Context/MessageResult.php
new file mode 100644
index 000000000..4fa8f7de0
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/MessageResult.php
@@ -0,0 +1,93 @@
+context = $context;
+ $this->consumer = $consumer;
+ $this->message = $message;
+ $this->logger = $logger;
+ $this->result = $result;
+ $this->receivedAt = $receivedAt;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getConsumer(): Consumer
+ {
+ return $this->consumer;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getReceivedAt(): int
+ {
+ return $this->receivedAt;
+ }
+
+ /**
+ * @return Result|object|string|null
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ /**
+ * @param Result|string|object|null $result
+ */
+ public function changeResult($result): void
+ {
+ $this->result = $result;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/PostConsume.php b/pkg/enqueue/Consumption/Context/PostConsume.php
new file mode 100644
index 000000000..a6f1d8375
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/PostConsume.php
@@ -0,0 +1,108 @@
+context = $context;
+ $this->subscriptionConsumer = $subscriptionConsumer;
+ $this->receivedMessagesCount = $receivedMessagesCount;
+ $this->cycle = $cycle;
+ $this->startTime = $startTime;
+ $this->logger = $logger;
+
+ $this->executionInterrupted = false;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getSubscriptionConsumer(): SubscriptionConsumer
+ {
+ return $this->subscriptionConsumer;
+ }
+
+ public function getReceivedMessagesCount(): int
+ {
+ return $this->receivedMessagesCount;
+ }
+
+ public function getCycle(): int
+ {
+ return $this->cycle;
+ }
+
+ public function getStartTime(): int
+ {
+ return $this->startTime;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+
+ public function isExecutionInterrupted(): bool
+ {
+ return $this->executionInterrupted;
+ }
+
+ public function interruptExecution(?int $exitStatus = null): void
+ {
+ $this->exitStatus = $exitStatus;
+ $this->executionInterrupted = true;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/PostMessageReceived.php b/pkg/enqueue/Consumption/Context/PostMessageReceived.php
new file mode 100644
index 000000000..23df2c849
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/PostMessageReceived.php
@@ -0,0 +1,119 @@
+context = $context;
+ $this->consumer = $consumer;
+ $this->message = $message;
+ $this->result = $result;
+ $this->receivedAt = $receivedAt;
+ $this->logger = $logger;
+
+ $this->executionInterrupted = false;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getConsumer(): Consumer
+ {
+ return $this->consumer;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getReceivedAt(): int
+ {
+ return $this->receivedAt;
+ }
+
+ /**
+ * @return Result|object|string|null
+ */
+ public function getResult()
+ {
+ return $this->result;
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+
+ public function isExecutionInterrupted(): bool
+ {
+ return $this->executionInterrupted;
+ }
+
+ public function interruptExecution(?int $exitStatus = null): void
+ {
+ $this->exitStatus = $exitStatus;
+ $this->executionInterrupted = true;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/PreConsume.php b/pkg/enqueue/Consumption/Context/PreConsume.php
new file mode 100644
index 000000000..77cc7d030
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/PreConsume.php
@@ -0,0 +1,108 @@
+context = $context;
+ $this->subscriptionConsumer = $subscriptionConsumer;
+ $this->logger = $logger;
+ $this->cycle = $cycle;
+ $this->receiveTimeout = $receiveTimeout;
+ $this->startTime = $startTime;
+
+ $this->executionInterrupted = false;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getSubscriptionConsumer(): SubscriptionConsumer
+ {
+ return $this->subscriptionConsumer;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getCycle(): int
+ {
+ return $this->cycle;
+ }
+
+ public function getReceiveTimeout(): int
+ {
+ return $this->receiveTimeout;
+ }
+
+ public function getStartTime(): int
+ {
+ return $this->startTime;
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+
+ public function isExecutionInterrupted(): bool
+ {
+ return $this->executionInterrupted;
+ }
+
+ public function interruptExecution(?int $exitStatus = null): void
+ {
+ $this->exitStatus = $exitStatus;
+ $this->executionInterrupted = true;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/PreSubscribe.php b/pkg/enqueue/Consumption/Context/PreSubscribe.php
new file mode 100644
index 000000000..dbc74bb69
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/PreSubscribe.php
@@ -0,0 +1,59 @@
+context = $context;
+ $this->processor = $processor;
+ $this->consumer = $consumer;
+ $this->logger = $logger;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getProcessor(): Processor
+ {
+ return $this->processor;
+ }
+
+ public function getConsumer(): Consumer
+ {
+ return $this->consumer;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/ProcessorException.php b/pkg/enqueue/Consumption/Context/ProcessorException.php
new file mode 100644
index 000000000..329b13d93
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/ProcessorException.php
@@ -0,0 +1,96 @@
+context = $context;
+ $this->consumer = $consumer;
+ $this->message = $message;
+ $this->exception = $exception;
+ $this->logger = $logger;
+ $this->receivedAt = $receivedAt;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getConsumer(): Consumer
+ {
+ return $this->consumer;
+ }
+
+ public function getMessage(): Message
+ {
+ return $this->message;
+ }
+
+ public function getException(): \Throwable
+ {
+ return $this->exception;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ public function getReceivedAt(): int
+ {
+ return $this->receivedAt;
+ }
+
+ public function getResult(): ?Result
+ {
+ return $this->result;
+ }
+
+ public function setResult(Result $result): void
+ {
+ $this->result = $result;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Context/Start.php b/pkg/enqueue/Consumption/Context/Start.php
new file mode 100644
index 000000000..84db29c44
--- /dev/null
+++ b/pkg/enqueue/Consumption/Context/Start.php
@@ -0,0 +1,128 @@
+context = $context;
+ $this->logger = $logger;
+ $this->processors = $processors;
+ $this->receiveTimeout = $receiveTimeout;
+ $this->startTime = $startTime;
+
+ $this->executionInterrupted = false;
+ }
+
+ public function getContext(): Context
+ {
+ return $this->context;
+ }
+
+ public function getLogger(): LoggerInterface
+ {
+ return $this->logger;
+ }
+
+ /**
+ * In milliseconds.
+ */
+ public function getReceiveTimeout(): int
+ {
+ return $this->receiveTimeout;
+ }
+
+ /**
+ * In milliseconds.
+ */
+ public function changeReceiveTimeout(int $timeout): void
+ {
+ $this->receiveTimeout = $timeout;
+ }
+
+ /**
+ * In milliseconds.
+ */
+ public function getStartTime(): int
+ {
+ return $this->startTime;
+ }
+
+ /**
+ * @return BoundProcessor[]
+ */
+ public function getBoundProcessors(): array
+ {
+ return $this->processors;
+ }
+
+ /**
+ * @param BoundProcessor[] $processors
+ */
+ public function changeBoundProcessors(array $processors): void
+ {
+ $this->processors = [];
+ array_walk($processors, function (BoundProcessor $processor) {
+ $this->processors[] = $processor;
+ });
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+
+ public function isExecutionInterrupted(): bool
+ {
+ return $this->executionInterrupted;
+ }
+
+ public function interruptExecution(?int $exitStatus = null): void
+ {
+ $this->exitStatus = $exitStatus;
+ $this->executionInterrupted = true;
+ }
+}
diff --git a/pkg/enqueue/Consumption/EmptyExtensionTrait.php b/pkg/enqueue/Consumption/EmptyExtensionTrait.php
deleted file mode 100644
index c98ed8da3..000000000
--- a/pkg/enqueue/Consumption/EmptyExtensionTrait.php
+++ /dev/null
@@ -1,48 +0,0 @@
-exitStatus = $context->getExitStatus();
+ }
+
+ public function getExitStatus(): ?int
+ {
+ return $this->exitStatus;
+ }
+}
diff --git a/pkg/enqueue/Consumption/Extension/LimitConsumedMessagesExtension.php b/pkg/enqueue/Consumption/Extension/LimitConsumedMessagesExtension.php
index ef6ec527e..0dc6feceb 100644
--- a/pkg/enqueue/Consumption/Extension/LimitConsumedMessagesExtension.php
+++ b/pkg/enqueue/Consumption/Extension/LimitConsumedMessagesExtension.php
@@ -2,14 +2,14 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
+use Enqueue\Consumption\PreConsumeExtensionInterface;
+use Psr\Log\LoggerInterface;
-class LimitConsumedMessagesExtension implements ExtensionInterface
+class LimitConsumedMessagesExtension implements PreConsumeExtensionInterface, PostMessageReceivedExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var int
*/
@@ -20,54 +20,41 @@ class LimitConsumedMessagesExtension implements ExtensionInterface
*/
protected $messageConsumed;
- /**
- * @param int $messageLimit
- */
- public function __construct($messageLimit)
+ public function __construct(int $messageLimit)
{
- if (false == is_int($messageLimit)) {
- throw new \InvalidArgumentException(sprintf(
- 'Expected message limit is int but got: "%s"',
- is_object($messageLimit) ? get_class($messageLimit) : gettype($messageLimit)
- ));
- }
-
$this->messageLimit = $messageLimit;
$this->messageConsumed = 0;
}
- /**
- * {@inheritdoc}
- */
- public function onBeforeReceive(Context $context)
+ public function onPreConsume(PreConsume $context): void
{
// this is added here to handle an edge case. when a user sets zero as limit.
- $this->checkMessageLimit($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
++$this->messageConsumed;
- $this->checkMessageLimit($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * @param Context $context
- */
- protected function checkMessageLimit(Context $context)
+ protected function shouldBeStopped(LoggerInterface $logger): bool
{
if ($this->messageConsumed >= $this->messageLimit) {
- $context->getLogger()->debug(sprintf(
+ $logger->debug(sprintf(
'[LimitConsumedMessagesExtension] Message consumption is interrupted since the message limit reached.'.
' limit: "%s"',
$this->messageLimit
));
- $context->setExecutionInterrupted(true);
+ return true;
}
+
+ return false;
}
}
diff --git a/pkg/enqueue/Consumption/Extension/LimitConsumerMemoryExtension.php b/pkg/enqueue/Consumption/Extension/LimitConsumerMemoryExtension.php
index c03686f33..7edbf232c 100644
--- a/pkg/enqueue/Consumption/Extension/LimitConsumerMemoryExtension.php
+++ b/pkg/enqueue/Consumption/Extension/LimitConsumerMemoryExtension.php
@@ -2,14 +2,16 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\PostConsumeExtensionInterface;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
+use Enqueue\Consumption\PreConsumeExtensionInterface;
+use Psr\Log\LoggerInterface;
-class LimitConsumerMemoryExtension implements ExtensionInterface
+class LimitConsumerMemoryExtension implements PreConsumeExtensionInterface, PostMessageReceivedExtensionInterface, PostConsumeExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var int
*/
@@ -21,53 +23,46 @@ class LimitConsumerMemoryExtension implements ExtensionInterface
public function __construct($memoryLimit)
{
if (false == is_int($memoryLimit)) {
- throw new \InvalidArgumentException(sprintf(
- 'Expected memory limit is int but got: "%s"',
- is_object($memoryLimit) ? get_class($memoryLimit) : gettype($memoryLimit)
- ));
+ throw new \InvalidArgumentException(sprintf('Expected memory limit is int but got: "%s"', is_object($memoryLimit) ? $memoryLimit::class : gettype($memoryLimit)));
}
$this->memoryLimit = $memoryLimit * 1024 * 1024;
}
- /**
- * {@inheritdoc}
- */
- public function onBeforeReceive(Context $context)
+ public function onPreConsume(PreConsume $context): void
{
- $this->checkMemory($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
- $this->checkMemory($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onIdle(Context $context)
+ public function onPostConsume(PostConsume $context): void
{
- $this->checkMemory($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * @param Context $context
- */
- protected function checkMemory(Context $context)
+ protected function shouldBeStopped(LoggerInterface $logger): bool
{
$memoryUsage = memory_get_usage(true);
if ($memoryUsage >= $this->memoryLimit) {
- $context->getLogger()->debug(sprintf(
+ $logger->debug(sprintf(
'[LimitConsumerMemoryExtension] Interrupt execution as memory limit reached. limit: "%s", used: "%s"',
$this->memoryLimit,
$memoryUsage
));
- $context->setExecutionInterrupted(true);
+ return true;
}
+
+ return false;
}
}
diff --git a/pkg/enqueue/Consumption/Extension/LimitConsumptionTimeExtension.php b/pkg/enqueue/Consumption/Extension/LimitConsumptionTimeExtension.php
index 65221f5c0..1953aa2e6 100644
--- a/pkg/enqueue/Consumption/Extension/LimitConsumptionTimeExtension.php
+++ b/pkg/enqueue/Consumption/Extension/LimitConsumptionTimeExtension.php
@@ -2,66 +2,61 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
-
-class LimitConsumptionTimeExtension implements ExtensionInterface
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\PostConsumeExtensionInterface;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
+use Enqueue\Consumption\PreConsumeExtensionInterface;
+use Psr\Log\LoggerInterface;
+
+class LimitConsumptionTimeExtension implements PreConsumeExtensionInterface, PostConsumeExtensionInterface, PostMessageReceivedExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var \DateTime
*/
protected $timeLimit;
- /**
- * @param \DateTime $timeLimit
- */
public function __construct(\DateTime $timeLimit)
{
$this->timeLimit = $timeLimit;
}
- /**
- * {@inheritdoc}
- */
- public function onBeforeReceive(Context $context)
+ public function onPreConsume(PreConsume $context): void
{
- $this->checkTime($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onIdle(Context $context)
+ public function onPostConsume(PostConsume $context): void
{
- $this->checkTime($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
- $this->checkTime($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * @param Context $context
- */
- protected function checkTime(Context $context)
+ protected function shouldBeStopped(LoggerInterface $logger): bool
{
$now = new \DateTime();
if ($now >= $this->timeLimit) {
- $context->getLogger()->debug(sprintf(
+ $logger->debug(sprintf(
'[LimitConsumptionTimeExtension] Execution interrupted as limit time has passed.'.
' now: "%s", time-limit: "%s"',
- $now->format(DATE_ISO8601),
- $this->timeLimit->format(DATE_ISO8601)
+ $now->format(\DATE_ISO8601),
+ $this->timeLimit->format(\DATE_ISO8601)
));
- $context->setExecutionInterrupted(true);
+ return true;
}
+
+ return false;
}
}
diff --git a/pkg/enqueue/Consumption/Extension/LogExtension.php b/pkg/enqueue/Consumption/Extension/LogExtension.php
new file mode 100644
index 000000000..14383c4d1
--- /dev/null
+++ b/pkg/enqueue/Consumption/Extension/LogExtension.php
@@ -0,0 +1,67 @@
+getLogger()->debug('Consumption has started');
+ }
+
+ public function onEnd(End $context): void
+ {
+ $context->getLogger()->debug('Consumption has ended');
+ }
+
+ public function onMessageReceived(MessageReceived $context): void
+ {
+ $message = $context->getMessage();
+
+ $context->getLogger()->debug("Received from {queueName}\t{body}", [
+ 'queueName' => $context->getConsumer()->getQueue()->getQueueName(),
+ 'redelivered' => $message->isRedelivered(),
+ 'body' => Stringify::that($message->getBody()),
+ 'properties' => Stringify::that($message->getProperties()),
+ 'headers' => Stringify::that($message->getHeaders()),
+ ]);
+ }
+
+ public function onPostMessageReceived(PostMessageReceived $context): void
+ {
+ $message = $context->getMessage();
+ $queue = $context->getConsumer()->getQueue();
+ $result = $context->getResult();
+
+ $reason = '';
+ $logMessage = "Processed from {queueName}\t{body}\t{result}";
+ if ($result instanceof Result && $result->getReason()) {
+ $reason = $result->getReason();
+ $logMessage .= ' {reason}';
+ }
+ $logContext = [
+ 'result' => str_replace('enqueue.', '', $result),
+ 'reason' => $reason,
+ 'queueName' => $queue->getQueueName(),
+ 'body' => Stringify::that($message->getBody()),
+ 'properties' => Stringify::that($message->getProperties()),
+ 'headers' => Stringify::that($message->getHeaders()),
+ ];
+
+ $logLevel = Result::REJECT == ((string) $result) ? LogLevel::ERROR : LogLevel::INFO;
+
+ $context->getLogger()->log($logLevel, $logMessage, $logContext);
+ }
+}
diff --git a/pkg/enqueue/Consumption/Extension/LoggerExtension.php b/pkg/enqueue/Consumption/Extension/LoggerExtension.php
index 57823bca0..90e92be8a 100644
--- a/pkg/enqueue/Consumption/Extension/LoggerExtension.php
+++ b/pkg/enqueue/Consumption/Extension/LoggerExtension.php
@@ -2,81 +2,30 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
-use Enqueue\Consumption\Result;
-use Enqueue\Psr\Message;
+use Enqueue\Consumption\Context\InitLogger;
+use Enqueue\Consumption\InitLoggerExtensionInterface;
use Psr\Log\LoggerInterface;
-class LoggerExtension implements ExtensionInterface
+class LoggerExtension implements InitLoggerExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var LoggerInterface
*/
private $logger;
- /**
- * @param LoggerInterface $logger
- */
public function __construct(LoggerInterface $logger)
{
$this->logger = $logger;
}
- /**
- * {@inheritdoc}
- */
- public function onStart(Context $context)
- {
- $context->setLogger($this->logger);
- $this->logger->debug(sprintf('Set context\'s logger %s', get_class($this->logger)));
- }
-
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onInitLogger(InitLogger $context): void
{
- if (false == $context->getResult() instanceof Result) {
- return;
- }
-
- /** @var $result Result */
- $result = $context->getResult();
+ $previousLogger = $context->getLogger();
- switch ($result->getStatus()) {
- case Result::REJECT:
- case Result::REQUEUE:
- if ($result->getReason()) {
- $this->logger->error($result->getReason(), $this->messageToLogContext($context->getPsrMessage()));
- }
+ if ($previousLogger !== $this->logger) {
+ $context->changeLogger($this->logger);
- break;
- case Result::ACK:
- if ($result->getReason()) {
- $this->logger->info($result->getReason(), $this->messageToLogContext($context->getPsrMessage()));
- }
-
- break;
- default:
- throw new \LogicException(sprintf('Got unexpected message result. "%s"', $result->getStatus()));
+ $this->logger->debug(sprintf('Change logger from "%s" to "%s"', $previousLogger::class, get_class($this->logger)));
}
}
-
- /**
- * @param Message $message
- *
- * @return array
- */
- private function messageToLogContext(Message $message)
- {
- return [
- 'body' => $message->getBody(),
- 'headers' => $message->getHeaders(),
- 'properties' => $message->getProperties(),
- ];
- }
}
diff --git a/pkg/enqueue/Consumption/Extension/NicenessExtension.php b/pkg/enqueue/Consumption/Extension/NicenessExtension.php
new file mode 100644
index 000000000..436a8ec0f
--- /dev/null
+++ b/pkg/enqueue/Consumption/Extension/NicenessExtension.php
@@ -0,0 +1,38 @@
+niceness = $niceness;
+ }
+
+ public function onStart(Start $context): void
+ {
+ if (0 !== $this->niceness) {
+ $changed = @proc_nice($this->niceness);
+ if (!$changed) {
+ throw new \InvalidArgumentException(sprintf('Cannot change process niceness, got warning: "%s"', error_get_last()['message']));
+ }
+ }
+ }
+}
diff --git a/pkg/enqueue/Consumption/Extension/ReplyExtension.php b/pkg/enqueue/Consumption/Extension/ReplyExtension.php
index d1f00443c..c1ac19bd7 100644
--- a/pkg/enqueue/Consumption/Extension/ReplyExtension.php
+++ b/pkg/enqueue/Consumption/Extension/ReplyExtension.php
@@ -2,22 +2,15 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
use Enqueue\Consumption\Result;
-class ReplyExtension implements ExtensionInterface
+class ReplyExtension implements PostMessageReceivedExtensionInterface
{
- use EmptyExtensionTrait;
-
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
- $replyTo = $context->getPsrMessage()->getReplyTo();
- $correlationId = $context->getPsrMessage()->getCorrelationId();
+ $replyTo = $context->getMessage()->getReplyTo();
if (false == $replyTo) {
return;
}
@@ -25,18 +18,20 @@ public function onPostReceived(Context $context)
/** @var Result $result */
$result = $context->getResult();
if (false == $result instanceof Result) {
- throw new \LogicException('To send a reply an instance of Result class has to returned from a Processor.');
+ return;
}
if (false == $result->getReply()) {
- throw new \LogicException('To send a reply the Result must contain a reply message.');
+ return;
}
+ $correlationId = $context->getMessage()->getCorrelationId();
$replyMessage = clone $result->getReply();
$replyMessage->setCorrelationId($correlationId);
- $replyQueue = $context->getPsrContext()->createQueue($replyTo);
+ $replyQueue = $context->getContext()->createQueue($replyTo);
- $context->getPsrContext()->createProducer()->send($replyQueue, $replyMessage);
+ $context->getLogger()->debug(sprintf('[ReplyExtension] Send reply to "%s"', $replyTo));
+ $context->getContext()->createProducer()->send($replyQueue, $replyMessage);
}
}
diff --git a/pkg/enqueue/Consumption/Extension/SignalExtension.php b/pkg/enqueue/Consumption/Extension/SignalExtension.php
index a3bab8500..8ea5307d5 100644
--- a/pkg/enqueue/Consumption/Extension/SignalExtension.php
+++ b/pkg/enqueue/Consumption/Extension/SignalExtension.php
@@ -2,113 +2,99 @@
namespace Enqueue\Consumption\Extension;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\Context\Start;
use Enqueue\Consumption\Exception\LogicException;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\PostConsumeExtensionInterface;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
+use Enqueue\Consumption\PreConsumeExtensionInterface;
+use Enqueue\Consumption\StartExtensionInterface;
use Psr\Log\LoggerInterface;
-class SignalExtension implements ExtensionInterface
+class SignalExtension implements StartExtensionInterface, PreConsumeExtensionInterface, PostMessageReceivedExtensionInterface, PostConsumeExtensionInterface
{
- use EmptyExtensionTrait;
-
/**
* @var bool
*/
protected $interruptConsumption = false;
/**
- * @var LoggerInterface
+ * @var LoggerInterface|null
*/
protected $logger;
- /**
- * {@inheritdoc}
- */
- public function onStart(Context $context)
+ public function onStart(Start $context): void
{
if (false == extension_loaded('pcntl')) {
throw new LogicException('The pcntl extension is required in order to catch signals.');
}
- pcntl_signal(SIGTERM, [$this, 'handleSignal']);
- pcntl_signal(SIGQUIT, [$this, 'handleSignal']);
- pcntl_signal(SIGINT, [$this, 'handleSignal']);
+ pcntl_async_signals(true);
+ pcntl_signal(\SIGTERM, [$this, 'handleSignal']);
+ pcntl_signal(\SIGQUIT, [$this, 'handleSignal']);
+ pcntl_signal(\SIGINT, [$this, 'handleSignal']);
+
+ $this->logger = $context->getLogger();
$this->interruptConsumption = false;
}
- /**
- * @param Context $context
- */
- public function onBeforeReceive(Context $context)
+ public function onPreConsume(PreConsume $context): void
{
$this->logger = $context->getLogger();
- pcntl_signal_dispatch();
-
- $this->interruptExecutionIfNeeded($context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function onPreReceived(Context $context)
- {
- $this->interruptExecutionIfNeeded($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * {@inheritdoc}
- */
- public function onPostReceived(Context $context)
+ public function onPostMessageReceived(PostMessageReceived $context): void
{
- pcntl_signal_dispatch();
-
- $this->interruptExecutionIfNeeded($context);
- }
-
- /**
- * {@inheritdoc}
- */
- public function onIdle(Context $context)
- {
- pcntl_signal_dispatch();
-
- $this->interruptExecutionIfNeeded($context);
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
+ }
}
- /**
- * @param Context $context
- */
- public function interruptExecutionIfNeeded(Context $context)
+ public function onPostConsume(PostConsume $context): void
{
- if (false == $context->isExecutionInterrupted() && $this->interruptConsumption) {
- $this->logger->debug('[SignalExtension] Interrupt execution');
- $context->setExecutionInterrupted($this->interruptConsumption);
-
- $this->interruptConsumption = false;
+ if ($this->shouldBeStopped($context->getLogger())) {
+ $context->interruptExecution();
}
}
- /**
- * @param int $signal
- */
- public function handleSignal($signal)
+ public function handleSignal(int $signal): void
{
if ($this->logger) {
$this->logger->debug(sprintf('[SignalExtension] Caught signal: %s', $signal));
}
switch ($signal) {
- case SIGTERM: // 15 : supervisor default stop
- case SIGQUIT: // 3 : kill -s QUIT
- case SIGINT: // 2 : ctrl+c
- $this->logger->debug('[SignalExtension] Interrupt consumption');
+ case \SIGTERM: // 15 : supervisor default stop
+ case \SIGQUIT: // 3 : kill -s QUIT
+ case \SIGINT: // 2 : ctrl+c
+ if ($this->logger) {
+ $this->logger->debug('[SignalExtension] Interrupt consumption');
+ }
+
$this->interruptConsumption = true;
break;
default:
break;
}
}
+
+ private function shouldBeStopped(LoggerInterface $logger): bool
+ {
+ if ($this->interruptConsumption) {
+ $logger->debug('[SignalExtension] Interrupt execution');
+
+ $this->interruptConsumption = false;
+
+ return true;
+ }
+
+ return false;
+ }
}
diff --git a/pkg/enqueue/Consumption/ExtensionInterface.php b/pkg/enqueue/Consumption/ExtensionInterface.php
index d05109450..326a98f0d 100644
--- a/pkg/enqueue/Consumption/ExtensionInterface.php
+++ b/pkg/enqueue/Consumption/ExtensionInterface.php
@@ -2,56 +2,6 @@
namespace Enqueue\Consumption;
-interface ExtensionInterface
+interface ExtensionInterface extends StartExtensionInterface, PreSubscribeExtensionInterface, PreConsumeExtensionInterface, MessageReceivedExtensionInterface, PostMessageReceivedExtensionInterface, MessageResultExtensionInterface, ProcessorExceptionExtensionInterface, PostConsumeExtensionInterface, EndExtensionInterface, InitLoggerExtensionInterface
{
- /**
- * Executed only once at the very begining of the consumption.
- * At this stage the context does not contain processor, consumer and queue.
- *
- * @param Context $context
- */
- public function onStart(Context $context);
-
- /**
- * Executed at every new cycle before we asked a broker for a new message.
- * At this stage the context already contains processor, consumer and queue.
- * The consumption could be interrupted at this step.
- *
- * @param Context $context
- */
- public function onBeforeReceive(Context $context);
-
- /**
- * Executed when a new message is received from a broker but before it was passed to processor
- * The context contains a message.
- * The extension may set a status.
- * The consumption could be interrupted at this step but it will done only after the message is processed.
- *
- * @param Context $context
- */
- public function onPreReceived(Context $context);
-
- /**
- * Executed when a message is processed by a processor.
- * The context contains a message status and could be changed
- * The consumption could be interrupted at this step but it will done only after the message is processed.
- *
- * @param Context $context
- */
- public function onPostReceived(Context $context);
-
- /**
- * Called each time at the end of the cycle if nothing was done.
- *
- * @param Context $context
- */
- public function onIdle(Context $context);
-
- /**
- * Called when the consumption was interrupted by an extension or exception
- * In case of exception it will be present in the context.
- *
- * @param Context $context
- */
- public function onInterrupted(Context $context);
}
diff --git a/pkg/enqueue/Consumption/FallbackSubscriptionConsumer.php b/pkg/enqueue/Consumption/FallbackSubscriptionConsumer.php
new file mode 100644
index 000000000..15e2f273b
--- /dev/null
+++ b/pkg/enqueue/Consumption/FallbackSubscriptionConsumer.php
@@ -0,0 +1,109 @@
+subscribers = [];
+ }
+
+ public function consume(int $timeoutMs = 0): void
+ {
+ if (!$subscriberCount = \count($this->subscribers)) {
+ throw new \LogicException('No subscribers');
+ }
+
+ $timeout = $timeoutMs / 1000;
+ $endAt = microtime(true) + $timeout;
+
+ while (true) {
+ /**
+ * @var string
+ * @var Consumer $consumer
+ * @var callable $processor
+ */
+ foreach ($this->subscribers as $queueName => list($consumer, $callback)) {
+ $message = 1 === $subscriberCount ? $consumer->receive($timeoutMs) : $consumer->receiveNoWait();
+
+ if ($message) {
+ if (false === call_user_func($callback, $message, $consumer)) {
+ return;
+ }
+ } elseif (1 !== $subscriberCount) {
+ if ($timeout && microtime(true) >= $endAt) {
+ return;
+ }
+
+ $this->idleTime && usleep($this->idleTime);
+ }
+
+ if ($timeout && microtime(true) >= $endAt) {
+ return;
+ }
+ }
+ }
+ }
+
+ public function subscribe(Consumer $consumer, callable $callback): void
+ {
+ $queueName = $consumer->getQueue()->getQueueName();
+ if (array_key_exists($queueName, $this->subscribers)) {
+ if ($this->subscribers[$queueName][0] === $consumer && $this->subscribers[$queueName][1] === $callback) {
+ return;
+ }
+
+ throw new \InvalidArgumentException(sprintf('There is a consumer subscribed to queue: "%s"', $queueName));
+ }
+
+ $this->subscribers[$queueName] = [$consumer, $callback];
+ }
+
+ public function unsubscribe(Consumer $consumer): void
+ {
+ if (false == array_key_exists($consumer->getQueue()->getQueueName(), $this->subscribers)) {
+ return;
+ }
+
+ if ($this->subscribers[$consumer->getQueue()->getQueueName()][0] !== $consumer) {
+ return;
+ }
+
+ unset($this->subscribers[$consumer->getQueue()->getQueueName()]);
+ }
+
+ public function unsubscribeAll(): void
+ {
+ $this->subscribers = [];
+ }
+
+ public function getIdleTime(): int
+ {
+ return $this->idleTime;
+ }
+
+ /**
+ * The time in milliseconds the consumer waits if no message has been received.
+ */
+ public function setIdleTime(int $idleTime): void
+ {
+ $this->idleTime = $idleTime;
+ }
+}
diff --git a/pkg/enqueue/Consumption/InitLoggerExtensionInterface.php b/pkg/enqueue/Consumption/InitLoggerExtensionInterface.php
new file mode 100644
index 000000000..936e32d6e
--- /dev/null
+++ b/pkg/enqueue/Consumption/InitLoggerExtensionInterface.php
@@ -0,0 +1,14 @@
+psrContext = $psrContext;
- $this->extension = $extension;
- $this->idleMicroseconds = $idleMicroseconds;
+ $this->interopContext = $interopContext;
+ $this->receiveTimeout = $receiveTimeout;
+
+ $this->staticExtension = $extension ?: new ChainExtension([]);
+ $this->logger = $logger ?: new NullLogger();
$this->boundProcessors = [];
+ array_walk($boundProcessors, function (BoundProcessor $processor) {
+ $this->boundProcessors[] = $processor;
+ });
+
+ $this->fallbackSubscriptionConsumer = new FallbackSubscriptionConsumer();
}
- /**
- * @return PsrContext
- */
- public function getPsrContext()
+ public function setReceiveTimeout(int $timeout): void
{
- return $this->psrContext;
+ $this->receiveTimeout = $timeout;
}
- /**
- * @param Queue|string $queue
- * @param Processor|callable $processor
- *
- * @return QueueConsumer
- */
- public function bind($queue, $processor)
+ public function getReceiveTimeout(): int
+ {
+ return $this->receiveTimeout;
+ }
+
+ public function getContext(): InteropContext
+ {
+ return $this->interopContext;
+ }
+
+ public function bind($queue, Processor $processor): QueueConsumerInterface
{
if (is_string($queue)) {
- $queue = $this->psrContext->createQueue($queue);
- }
- if (is_callable($processor)) {
- $processor = new CallbackProcessor($processor);
+ $queue = $this->interopContext->createQueue($queue);
}
- InvalidArgumentException::assertInstanceOf($queue, Queue::class);
- InvalidArgumentException::assertInstanceOf($processor, Processor::class);
+ InvalidArgumentException::assertInstanceOf($queue, InteropQueue::class);
if (empty($queue->getQueueName())) {
throw new LogicException('The queue name must be not empty.');
@@ -88,116 +112,99 @@ public function bind($queue, $processor)
throw new LogicException(sprintf('The queue was already bound. Queue: %s', $queue->getQueueName()));
}
- $this->boundProcessors[$queue->getQueueName()] = [$queue, $processor];
+ $this->boundProcessors[$queue->getQueueName()] = new BoundProcessor($queue, $processor);
return $this;
}
- /**
- * Runtime extension - is an extension or a collection of extensions which could be set on runtime.
- * Here's a good example: @see LimitsExtensionsCommandTrait.
- *
- * @param ExtensionInterface|ChainExtension|null $runtimeExtension
- *
- * @throws \Exception
- */
- public function consume(ExtensionInterface $runtimeExtension = null)
+ public function bindCallback($queue, callable $processor): QueueConsumerInterface
{
- /** @var Consumer[] $messageConsumers */
- $messageConsumers = [];
- /** @var \Enqueue\Psr\Queue $queue */
- foreach ($this->boundProcessors as list($queue, $processor)) {
- $messageConsumers[$queue->getQueueName()] = $this->psrContext->createConsumer($queue);
- }
+ return $this->bind($queue, new CallbackProcessor($processor));
+ }
- $extension = $this->extension ?: new ChainExtension([]);
- if ($runtimeExtension) {
- $extension = new ChainExtension([$extension, $runtimeExtension]);
- }
+ public function consume(?ExtensionInterface $runtimeExtension = null): void
+ {
+ $extension = $runtimeExtension ?
+ new ChainExtension([$this->staticExtension, $runtimeExtension]) :
+ $this->staticExtension
+ ;
- $context = new Context($this->psrContext);
- $extension->onStart($context);
+ $initLogger = new InitLogger($this->logger);
+ $extension->onInitLogger($initLogger);
- $logger = $context->getLogger() ?: new NullLogger();
- $logger->info('Start consuming');
+ $this->logger = $initLogger->getLogger();
- while (true) {
- try {
- /** @var Queue $queue */
- foreach ($this->boundProcessors as list($queue, $processor)) {
- $logger->debug(sprintf('Switch to a queue %s', $queue->getQueueName()));
+ $startTime = (int) (microtime(true) * 1000);
- $messageConsumer = $messageConsumers[$queue->getQueueName()];
+ $start = new Start(
+ $this->interopContext,
+ $this->logger,
+ $this->boundProcessors,
+ $this->receiveTimeout,
+ $startTime
+ );
- $context = new Context($this->psrContext);
- $context->setLogger($logger);
- $context->setPsrQueue($queue);
- $context->setPsrConsumer($messageConsumer);
- $context->setPsrProcessor($processor);
+ $extension->onStart($start);
- $this->doConsume($extension, $context);
- }
- } catch (ConsumptionInterruptedException $e) {
- $logger->info(sprintf('Consuming interrupted'));
+ if ($start->isExecutionInterrupted()) {
+ $this->onEnd($extension, $startTime, $start->getExitStatus());
- $context->setExecutionInterrupted(true);
+ return;
+ }
- $extension->onInterrupted($context);
- $this->psrContext->close();
+ $this->logger = $start->getLogger();
+ $this->receiveTimeout = $start->getReceiveTimeout();
+ $this->boundProcessors = $start->getBoundProcessors();
- return;
- } catch (\Exception $exception) {
- $context->setExecutionInterrupted(true);
- $context->setException($exception);
+ if (empty($this->boundProcessors)) {
+ throw new \LogicException('There is nothing to consume. It is required to bind something before calling consume method.');
+ }
- try {
- $this->onInterruptionByException($extension, $context);
- $this->psrContext->close();
- } catch (\Exception $e) {
- // for some reason finally does not work here on php5.5
- $this->psrContext->close();
+ /** @var Consumer[] $consumers */
+ $consumers = [];
+ foreach ($this->boundProcessors as $queueName => $boundProcessor) {
+ $queue = $boundProcessor->getQueue();
- throw $e;
- }
- }
+ $consumers[$queue->getQueueName()] = $this->interopContext->createConsumer($queue);
}
- }
- /**
- * @param ExtensionInterface $extension
- * @param Context $context
- *
- * @throws ConsumptionInterruptedException
- *
- * @return bool
- */
- protected function doConsume(ExtensionInterface $extension, Context $context)
- {
- $processor = $context->getPsrProcessor();
- $consumer = $context->getPsrConsumer();
- $logger = $context->getLogger();
+ try {
+ $subscriptionConsumer = $this->interopContext->createSubscriptionConsumer();
+ } catch (SubscriptionConsumerNotSupportedException $e) {
+ $subscriptionConsumer = $this->fallbackSubscriptionConsumer;
+ }
- $extension->onBeforeReceive($context);
+ $receivedMessagesCount = 0;
+ $interruptExecution = false;
- if ($context->isExecutionInterrupted()) {
- throw new ConsumptionInterruptedException();
- }
+ $callback = function (InteropMessage $message, Consumer $consumer) use (&$receivedMessagesCount, &$interruptExecution, $extension) {
+ ++$receivedMessagesCount;
- if ($message = $consumer->receive($timeout = 1)) {
- $logger->info('Message received');
- $logger->debug('Headers: {headers}', ['headers' => new VarExport($message->getHeaders())]);
- $logger->debug('Properties: {properties}', ['properties' => new VarExport($message->getProperties())]);
- $logger->debug('Payload: {payload}', ['payload' => new VarExport($message->getBody())]);
+ $receivedAt = (int) (microtime(true) * 1000);
+ $queue = $consumer->getQueue();
+ if (false == array_key_exists($queue->getQueueName(), $this->boundProcessors)) {
+ throw new \LogicException(sprintf('The processor for the queue "%s" could not be found.', $queue->getQueueName()));
+ }
- $context->setPsrMessage($message);
+ $processor = $this->boundProcessors[$queue->getQueueName()]->getProcessor();
- $extension->onPreReceived($context);
- if (!$context->getResult()) {
- $result = $processor->process($message, $this->psrContext);
- $context->setResult($result);
+ $messageReceived = new MessageReceived($this->interopContext, $consumer, $message, $processor, $receivedAt, $this->logger);
+ $extension->onMessageReceived($messageReceived);
+ $result = $messageReceived->getResult();
+ $processor = $messageReceived->getProcessor();
+ if (null === $result) {
+ try {
+ $result = $processor->process($message, $this->interopContext);
+ } catch (\Exception|\Throwable $e) {
+ $result = $this->onProcessorException($extension, $consumer, $message, $e, $receivedAt);
+ }
}
- switch ($context->getResult()) {
+ $messageResult = new MessageResult($this->interopContext, $consumer, $message, $result, $receivedAt, $this->logger);
+ $extension->onResult($messageResult);
+ $result = $messageResult->getResult();
+
+ switch ($result) {
case Result::ACK:
$consumer->acknowledge($message);
break;
@@ -207,64 +214,119 @@ protected function doConsume(ExtensionInterface $extension, Context $context)
case Result::REQUEUE:
$consumer->reject($message, true);
break;
+ case Result::ALREADY_ACKNOWLEDGED:
+ break;
default:
- throw new \LogicException(sprintf('Status is not supported: %s', $context->getResult()));
+ throw new \LogicException(sprintf('Status is not supported: %s', $result));
+ }
+
+ $postMessageReceived = new PostMessageReceived($this->interopContext, $consumer, $message, $result, $receivedAt, $this->logger);
+ $extension->onPostMessageReceived($postMessageReceived);
+
+ if ($postMessageReceived->isExecutionInterrupted()) {
+ $interruptExecution = true;
+
+ return false;
+ }
+
+ return true;
+ };
+
+ foreach ($consumers as $queueName => $consumer) {
+ /* @var Consumer $consumer */
+
+ $preSubscribe = new PreSubscribe(
+ $this->interopContext,
+ $this->boundProcessors[$queueName]->getProcessor(),
+ $consumer,
+ $this->logger
+ );
+
+ $extension->onPreSubscribe($preSubscribe);
+
+ $subscriptionConsumer->subscribe($consumer, $callback);
+ }
+
+ $cycle = 1;
+ while (true) {
+ $receivedMessagesCount = 0;
+ $interruptExecution = false;
+
+ $preConsume = new PreConsume($this->interopContext, $subscriptionConsumer, $this->logger, $cycle, $this->receiveTimeout, $startTime);
+ $extension->onPreConsume($preConsume);
+
+ if ($preConsume->isExecutionInterrupted()) {
+ $this->onEnd($extension, $startTime, $preConsume->getExitStatus(), $subscriptionConsumer);
+
+ return;
}
- $logger->info(sprintf('Message processed: %s', $context->getResult()));
+ $subscriptionConsumer->consume($this->receiveTimeout);
- $extension->onPostReceived($context);
- } else {
- $logger->info(sprintf('Idle'));
+ $postConsume = new PostConsume($this->interopContext, $subscriptionConsumer, $receivedMessagesCount, $cycle, $startTime, $this->logger);
+ $extension->onPostConsume($postConsume);
- usleep($this->idleMicroseconds);
- $extension->onIdle($context);
+ if ($interruptExecution || $postConsume->isExecutionInterrupted()) {
+ $this->onEnd($extension, $startTime, $postConsume->getExitStatus(), $subscriptionConsumer);
+
+ return;
+ }
+
+ ++$cycle;
}
+ }
+
+ /**
+ * @internal
+ */
+ public function setFallbackSubscriptionConsumer(SubscriptionConsumer $fallbackSubscriptionConsumer): void
+ {
+ $this->fallbackSubscriptionConsumer = $fallbackSubscriptionConsumer;
+ }
+
+ private function onEnd(ExtensionInterface $extension, int $startTime, ?int $exitStatus = null, ?SubscriptionConsumer $subscriptionConsumer = null): void
+ {
+ $endTime = (int) (microtime(true) * 1000);
+
+ $endContext = new End($this->interopContext, $startTime, $endTime, $this->logger, $exitStatus);
+ $extension->onEnd($endContext);
- if ($context->isExecutionInterrupted()) {
- throw new ConsumptionInterruptedException();
+ if ($subscriptionConsumer) {
+ $subscriptionConsumer->unsubscribeAll();
}
}
/**
- * @param ExtensionInterface $extension
- * @param Context $context
+ * The logic is similar to one in Symfony's ExceptionListener::onKernelException().
*
- * @throws \Exception
+ * https://github.com/symfony/symfony/blob/cbe289517470eeea27162fd2d523eb29c95f775f/src/Symfony/Component/HttpKernel/EventListener/ExceptionListener.php#L77
*/
- protected function onInterruptionByException(ExtensionInterface $extension, Context $context)
+ private function onProcessorException(ExtensionInterface $extension, Consumer $consumer, Message $message, \Throwable $exception, int $receivedAt)
{
- $logger = $context->getLogger();
- $logger->error(sprintf('Consuming interrupted by exception'));
-
- $exception = $context->getException();
+ $processorException = new ProcessorException($this->interopContext, $consumer, $message, $exception, $receivedAt, $this->logger);
try {
- $extension->onInterrupted($context);
+ $extension->onProcessorException($processorException);
+
+ $result = $processorException->getResult();
+ if (null === $result) {
+ throw $exception;
+ }
+
+ return $result;
} catch (\Exception $e) {
- // logic is similar to one in Symfony's ExceptionListener::onKernelException
- $logger->error(sprintf(
- 'Exception thrown when handling an exception (%s: %s at %s line %s)',
- get_class($e),
- $e->getMessage(),
- $e->getFile(),
- $e->getLine()
- ));
-
- $wrapper = $e;
- while ($prev = $wrapper->getPrevious()) {
+ $prev = $e;
+ do {
if ($exception === $wrapper = $prev) {
throw $e;
}
- }
+ } while ($prev = $wrapper->getPrevious());
- $prev = new \ReflectionProperty('Exception', 'previous');
+ $prev = new \ReflectionProperty($wrapper instanceof \Exception ? \Exception::class : \Error::class, 'previous');
$prev->setAccessible(true);
$prev->setValue($wrapper, $exception);
throw $e;
}
-
- throw $exception;
}
}
diff --git a/pkg/enqueue/Consumption/QueueConsumerInterface.php b/pkg/enqueue/Consumption/QueueConsumerInterface.php
new file mode 100644
index 000000000..ee2565252
--- /dev/null
+++ b/pkg/enqueue/Consumption/QueueConsumerInterface.php
@@ -0,0 +1,40 @@
+status = (string) $status;
@@ -72,17 +70,14 @@ public function getReason()
}
/**
- * @return PsrMessage|null
+ * @return InteropMessage|null
*/
public function getReply()
{
return $this->reply;
}
- /**
- * @param PsrMessage|null $reply
- */
- public function setReply(PsrMessage $reply = null)
+ public function setReply(?InteropMessage $reply = null)
{
$this->reply = $reply;
}
@@ -90,42 +85,44 @@ public function setReply(PsrMessage $reply = null)
/**
* @param string $reason
*
- * @return Result
+ * @return static
*/
public static function ack($reason = '')
{
- return new static(self::ACK, $reason);
+ return new self(self::ACK, $reason);
}
/**
* @param string $reason
*
- * @return Result
+ * @return static
*/
public static function reject($reason)
{
- return new static(self::REJECT, $reason);
+ return new self(self::REJECT, $reason);
}
/**
* @param string $reason
*
- * @return Result
+ * @return static
*/
public static function requeue($reason = '')
{
- return new static(self::REQUEUE, $reason);
+ return new self(self::REQUEUE, $reason);
}
/**
- * @param PsrMessage $replyMessage
+ * @param string $status
* @param string|null $reason
*
- * @return Result
+ * @return static
*/
- public static function reply(PsrMessage $replyMessage, $reason = '')
+ public static function reply(InteropMessage $replyMessage, $status = self::ACK, $reason = null)
{
- $result = static::ack($reason);
+ $status = null === $status ? self::ACK : $status;
+
+ $result = new self($status, $reason);
$result->setReply($replyMessage);
return $result;
diff --git a/pkg/enqueue/Consumption/StartExtensionInterface.php b/pkg/enqueue/Consumption/StartExtensionInterface.php
new file mode 100644
index 000000000..98571061c
--- /dev/null
+++ b/pkg/enqueue/Consumption/StartExtensionInterface.php
@@ -0,0 +1,13 @@
+services = $services;
+ }
+
+ public function get($id)
+ {
+ if (false == $this->has($id)) {
+ throw new NotFoundException(sprintf('The service "%s" not found.', $id));
+ }
+
+ return $this->services[$id];
+ }
+
+ public function has(string $id): bool
+ {
+ return array_key_exists($id, $this->services);
+ }
+}
diff --git a/pkg/enqueue/Container/NotFoundException.php b/pkg/enqueue/Container/NotFoundException.php
new file mode 100644
index 000000000..fcc3386e6
--- /dev/null
+++ b/pkg/enqueue/Container/NotFoundException.php
@@ -0,0 +1,9 @@
+doctrine = $doctrine;
+ $this->fallbackFactory = $fallbackFactory;
+ }
+
+ public function create($config): ConnectionFactory
+ {
+ if (is_string($config)) {
+ $config = ['dsn' => $config];
+ }
+
+ if (false == is_array($config)) {
+ throw new \InvalidArgumentException('The config must be either array or DSN string.');
+ }
+
+ if (false == array_key_exists('dsn', $config)) {
+ throw new \InvalidArgumentException('The config must have dsn key set.');
+ }
+
+ $dsn = Dsn::parseFirst($config['dsn']);
+
+ if ('doctrine' === $dsn->getScheme()) {
+ $config = $dsn->getQuery();
+ $config['connection_name'] = $dsn->getHost();
+
+ return new ManagerRegistryConnectionFactory($this->doctrine, $config);
+ }
+
+ return $this->fallbackFactory->create($config);
+ }
+}
diff --git a/pkg/enqueue/Doctrine/DoctrineDriverFactory.php b/pkg/enqueue/Doctrine/DoctrineDriverFactory.php
new file mode 100644
index 000000000..aab6489aa
--- /dev/null
+++ b/pkg/enqueue/Doctrine/DoctrineDriverFactory.php
@@ -0,0 +1,41 @@
+fallbackFactory = $fallbackFactory;
+ }
+
+ public function create(ConnectionFactory $factory, Config $config, RouteCollection $collection): DriverInterface
+ {
+ $dsn = $config->getTransportOption('dsn');
+
+ if (empty($dsn)) {
+ throw new \LogicException('This driver factory relies on dsn option from transport config. The option is empty or not set.');
+ }
+
+ $dsn = Dsn::parseFirst($dsn);
+
+ if ('doctrine' === $dsn->getScheme()) {
+ return new DbalDriver($factory->createContext(), $config, $collection);
+ }
+
+ return $this->fallbackFactory->create($factory, $config, $collection);
+ }
+}
diff --git a/pkg/enqueue/Doctrine/DoctrineSchemaCompilerPass.php b/pkg/enqueue/Doctrine/DoctrineSchemaCompilerPass.php
new file mode 100644
index 000000000..0eb378470
--- /dev/null
+++ b/pkg/enqueue/Doctrine/DoctrineSchemaCompilerPass.php
@@ -0,0 +1,39 @@
+hasDefinition('doctrine')) {
+ return;
+ }
+
+ foreach ($container->getParameter('enqueue.transports') as $name) {
+ $diUtils = DiUtils::create(TransportFactory::MODULE, $name);
+
+ $container->register($diUtils->format('connection_factory_factory.outer'), DoctrineConnectionFactoryFactory::class)
+ ->setDecoratedService($diUtils->format('connection_factory_factory'), $diUtils->format('connection_factory_factory.inner'))
+ ->addArgument(new Reference('doctrine'))
+ ->addArgument(new Reference($diUtils->format('connection_factory_factory.inner')))
+ ;
+ }
+
+ foreach ($container->getParameter('enqueue.clients') as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+
+ $container->register($diUtils->format('driver_factory.outer'), DoctrineDriverFactory::class)
+ ->setDecoratedService($diUtils->format('driver_factory'), $diUtils->format('driver_factory.inner'))
+ ->addArgument(new Reference($diUtils->format('driver_factory.inner')))
+ ;
+ }
+ }
+}
diff --git a/pkg/enqueue/ProcessorRegistryInterface.php b/pkg/enqueue/ProcessorRegistryInterface.php
new file mode 100644
index 000000000..5306c3035
--- /dev/null
+++ b/pkg/enqueue/ProcessorRegistryInterface.php
@@ -0,0 +1,12 @@
+Supporting Enqueue
+
+Enqueue is an MIT-licensed open source project with its ongoing development made possible entirely by the support of community and our customers. If you'd like to join them, please consider:
+
+- [Become a sponsor](https://www.patreon.com/makasim)
+- [Become our client](http://forma-pro.com/)
+
+---
+
# Message Queue.
[](https://gitter.im/php-enqueue/Lobby)
-[](https://travis-ci.org/php-enqueue/enqueue)
+[](https://github.com/php-enqueue/enqueue/actions?query=workflow%3ACI)
[](https://packagist.org/packages/enqueue/enqueue)
[](https://packagist.org/packages/enqueue/enqueue)
-
-It contains advanced features build on top of a transport component.
+
+It contains advanced features build on top of a transport component.
Client component kind of plug and play things or consumption component that simplify message processing a lot.
-Read more about it in documentation.
+Read more about it in documentation.
## Resources
-* [Documentation](https://github.com/php-enqueue/enqueue-dev/blob/master/docs/index.md)
+* [Site](https://enqueue.forma-pro.com/)
+* [Documentation](https://php-enqueue.github.io/)
* [Questions](https://gitter.im/php-enqueue/Lobby)
* [Issue Tracker](https://github.com/php-enqueue/enqueue-dev/issues)
+## Developed by Forma-Pro
+
+Forma-Pro is a full stack development company which interests also spread to open source development.
+Being a team of strong professionals we have an aim an ability to help community by developing cutting edge solutions in the areas of e-commerce, docker & microservice oriented architecture where we have accumulated a huge many-years experience.
+Our main specialization is Symfony framework based solution, but we are always looking to the technologies that allow us to do our job the best way. We are committed to creating solutions that revolutionize the way how things are developed in aspects of architecture & scalability.
+
+If you have any questions and inquires about our open source development, this product particularly or any other matter feel free to contact at opensource@forma-pro.com
+
## License
-It is released under the [MIT License](LICENSE).
\ No newline at end of file
+It is released under the [MIT License](LICENSE).
diff --git a/pkg/enqueue/Resources.php b/pkg/enqueue/Resources.php
new file mode 100644
index 000000000..4c500006f
--- /dev/null
+++ b/pkg/enqueue/Resources.php
@@ -0,0 +1,211 @@
+ [
+ * schemes => [schemes strings],
+ * package => package name,
+ * ].
+ *
+ * @var array
+ */
+ private static $knownConnections;
+
+ private function __construct()
+ {
+ }
+
+ public static function getAvailableConnections(): array
+ {
+ $map = self::getKnownConnections();
+
+ $availableMap = [];
+ foreach ($map as $connectionClass => $item) {
+ if (\class_exists($connectionClass)) {
+ $availableMap[$connectionClass] = $item;
+ }
+ }
+
+ return $availableMap;
+ }
+
+ public static function getKnownSchemes(): array
+ {
+ $map = self::getKnownConnections();
+
+ $schemes = [];
+ foreach ($map as $connectionClass => $item) {
+ foreach ($item['schemes'] as $scheme) {
+ $schemes[$scheme] = $connectionClass;
+ }
+ }
+
+ return $schemes;
+ }
+
+ public static function getAvailableSchemes(): array
+ {
+ $map = self::getAvailableConnections();
+
+ $schemes = [];
+ foreach ($map as $connectionClass => $item) {
+ foreach ($item['schemes'] as $scheme) {
+ $schemes[$scheme] = $connectionClass;
+ }
+ }
+
+ return $schemes;
+ }
+
+ public static function getKnownConnections(): array
+ {
+ if (null === self::$knownConnections) {
+ $map = [];
+
+ $map[FsConnectionFactory::class] = [
+ 'schemes' => ['file'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/fs',
+ ];
+ $map[AmqpBunnyConnectionFactory::class] = [
+ 'schemes' => ['amqp'],
+ 'supportedSchemeExtensions' => ['bunny'],
+ 'package' => 'enqueue/amqp-bunny',
+ ];
+ $map[AmqpExtConnectionFactory::class] = [
+ 'schemes' => ['amqp', 'amqps'],
+ 'supportedSchemeExtensions' => ['ext'],
+ 'package' => 'enqueue/amqp-ext',
+ ];
+ $map[AmqpLibConnectionFactory::class] = [
+ 'schemes' => ['amqp', 'amqps'],
+ 'supportedSchemeExtensions' => ['lib'],
+ 'package' => 'enqueue/amqp-lib',
+ ];
+
+ $map[DbalConnectionFactory::class] = [
+ 'schemes' => [
+ 'db2',
+ 'ibm-db2',
+ 'mssql',
+ 'sqlsrv',
+ 'mysql',
+ 'mysql2',
+ 'mysql',
+ 'pgsql',
+ 'postgres',
+ 'sqlite',
+ 'sqlite3',
+ 'sqlite',
+ ],
+ 'supportedSchemeExtensions' => ['pdo'],
+ 'package' => 'enqueue/dbal',
+ ];
+
+ $map[NullConnectionFactory::class] = [
+ 'schemes' => ['null'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/null',
+ ];
+ $map[GearmanConnectionFactory::class] = [
+ 'schemes' => ['gearman'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/gearman',
+ ];
+ $map[PheanstalkConnectionFactory::class] = [
+ 'schemes' => ['beanstalk'],
+ 'supportedSchemeExtensions' => ['pheanstalk'],
+ 'package' => 'enqueue/pheanstalk',
+ ];
+ $map[RdKafkaConnectionFactory::class] = [
+ 'schemes' => ['kafka', 'rdkafka'],
+ 'supportedSchemeExtensions' => ['rdkafka'],
+ 'package' => 'enqueue/rdkafka',
+ ];
+ $map[RedisConnectionFactory::class] = [
+ 'schemes' => ['redis', 'rediss'],
+ 'supportedSchemeExtensions' => ['predis', 'phpredis'],
+ 'package' => 'enqueue/redis',
+ ];
+ $map[StompConnectionFactory::class] = [
+ 'schemes' => ['stomp'],
+ 'supportedSchemeExtensions' => ['rabbitmq'],
+ 'package' => 'enqueue/stomp', ];
+ $map[SqsConnectionFactory::class] = [
+ 'schemes' => ['sqs'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/sqs', ];
+ $map[SnsConnectionFactory::class] = [
+ 'schemes' => ['sns'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/sns', ];
+ $map[SnsQsConnectionFactory::class] = [
+ 'schemes' => ['snsqs'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/snsqs', ];
+ $map[GpsConnectionFactory::class] = [
+ 'schemes' => ['gps'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/gps', ];
+ $map[MongodbConnectionFactory::class] = [
+ 'schemes' => ['mongodb'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/mongodb',
+ ];
+ $map[WampConnectionFactory::class] = [
+ 'schemes' => ['wamp', 'ws'],
+ 'supportedSchemeExtensions' => [],
+ 'package' => 'enqueue/wamp',
+ ];
+
+ self::$knownConnections = $map;
+ }
+
+ return self::$knownConnections;
+ }
+
+ public static function addConnection(string $connectionFactoryClass, array $schemes, array $extensions, string $package): void
+ {
+ if (\class_exists($connectionFactoryClass)) {
+ if (false == (new \ReflectionClass($connectionFactoryClass))->implementsInterface(ConnectionFactory::class)) {
+ throw new \InvalidArgumentException(\sprintf('The connection factory class "%s" must implement "%s" interface.', $connectionFactoryClass, ConnectionFactory::class));
+ }
+ }
+
+ if (empty($schemes)) {
+ throw new \InvalidArgumentException('Schemes could not be empty.');
+ }
+ if (empty($package)) {
+ throw new \InvalidArgumentException('Package name could not be empty.');
+ }
+
+ self::getKnownConnections();
+ self::$knownConnections[$connectionFactoryClass] = [
+ 'schemes' => $schemes,
+ 'supportedSchemeExtensions' => $extensions,
+ 'package' => $package,
+ ];
+ }
+}
diff --git a/pkg/enqueue/Router/Recipient.php b/pkg/enqueue/Router/Recipient.php
index 892c0e511..d2f668f42 100644
--- a/pkg/enqueue/Router/Recipient.php
+++ b/pkg/enqueue/Router/Recipient.php
@@ -2,8 +2,8 @@
namespace Enqueue\Router;
-use Enqueue\Psr\Destination;
-use Enqueue\Psr\Message;
+use Interop\Queue\Destination;
+use Interop\Queue\Message as InteropMessage;
class Recipient
{
@@ -13,15 +13,11 @@ class Recipient
private $destination;
/**
- * @var Message
+ * @var InteropMessage
*/
private $message;
- /**
- * @param Destination $destination
- * @param Message $message
- */
- public function __construct(Destination $destination, Message $message)
+ public function __construct(Destination $destination, InteropMessage $message)
{
$this->destination = $destination;
$this->message = $message;
@@ -36,7 +32,7 @@ public function getDestination()
}
/**
- * @return Message
+ * @return InteropMessage
*/
public function getMessage()
{
diff --git a/pkg/enqueue/Router/RecipientListRouterInterface.php b/pkg/enqueue/Router/RecipientListRouterInterface.php
index de7669bae..6bb950fdc 100644
--- a/pkg/enqueue/Router/RecipientListRouterInterface.php
+++ b/pkg/enqueue/Router/RecipientListRouterInterface.php
@@ -2,14 +2,12 @@
namespace Enqueue\Router;
-use Enqueue\Psr\Message;
+use Interop\Queue\Message as InteropMessage;
interface RecipientListRouterInterface
{
/**
- * @param Message $message
- *
* @return \Traversable|Recipient[]
*/
- public function route(Message $message);
+ public function route(InteropMessage $message);
}
diff --git a/pkg/enqueue/Router/RouteRecipientListProcessor.php b/pkg/enqueue/Router/RouteRecipientListProcessor.php
index 1cc2daef2..22488e33f 100644
--- a/pkg/enqueue/Router/RouteRecipientListProcessor.php
+++ b/pkg/enqueue/Router/RouteRecipientListProcessor.php
@@ -2,9 +2,9 @@
namespace Enqueue\Router;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Message;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Context;
+use Interop\Queue\Message as InteropMessage;
+use Interop\Queue\Processor;
class RouteRecipientListProcessor implements Processor
{
@@ -13,18 +13,12 @@ class RouteRecipientListProcessor implements Processor
*/
private $router;
- /**
- * @param RecipientListRouterInterface $router
- */
public function __construct(RecipientListRouterInterface $router)
{
$this->router = $router;
}
- /**
- * {@inheritdoc}
- */
- public function process(Message $message, Context $context)
+ public function process(InteropMessage $message, Context $context)
{
$producer = $context->createProducer();
foreach ($this->router->route($message) as $recipient) {
diff --git a/pkg/enqueue/Rpc/Promise.php b/pkg/enqueue/Rpc/Promise.php
index b0265f115..01b47e1f6 100644
--- a/pkg/enqueue/Rpc/Promise.php
+++ b/pkg/enqueue/Rpc/Promise.php
@@ -2,67 +2,117 @@
namespace Enqueue\Rpc;
-use Enqueue\Psr\Consumer;
+use Interop\Queue\Message as InteropMessage;
class Promise
{
/**
- * @var Consumer
+ * @var \Closure
*/
- private $consumer;
+ private $receiveCallback;
/**
- * @var int
+ * @var \Closure
*/
- private $timeout;
+ private $receiveNoWaitCallback;
+
+ /**
+ * @var \Closure
+ */
+ private $finallyCallback;
+
/**
- * @var string
+ * @var bool
*/
- private $correlationId;
+ private $deleteReplyQueue;
/**
- * @param Consumer $consumer
- * @param string $correlationId
- * @param int $timeout
+ * @var InteropMessage
*/
- public function __construct(Consumer $consumer, $correlationId, $timeout)
+ private $message;
+
+ public function __construct(\Closure $receiveCallback, \Closure $receiveNoWaitCallback, \Closure $finallyCallback)
{
- $this->consumer = $consumer;
- $this->timeout = $timeout;
- $this->correlationId = $correlationId;
+ $this->receiveCallback = $receiveCallback;
+ $this->receiveNoWaitCallback = $receiveNoWaitCallback;
+ $this->finallyCallback = $finallyCallback;
+
+ $this->deleteReplyQueue = true;
}
- public function getMessage()
+ /**
+ * Blocks until message received or timeout expired.
+ *
+ * @param int $timeout
+ *
+ * @throws TimeoutException if the wait timeout is reached
+ *
+ * @return InteropMessage
+ */
+ public function receive($timeout = null)
{
- $endTime = time() + $this->timeout;
+ if (null == $this->message) {
+ try {
+ if ($message = $this->doReceive($this->receiveCallback, $this, $timeout)) {
+ $this->message = $message;
+ }
+ } finally {
+ call_user_func($this->finallyCallback, $this);
+ }
+ }
- while (time() < $endTime) {
- if ($message = $this->consumer->receive($this->timeout)) {
- if ($message->getCorrelationId() === $this->correlationId) {
- $this->consumer->acknowledge($message);
+ return $this->message;
+ }
- return $message;
- }
- $this->consumer->reject($message, true);
+ /**
+ * Non blocking function. Returns message or null.
+ *
+ * @return InteropMessage|null
+ */
+ public function receiveNoWait()
+ {
+ if (null == $this->message) {
+ if ($message = $this->doReceive($this->receiveNoWaitCallback, $this)) {
+ $this->message = $message;
+
+ call_user_func($this->finallyCallback, $this);
}
}
- throw new \LogicException(sprintf('Time outed without receiving reply message. Timeout: %s, CorrelationId: %s', $this->timeout, $this->correlationId));
+ return $this->message;
}
/**
- * @param int $timeout
+ * On TRUE deletes reply queue after getMessage call.
+ *
+ * @param bool $delete
*/
- public function setTimeout($timeout)
+ public function setDeleteReplyQueue($delete)
{
- $this->timeout = $timeout;
+ $this->deleteReplyQueue = (bool) $delete;
}
/**
- * @return int
+ * @return bool
*/
- public function getTimeout()
+ public function isDeleteReplyQueue()
{
- return $this->timeout;
+ return $this->deleteReplyQueue;
+ }
+
+ /**
+ * @param array $args
+ *
+ * @return InteropMessage
+ */
+ private function doReceive(\Closure $cb, ...$args)
+ {
+ $message = call_user_func_array($cb, $args);
+
+ if (null !== $message && false == $message instanceof InteropMessage) {
+ throw new \RuntimeException(sprintf('Expected "%s" but got: "%s"', InteropMessage::class, is_object($message) ? $message::class : gettype($message)));
+ }
+
+ return $message;
}
}
diff --git a/pkg/enqueue/Rpc/RpcClient.php b/pkg/enqueue/Rpc/RpcClient.php
index fad0bcd69..bd3d7cedb 100644
--- a/pkg/enqueue/Rpc/RpcClient.php
+++ b/pkg/enqueue/Rpc/RpcClient.php
@@ -2,10 +2,10 @@
namespace Enqueue\Rpc;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Destination;
-use Enqueue\Psr\Message;
use Enqueue\Util\UUID;
+use Interop\Queue\Context;
+use Interop\Queue\Destination;
+use Interop\Queue\Message as InteropMessage;
class RpcClient
{
@@ -15,43 +15,45 @@ class RpcClient
private $context;
/**
- * @param Context $context
+ * @var RpcFactory
*/
- public function __construct(Context $context)
+ private $rpcFactory;
+
+ public function __construct(Context $context, ?RpcFactory $promiseFactory = null)
{
$this->context = $context;
+ $this->rpcFactory = $promiseFactory ?: new RpcFactory($context);
}
/**
- * @param Destination $destination
- * @param Message $message
- * @param $timeout
+ * @param int $timeout
+ *
+ * @throws TimeoutException if the wait timeout is reached
*
- * @return Message
+ * @return InteropMessage
*/
- public function call(Destination $destination, Message $message, $timeout)
+ public function call(Destination $destination, InteropMessage $message, $timeout)
{
- return $this->callAsync($destination, $message, $timeout)->getMessage();
+ return $this->callAsync($destination, $message, $timeout)->receive();
}
/**
- * @param Destination $destination
- * @param Message $message
- * @param $timeout
+ * @param int $timeout
*
* @return Promise
*/
- public function callAsync(Destination $destination, Message $message, $timeout)
+ public function callAsync(Destination $destination, InteropMessage $message, $timeout)
{
if ($timeout < 1) {
throw new \InvalidArgumentException(sprintf('Timeout must be positive not zero integer. Got %s', $timeout));
}
- if ($message->getReplyTo()) {
- $replyQueue = $this->context->createQueue($message->getReplyTo());
- } else {
- $replyQueue = $this->context->createTemporaryQueue();
- $message->setReplyTo($replyQueue->getQueueName());
+ $deleteReplyQueue = false;
+ $replyTo = $message->getReplyTo();
+
+ if (false == $replyTo) {
+ $message->setReplyTo($replyTo = $this->rpcFactory->createReplyTo());
+ $deleteReplyQueue = true;
}
if (false == $message->getCorrelationId()) {
@@ -60,10 +62,9 @@ public function callAsync(Destination $destination, Message $message, $timeout)
$this->context->createProducer()->send($destination, $message);
- return new Promise(
- $this->context->createConsumer($replyQueue),
- $message->getCorrelationId(),
- $timeout
- );
+ $promise = $this->rpcFactory->createPromise($replyTo, $message->getCorrelationId(), $timeout);
+ $promise->setDeleteReplyQueue($deleteReplyQueue);
+
+ return $promise;
}
}
diff --git a/pkg/enqueue/Rpc/RpcFactory.php b/pkg/enqueue/Rpc/RpcFactory.php
new file mode 100644
index 000000000..9100babd3
--- /dev/null
+++ b/pkg/enqueue/Rpc/RpcFactory.php
@@ -0,0 +1,84 @@
+context = $context;
+ }
+
+ /**
+ * @param string $replyTo
+ * @param string $correlationId
+ * @param int $timeout
+ *
+ * @return Promise
+ */
+ public function createPromise($replyTo, $correlationId, $timeout)
+ {
+ $replyQueue = $this->context->createQueue($replyTo);
+
+ $receive = function (Promise $promise, $promiseTimeout) use ($replyQueue, $timeout, $correlationId) {
+ $runTimeout = $promiseTimeout ?: $timeout;
+ $endTime = time() + ((int) ($runTimeout / 1000));
+ $consumer = $this->context->createConsumer($replyQueue);
+
+ do {
+ if ($message = $consumer->receive($runTimeout)) {
+ if ($message->getCorrelationId() === $correlationId) {
+ $consumer->acknowledge($message);
+
+ return $message;
+ }
+
+ $consumer->reject($message, true);
+ }
+ } while (time() < $endTime);
+
+ throw TimeoutException::create($runTimeout, $correlationId);
+ };
+
+ $receiveNoWait = function () use ($replyQueue, $correlationId) {
+ static $consumer;
+
+ if (null === $consumer) {
+ $consumer = $this->context->createConsumer($replyQueue);
+ }
+
+ if ($message = $consumer->receiveNoWait()) {
+ if ($message->getCorrelationId() === $correlationId) {
+ $consumer->acknowledge($message);
+
+ return $message;
+ }
+
+ $consumer->reject($message, true);
+ }
+ };
+
+ $finally = function (Promise $promise) use ($replyQueue) {
+ if ($promise->isDeleteReplyQueue() && method_exists($this->context, 'deleteQueue')) {
+ $this->context->deleteQueue($replyQueue);
+ }
+ };
+
+ return new Promise($receive, $receiveNoWait, $finally);
+ }
+
+ /**
+ * @return string
+ */
+ public function createReplyTo()
+ {
+ return $this->context->createTemporaryQueue()->getQueueName();
+ }
+}
diff --git a/pkg/enqueue/Rpc/TimeoutException.php b/pkg/enqueue/Rpc/TimeoutException.php
new file mode 100644
index 000000000..a7f68b967
--- /dev/null
+++ b/pkg/enqueue/Rpc/TimeoutException.php
@@ -0,0 +1,17 @@
+container = $container;
+ $this->defaultClient = $defaultClient;
+ $this->queueConsumerIdPattern = $queueConsumerIdPattern;
+ $this->driverIdPattern = $driverIdPattern;
+ $this->processorIdPattern = $processorIdPatter;
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this->configureLimitsExtensions();
+ $this->configureSetupBrokerExtension();
+ $this->configureQueueConsumerOptions();
+ $this->configureLoggerExtension();
+
+ $this
+ ->setAliases(['enq:c'])
+ ->setDescription('A client\'s worker that processes messages. '.
+ 'By default it connects to default queue. '.
+ 'It select an appropriate message processor based on a message headers')
+ ->addArgument('client-queue-names', InputArgument::IS_ARRAY, 'Queues to consume messages from')
+ ->addOption('skip', null, InputOption::VALUE_IS_ARRAY | InputOption::VALUE_OPTIONAL, 'Queues to skip consumption of messages from', [])
+ ->addOption('client', 'c', InputOption::VALUE_OPTIONAL, 'The client to consume messages from.', $this->defaultClient)
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $client = $input->getOption('client');
+
+ try {
+ $consumer = $this->getQueueConsumer($client);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Client "%s" is not supported.', $client), previous: $e);
+ }
+
+ $driver = $this->getDriver($client);
+ $processor = $this->getProcessor($client);
+
+ $this->setQueueConsumerOptions($consumer, $input);
+
+ $allQueues[$driver->getConfig()->getDefaultQueue()] = true;
+ $allQueues[$driver->getConfig()->getRouterQueue()] = true;
+ foreach ($driver->getRouteCollection()->all() as $route) {
+ if (false == $route->getQueue()) {
+ continue;
+ }
+ if ($route->isProcessorExternal()) {
+ continue;
+ }
+
+ $allQueues[$route->getQueue()] = $route->isPrefixQueue();
+ }
+
+ $selectedQueues = $input->getArgument('client-queue-names');
+ if (empty($selectedQueues)) {
+ $queues = $allQueues;
+ } else {
+ $queues = [];
+ foreach ($selectedQueues as $queue) {
+ if (false == array_key_exists($queue, $allQueues)) {
+ throw new \LogicException(sprintf('There is no such queue "%s". Available are "%s"', $queue, implode('", "', array_keys($allQueues))));
+ }
+
+ $queues[$queue] = $allQueues[$queue];
+ }
+ }
+
+ foreach ($input->getOption('skip') as $skipQueue) {
+ unset($queues[$skipQueue]);
+ }
+
+ foreach ($queues as $queue => $prefix) {
+ $queue = $driver->createQueue($queue, $prefix);
+ $consumer->bind($queue, $processor);
+ }
+
+ $runtimeExtensionChain = $this->getRuntimeExtensions($input, $output);
+ $exitStatusExtension = new ExitStatusExtension();
+
+ $consumer->consume(new ChainExtension([$runtimeExtensionChain, $exitStatusExtension]));
+
+ return $exitStatusExtension->getExitStatus() ?? 0;
+ }
+
+ protected function getRuntimeExtensions(InputInterface $input, OutputInterface $output): ExtensionInterface
+ {
+ $extensions = [];
+ $extensions = array_merge($extensions, $this->getLimitsExtensions($input, $output));
+
+ $driver = $this->getDriver($input->getOption('client'));
+
+ if ($setupBrokerExtension = $this->getSetupBrokerExtension($input, $driver)) {
+ $extensions[] = $setupBrokerExtension;
+ }
+
+ if ($loggerExtension = $this->getLoggerExtension($input, $output)) {
+ array_unshift($extensions, $loggerExtension);
+ }
+
+ return new ChainExtension($extensions);
+ }
+
+ private function getDriver(string $name): DriverInterface
+ {
+ return $this->container->get(sprintf($this->driverIdPattern, $name));
+ }
+
+ private function getQueueConsumer(string $name): QueueConsumerInterface
+ {
+ return $this->container->get(sprintf($this->queueConsumerIdPattern, $name));
+ }
+
+ private function getProcessor(string $name): Processor
+ {
+ return $this->container->get(sprintf($this->processorIdPattern, $name));
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/ConsumeMessagesCommand.php b/pkg/enqueue/Symfony/Client/ConsumeMessagesCommand.php
deleted file mode 100644
index 3571d79a3..000000000
--- a/pkg/enqueue/Symfony/Client/ConsumeMessagesCommand.php
+++ /dev/null
@@ -1,124 +0,0 @@
-consumer = $consumer;
- $this->processor = $processor;
- $this->queueMetaRegistry = $queueMetaRegistry;
- $this->driver = $driver;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this->configureLimitsExtensions();
- $this->configureSetupBrokerExtension();
-
- $this
- ->setName('enqueue:consume')
- ->setAliases(['enq:c'])
- ->setDescription('A client\'s worker that processes messages. '.
- 'By default it connects to default queue. '.
- 'It select an appropriate message processor based on a message headers')
- ->addArgument('client-queue-names', InputArgument::IS_ARRAY, 'Queues to consume messages from')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $queueMetas = [];
- if ($clientQueueNames = $input->getArgument('client-queue-names')) {
- foreach ($clientQueueNames as $clientQueueName) {
- $queueMetas[] = $this->queueMetaRegistry->getQueueMeta($clientQueueName);
- }
- } else {
- $queueMetas = $this->queueMetaRegistry->getQueuesMeta();
- }
-
- foreach ($queueMetas as $queueMeta) {
- $queue = $this->driver->createQueue($queueMeta->getClientName());
- $this->consumer->bind($queue, $this->processor);
- }
-
- try {
- $this->consumer->consume($this->getRuntimeExtensions($input, $output));
- } finally {
- $this->consumer->getPsrContext()->close();
- }
- }
-
- /**
- * @param InputInterface $input
- * @param OutputInterface $output
- *
- * @return ChainExtension
- */
- protected function getRuntimeExtensions(InputInterface $input, OutputInterface $output)
- {
- $extensions = [new LoggerExtension(new ConsoleLogger($output))];
- $extensions = array_merge($extensions, $this->getLimitsExtensions($input, $output));
-
- if ($setupBrokerExtension = $this->getSetupBrokerExtension($input, $this->driver)) {
- $extensions[] = $setupBrokerExtension;
- }
-
- return new ChainExtension($extensions);
- }
-}
diff --git a/pkg/enqueue/Symfony/Client/ContainerAwareProcessorRegistry.php b/pkg/enqueue/Symfony/Client/ContainerAwareProcessorRegistry.php
deleted file mode 100644
index f199167dc..000000000
--- a/pkg/enqueue/Symfony/Client/ContainerAwareProcessorRegistry.php
+++ /dev/null
@@ -1,61 +0,0 @@
-processors = $processors;
- }
-
- /**
- * @param string $processorName
- * @param string $serviceId
- */
- public function set($processorName, $serviceId)
- {
- $this->processors[$processorName] = $serviceId;
- }
-
- /**
- * {@inheritdoc}
- */
- public function get($processorName)
- {
- if (false == isset($this->processors[$processorName])) {
- throw new \LogicException(sprintf('Processor was not found. processorName: "%s"', $processorName));
- }
-
- if (null === $this->container) {
- throw new \LogicException('Container was not set');
- }
-
- $processor = $this->container->get($this->processors[$processorName]);
-
- if (false == $processor instanceof Processor) {
- throw new \LogicException(sprintf(
- 'Invalid instance of message processor. expected: "%s", got: "%s"',
- Processor::class,
- is_object($processor) ? get_class($processor) : gettype($processor)
- ));
- }
-
- return $processor;
- }
-}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPass.php
new file mode 100644
index 000000000..577f15902
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPass.php
@@ -0,0 +1,107 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+
+ $routeCollectionId = $diUtils->format('route_collection');
+ if (false == $container->hasDefinition($routeCollectionId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routeCollectionId));
+ }
+
+ $collection = RouteCollection::fromArray($container->getDefinition($routeCollectionId)->getArgument(0));
+
+ $this->exclusiveCommandsCouldNotBeRunOnDefaultQueue($collection);
+ $this->exclusiveCommandProcessorMustBeSingleOnGivenQueue($collection);
+ $this->customQueueNamesUnique($collection);
+ $this->defaultQueueMustBePrefixed($collection);
+ }
+ }
+
+ private function exclusiveCommandsCouldNotBeRunOnDefaultQueue(RouteCollection $collection): void
+ {
+ foreach ($collection->all() as $route) {
+ if ($route->isCommand() && $route->isProcessorExclusive() && false == $route->getQueue()) {
+ throw new \LogicException(sprintf('The command "%s" processor "%s" is exclusive but queue is not specified. Exclusive processors could not be run on a default queue.', $route->getSource(), $route->getProcessor()));
+ }
+ }
+ }
+
+ private function exclusiveCommandProcessorMustBeSingleOnGivenQueue(RouteCollection $collection): void
+ {
+ $prefixedQueues = [];
+ $queues = [];
+ foreach ($collection->all() as $route) {
+ if (false == $route->isCommand()) {
+ continue;
+ }
+ if (false == $route->isProcessorExclusive()) {
+ continue;
+ }
+
+ if ($route->isPrefixQueue()) {
+ if (array_key_exists($route->getQueue(), $prefixedQueues)) {
+ throw new \LogicException(sprintf('The command "%s" processor "%s" is exclusive. The queue "%s" already has another exclusive command processor "%s" bound to it.', $route->getSource(), $route->getProcessor(), $route->getQueue(), $prefixedQueues[$route->getQueue()]));
+ }
+
+ $prefixedQueues[$route->getQueue()] = $route->getProcessor();
+ } else {
+ if (array_key_exists($route->getQueue(), $queues)) {
+ throw new \LogicException(sprintf('The command "%s" processor "%s" is exclusive. The queue "%s" already has another exclusive command processor "%s" bound to it.', $route->getSource(), $route->getProcessor(), $route->getQueue(), $queues[$route->getQueue()]));
+ }
+
+ $queues[$route->getQueue()] = $route->getProcessor();
+ }
+ }
+ }
+
+ private function defaultQueueMustBePrefixed(RouteCollection $collection): void
+ {
+ foreach ($collection->all() as $route) {
+ if (false == $route->getQueue() && false == $route->isPrefixQueue()) {
+ throw new \LogicException('The default queue must be prefixed.');
+ }
+ }
+ }
+
+ private function customQueueNamesUnique(RouteCollection $collection): void
+ {
+ $prefixedQueues = [];
+ $notPrefixedQueues = [];
+
+ foreach ($collection->all() as $route) {
+ // default queue
+ $queueName = $route->getQueue();
+ if (false == $queueName) {
+ return;
+ }
+
+ $route->isPrefixQueue() ?
+ $prefixedQueues[$queueName] = $queueName :
+ $notPrefixedQueues[$queueName] = $queueName
+ ;
+ }
+
+ foreach ($notPrefixedQueues as $queueName) {
+ if (array_key_exists($queueName, $prefixedQueues)) {
+ throw new \LogicException(sprintf('There are prefixed and not prefixed queue with the same name "%s". This is not allowed.', $queueName));
+ }
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildClientExtensionsPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildClientExtensionsPass.php
new file mode 100644
index 000000000..92124f243
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildClientExtensionsPass.php
@@ -0,0 +1,63 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+ $defaultName = $container->getParameter('enqueue.default_client');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+
+ $extensionsId = $diUtils->format('client_extensions');
+ if (false == $container->hasDefinition($extensionsId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $extensionsId));
+ }
+
+ $tags = array_merge(
+ $container->findTaggedServiceIds('enqueue.client_extension'),
+ $container->findTaggedServiceIds('enqueue.client.extension') // TODO BC
+ );
+
+ $groupByPriority = [];
+ foreach ($tags as $serviceId => $tagAttributes) {
+ foreach ($tagAttributes as $tagAttribute) {
+ $client = $tagAttribute['client'] ?? $defaultName;
+
+ if ($client !== $name && 'all' !== $client) {
+ continue;
+ }
+
+ $priority = (int) ($tagAttribute['priority'] ?? 0);
+
+ $groupByPriority[$priority][] = new Reference($serviceId);
+ }
+ }
+
+ krsort($groupByPriority, \SORT_NUMERIC);
+
+ $flatExtensions = [];
+ foreach ($groupByPriority as $extension) {
+ $flatExtensions = array_merge($flatExtensions, $extension);
+ }
+
+ $extensionsService = $container->getDefinition($extensionsId);
+ $extensionsService->replaceArgument(0, array_merge(
+ $extensionsService->getArgument(0),
+ $flatExtensions
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPass.php
new file mode 100644
index 000000000..4adc09e9d
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPass.php
@@ -0,0 +1,135 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+ $defaultName = $container->getParameter('enqueue.default_client');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+ $routeCollectionId = $diUtils->format('route_collection');
+ if (false == $container->hasDefinition($routeCollectionId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routeCollectionId));
+ }
+
+ $tag = 'enqueue.command_subscriber';
+ $routeCollection = new RouteCollection([]);
+ foreach ($container->findTaggedServiceIds($tag) as $serviceId => $tagAttributes) {
+ $processorDefinition = $container->getDefinition($serviceId);
+ if ($processorDefinition->getFactory()) {
+ throw new \LogicException('The command subscriber tag could not be applied to a service created by factory.');
+ }
+
+ $processorClass = $processorDefinition->getClass() ?? $serviceId;
+ if (false == class_exists($processorClass)) {
+ throw new \LogicException(sprintf('The processor class "%s" could not be found.', $processorClass));
+ }
+
+ if (false == is_subclass_of($processorClass, CommandSubscriberInterface::class)) {
+ throw new \LogicException(sprintf('The processor must implement "%s" interface to be used with the tag "%s"', CommandSubscriberInterface::class, $tag));
+ }
+
+ foreach ($tagAttributes as $tagAttribute) {
+ $client = $tagAttribute['client'] ?? $defaultName;
+
+ if ($client !== $name && 'all' !== $client) {
+ continue;
+ }
+
+ /** @var CommandSubscriberInterface $processorClass */
+ $commands = $processorClass::getSubscribedCommand();
+
+ if (empty($commands)) {
+ throw new \LogicException('Command subscriber must return something.');
+ }
+
+ if (is_string($commands)) {
+ $commands = [$commands];
+ }
+
+ if (!is_array($commands)) {
+ throw new \LogicException('Command subscriber configuration is invalid. Should be an array or string.');
+ }
+
+ // 0.8 command subscriber
+ if (isset($commands['processorName'])) {
+ @trigger_error('The command subscriber 0.8 syntax is deprecated since Enqueue 0.9.', \E_USER_DEPRECATED);
+
+ $source = $commands['processorName'];
+ $processor = $params['processorName'] ?? $serviceId;
+
+ $options = $commands;
+ unset(
+ $options['processorName'],
+ $options['queueName'],
+ $options['queueNameHardcoded'],
+ $options['exclusive'],
+ $options['topic'],
+ $options['source'],
+ $options['source_type'],
+ $options['processor'],
+ $options['options']
+ );
+
+ $options['processor_service_id'] = $serviceId;
+
+ if (isset($commands['queueName'])) {
+ $options['queue'] = $commands['queueName'];
+ }
+
+ if (isset($commands['queueNameHardcoded']) && $commands['queueNameHardcoded']) {
+ $options['prefix_queue'] = false;
+ }
+
+ $routeCollection->add(new Route($source, Route::COMMAND, $processor, $options));
+
+ continue;
+ }
+
+ if (isset($commands['command'])) {
+ $commands = [$commands];
+ }
+
+ foreach ($commands as $key => $params) {
+ if (is_string($params)) {
+ $routeCollection->add(new Route($params, Route::COMMAND, $serviceId, ['processor_service_id' => $serviceId]));
+ } elseif (is_array($params)) {
+ $source = $params['command'] ?? null;
+ $processor = $params['processor'] ?? $serviceId;
+ unset($params['command'], $params['source'], $params['source_type'], $params['processor'], $params['options']);
+ $options = $params;
+ $options['processor_service_id'] = $serviceId;
+
+ $routeCollection->add(new Route($source, Route::COMMAND, $processor, $options));
+ } else {
+ throw new \LogicException(sprintf('Command subscriber configuration is invalid for "%s::getSubscribedCommand()". "%s"', $processorClass, json_encode($processorClass::getSubscribedCommand())));
+ }
+ }
+ }
+ }
+
+ $rawRoutes = $routeCollection->toArray();
+
+ $routeCollectionService = $container->getDefinition($routeCollectionId);
+ $routeCollectionService->replaceArgument(0, array_merge(
+ $routeCollectionService->getArgument(0),
+ $rawRoutes
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPass.php
new file mode 100644
index 000000000..274847c90
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPass.php
@@ -0,0 +1,63 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+ $defaultName = $container->getParameter('enqueue.default_client');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+
+ $extensionsId = $diUtils->format('consumption_extensions');
+ if (false == $container->hasDefinition($extensionsId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $extensionsId));
+ }
+
+ $tags = array_merge(
+ $container->findTaggedServiceIds('enqueue.consumption_extension'),
+ $container->findTaggedServiceIds('enqueue.consumption.extension') // TODO BC
+ );
+
+ $groupByPriority = [];
+ foreach ($tags as $serviceId => $tagAttributes) {
+ foreach ($tagAttributes as $tagAttribute) {
+ $client = $tagAttribute['client'] ?? $defaultName;
+
+ if ($client !== $name && 'all' !== $client) {
+ continue;
+ }
+
+ $priority = (int) ($tagAttribute['priority'] ?? 0);
+
+ $groupByPriority[$priority][] = new Reference($serviceId);
+ }
+ }
+
+ krsort($groupByPriority, \SORT_NUMERIC);
+
+ $flatExtensions = [];
+ foreach ($groupByPriority as $extension) {
+ $flatExtensions = array_merge($flatExtensions, $extension);
+ }
+
+ $extensionsService = $container->getDefinition($extensionsId);
+ $extensionsService->replaceArgument(0, array_merge(
+ $extensionsService->getArgument(0),
+ $flatExtensions
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRegistryPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRegistryPass.php
new file mode 100644
index 000000000..3759dd209
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRegistryPass.php
@@ -0,0 +1,57 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+
+ $processorRegistryId = $diUtils->format('processor_registry');
+ if (false == $container->hasDefinition($processorRegistryId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $processorRegistryId));
+ }
+
+ $routeCollectionId = $diUtils->format('route_collection');
+ if (false == $container->hasDefinition($routeCollectionId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routeCollectionId));
+ }
+
+ $routerProcessorId = $diUtils->format('router_processor');
+ if (false == $container->hasDefinition($routerProcessorId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routerProcessorId));
+ }
+
+ $routeCollection = RouteCollection::fromArray($container->getDefinition($routeCollectionId)->getArgument(0));
+
+ $map = [];
+ foreach ($routeCollection->all() as $route) {
+ if (false == $processorServiceId = $route->getOption('processor_service_id')) {
+ throw new \LogicException('The route option "processor_service_id" is required');
+ }
+
+ $map[$route->getProcessor()] = new Reference($processorServiceId);
+ }
+
+ $map[$diUtils->parameter('router_processor')] = new Reference($routerProcessorId);
+
+ $registry = $container->getDefinition($processorRegistryId);
+ $registry->setArgument(0, ServiceLocatorTagPass::register($container, $map, $processorRegistryId));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRoutesPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRoutesPass.php
new file mode 100644
index 000000000..e88cb1f83
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildProcessorRoutesPass.php
@@ -0,0 +1,77 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+ $defaultName = $container->getParameter('enqueue.default_client');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+ $routeCollectionId = $diUtils->format('route_collection');
+ if (false == $container->hasDefinition($routeCollectionId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routeCollectionId));
+ }
+
+ $tag = 'enqueue.processor';
+ $routeCollection = new RouteCollection([]);
+ foreach ($container->findTaggedServiceIds($tag) as $serviceId => $tagAttributes) {
+ foreach ($tagAttributes as $tagAttribute) {
+ $client = $tagAttribute['client'] ?? $defaultName;
+
+ if ($client !== $name && 'all' !== $client) {
+ continue;
+ }
+
+ $topic = $tagAttribute['topic'] ?? null;
+ $command = $tagAttribute['command'] ?? null;
+
+ if (false == $topic && false == $command) {
+ throw new \LogicException(sprintf('Either "topic" or "command" tag attribute must be set on service "%s". None is set.', $serviceId));
+ }
+ if ($topic && $command) {
+ throw new \LogicException(sprintf('Either "topic" or "command" tag attribute must be set on service "%s". Both are set.', $serviceId));
+ }
+
+ $source = $command ?: $topic;
+ $sourceType = $command ? Route::COMMAND : Route::TOPIC;
+ $processor = $tagAttribute['processor'] ?? $serviceId;
+
+ unset(
+ $tagAttribute['topic'],
+ $tagAttribute['command'],
+ $tagAttribute['source'],
+ $tagAttribute['source_type'],
+ $tagAttribute['processor'],
+ $tagAttribute['options']
+ );
+ $options = $tagAttribute;
+ $options['processor_service_id'] = $serviceId;
+
+ $routeCollection->add(new Route($source, $sourceType, $processor, $options));
+ }
+ }
+
+ $rawRoutes = $routeCollection->toArray();
+
+ $routeCollectionService = $container->getDefinition($routeCollectionId);
+ $routeCollectionService->replaceArgument(0, array_merge(
+ $routeCollectionService->getArgument(0),
+ $rawRoutes
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPass.php b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPass.php
new file mode 100644
index 000000000..ef01e6fcf
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPass.php
@@ -0,0 +1,127 @@
+hasParameter('enqueue.clients')) {
+ throw new \LogicException('The "enqueue.clients" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.clients');
+ $defaultName = $container->getParameter('enqueue.default_client');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(ClientFactory::MODULE, $name);
+ $routeCollectionId = $diUtils->format('route_collection');
+ if (false == $container->hasDefinition($routeCollectionId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $routeCollectionId));
+ }
+
+ $tag = 'enqueue.topic_subscriber';
+ $routeCollection = new RouteCollection([]);
+ foreach ($container->findTaggedServiceIds($tag) as $serviceId => $tagAttributes) {
+ $processorDefinition = $container->getDefinition($serviceId);
+ if ($processorDefinition->getFactory()) {
+ throw new \LogicException('The topic subscriber tag could not be applied to a service created by factory.');
+ }
+
+ $processorClass = $processorDefinition->getClass() ?? $serviceId;
+ if (false == class_exists($processorClass)) {
+ throw new \LogicException(sprintf('The processor class "%s" could not be found.', $processorClass));
+ }
+
+ if (false == is_subclass_of($processorClass, TopicSubscriberInterface::class)) {
+ throw new \LogicException(sprintf('The processor must implement "%s" interface to be used with the tag "%s"', TopicSubscriberInterface::class, $tag));
+ }
+
+ foreach ($tagAttributes as $tagAttribute) {
+ $client = $tagAttribute['client'] ?? $defaultName;
+
+ if ($client !== $name && 'all' !== $client) {
+ continue;
+ }
+
+ /** @var TopicSubscriberInterface $processorClass */
+ $topics = $processorClass::getSubscribedTopics();
+
+ if (empty($topics)) {
+ throw new \LogicException('Topic subscriber must return something.');
+ }
+
+ if (is_string($topics)) {
+ $topics = [$topics];
+ }
+
+ if (!is_array($topics)) {
+ throw new \LogicException('Topic subscriber configuration is invalid. Should be an array or string.');
+ }
+
+ foreach ($topics as $key => $params) {
+ if (is_string($params)) {
+ $routeCollection->add(new Route($params, Route::TOPIC, $serviceId, ['processor_service_id' => $serviceId]));
+
+ // 0.8 topic subscriber
+ } elseif (is_array($params) && is_string($key)) {
+ @trigger_error('The topic subscriber 0.8 syntax is deprecated since Enqueue 0.9.', \E_USER_DEPRECATED);
+
+ $source = $key;
+ $processor = $params['processorName'] ?? $serviceId;
+
+ $options = $params;
+ unset(
+ $options['processorName'],
+ $options['queueName'],
+ $options['queueNameHardcoded'],
+ $options['topic'],
+ $options['source'],
+ $options['source_type'],
+ $options['processor'],
+ $options['options']
+ );
+
+ $options['processor_service_id'] = $serviceId;
+
+ if (isset($params['queueName'])) {
+ $options['queue'] = $params['queueName'];
+ }
+
+ if (isset($params['queueNameHardcoded']) && $params['queueNameHardcoded']) {
+ $options['prefix_queue'] = false;
+ }
+
+ $routeCollection->add(new Route($source, Route::TOPIC, $processor, $options));
+ } elseif (is_array($params)) {
+ $source = $params['topic'] ?? null;
+ $processor = $params['processor'] ?? $serviceId;
+ unset($params['topic'], $params['source'], $params['source_type'], $params['processor'], $params['options']);
+ $options = $params;
+ $options['processor_service_id'] = $serviceId;
+
+ $routeCollection->add(new Route($source, Route::TOPIC, $processor, $options));
+ } else {
+ throw new \LogicException(sprintf('Topic subscriber configuration is invalid for "%s::getSubscribedTopics()". Got "%s"', $processorClass, json_encode($processorClass::getSubscribedTopics())));
+ }
+ }
+ }
+ }
+
+ $rawRoutes = $routeCollection->toArray();
+
+ $routeCollectionService = $container->getDefinition($routeCollectionId);
+ $routeCollectionService->replaceArgument(0, array_merge(
+ $routeCollectionService->getArgument(0),
+ $rawRoutes
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/DependencyInjection/ClientFactory.php b/pkg/enqueue/Symfony/Client/DependencyInjection/ClientFactory.php
new file mode 100644
index 000000000..be020dcff
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/DependencyInjection/ClientFactory.php
@@ -0,0 +1,254 @@
+default = $default;
+ $this->diUtils = DiUtils::create(self::MODULE, $name);
+ }
+
+ public static function getConfiguration(bool $debug, string $name = 'client'): NodeDefinition
+ {
+ $builder = new ArrayNodeDefinition($name);
+
+ $builder->children()
+ ->booleanNode('traceable_producer')->defaultValue($debug)->end()
+ ->scalarNode('prefix')->defaultValue('enqueue')->end()
+ ->scalarNode('separator')->defaultValue('.')->end()
+ ->scalarNode('app_name')->defaultValue('app')->end()
+ ->scalarNode('router_topic')->defaultValue('default')->cannotBeEmpty()->end()
+ ->scalarNode('router_queue')->defaultValue('default')->cannotBeEmpty()->end()
+ ->scalarNode('router_processor')->defaultNull()->end()
+ ->integerNode('redelivered_delay_time')->min(0)->defaultValue(0)->end()
+ ->scalarNode('default_queue')->defaultValue('default')->cannotBeEmpty()->end()
+ ->arrayNode('driver_options')->addDefaultsIfNotSet()->info('The array contains driver specific options')->ignoreExtraKeys(false)->end()
+ ->end()
+ ;
+
+ return $builder;
+ }
+
+ public function build(ContainerBuilder $container, array $config): void
+ {
+ $container->register($this->diUtils->format('context'), Context::class)
+ ->setFactory([$this->diUtils->reference('driver'), 'getContext'])
+ ;
+
+ $container->register($this->diUtils->format('driver_factory'), DriverFactory::class);
+
+ $routerProcessor = empty($config['router_processor'])
+ ? $this->diUtils->format('router_processor')
+ : $config['router_processor']
+ ;
+
+ $container->register($this->diUtils->format('config'), Config::class)
+ ->setArguments([
+ $config['prefix'],
+ $config['separator'],
+ $config['app_name'],
+ $config['router_topic'],
+ $config['router_queue'],
+ $config['default_queue'],
+ $routerProcessor,
+ $config['transport'],
+ $config['driver_options'] ?? [],
+ ]);
+
+ $container->setParameter($this->diUtils->format('router_processor'), $routerProcessor);
+ $container->setParameter($this->diUtils->format('router_queue_name'), $config['router_queue']);
+ $container->setParameter($this->diUtils->format('default_queue_name'), $config['default_queue']);
+
+ $container->register($this->diUtils->format('route_collection'), RouteCollection::class)
+ ->addArgument([])
+ ->setFactory([RouteCollection::class, 'fromArray'])
+ ;
+
+ $container->register($this->diUtils->format('producer'), Producer::class)
+ // @deprecated
+ ->setPublic(true)
+ ->addArgument($this->diUtils->reference('driver'))
+ ->addArgument($this->diUtils->reference('rpc_factory'))
+ ->addArgument($this->diUtils->reference('client_extensions'))
+ ;
+
+ $lazyProducer = $container->register($this->diUtils->format('lazy_producer'), LazyProducer::class);
+ $lazyProducer->addArgument(ServiceLocatorTagPass::register($container, [
+ $this->diUtils->format('producer') => new Reference($this->diUtils->format('producer')),
+ ]));
+ $lazyProducer->addArgument($this->diUtils->format('producer'));
+
+ $container->register($this->diUtils->format('spool_producer'), SpoolProducer::class)
+ ->addArgument($this->diUtils->reference('lazy_producer'))
+ ;
+
+ $container->register($this->diUtils->format('client_extensions'), ChainExtension::class)
+ ->addArgument([])
+ ;
+
+ $container->register($this->diUtils->format('rpc_factory'), RpcFactory::class)
+ ->addArgument($this->diUtils->reference('context'))
+ ;
+
+ $container->register($this->diUtils->format('router_processor'), RouterProcessor::class)
+ ->addArgument($this->diUtils->reference('driver'))
+ ;
+
+ $container->register($this->diUtils->format('processor_registry'), ContainerProcessorRegistry::class);
+
+ $container->register($this->diUtils->format('delegate_processor'), DelegateProcessor::class)
+ ->addArgument($this->diUtils->reference('processor_registry'))
+ ;
+
+ $container->register($this->diUtils->format('set_router_properties_extension'), SetRouterPropertiesExtension::class)
+ ->addArgument($this->diUtils->reference('driver'))
+ ->addTag('enqueue.consumption_extension', ['priority' => 100, 'client' => $this->diUtils->getConfigName()])
+ ;
+
+ $container->register($this->diUtils->format('queue_consumer'), QueueConsumer::class)
+ ->addArgument($this->diUtils->reference('context'))
+ ->addArgument($this->diUtils->reference('consumption_extensions'))
+ ->addArgument([])
+ ->addArgument(new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE))
+ ->addArgument($config['consumption']['receive_timeout'])
+ ;
+
+ $container->register($this->diUtils->format('consumption_extensions'), ConsumptionChainExtension::class)
+ ->addArgument([])
+ ;
+
+ $container->register($this->diUtils->format('flush_spool_producer_extension'), FlushSpoolProducerExtension::class)
+ ->addArgument($this->diUtils->reference('spool_producer'))
+ ->addTag('enqueue.consumption.extension', ['priority' => -100, 'client' => $this->diUtils->getConfigName()])
+ ;
+
+ $container->register($this->diUtils->format('exclusive_command_extension'), ExclusiveCommandExtension::class)
+ ->addArgument($this->diUtils->reference('driver'))
+ ->addTag('enqueue.consumption.extension', ['priority' => 100, 'client' => $this->diUtils->getConfigName()])
+ ;
+
+ if ($config['traceable_producer']) {
+ $container->register($this->diUtils->format('traceable_producer'), TraceableProducer::class)
+ ->setDecoratedService($this->diUtils->format('producer'))
+ ->addArgument($this->diUtils->reference('traceable_producer.inner'))
+ ;
+ }
+
+ if ($config['redelivered_delay_time']) {
+ $container->register($this->diUtils->format('delay_redelivered_message_extension'), DelayRedeliveredMessageExtension::class)
+ ->addArgument($this->diUtils->reference('driver'))
+ ->addArgument($config['redelivered_delay_time'])
+ ->addTag('enqueue.consumption_extension', ['priority' => 10, 'client' => $this->diUtils->getConfigName()])
+ ;
+
+ $container->getDefinition($this->diUtils->format('delay_redelivered_message_extension'))
+ ->replaceArgument(1, $config['redelivered_delay_time'])
+ ;
+ }
+
+ $locatorId = 'enqueue.locator';
+ if ($container->hasDefinition($locatorId)) {
+ $locator = $container->getDefinition($locatorId);
+ $locator->replaceArgument(0, array_replace($locator->getArgument(0), [
+ $this->diUtils->format('queue_consumer') => $this->diUtils->reference('queue_consumer'),
+ $this->diUtils->format('driver') => $this->diUtils->reference('driver'),
+ $this->diUtils->format('delegate_processor') => $this->diUtils->reference('delegate_processor'),
+ $this->diUtils->format('producer') => $this->diUtils->reference('lazy_producer'),
+ ]));
+ }
+
+ if ($this->default) {
+ $container->setAlias(ProducerInterface::class, $this->diUtils->format('lazy_producer'));
+ $container->setAlias(SpoolProducer::class, $this->diUtils->format('spool_producer'));
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('producer'), $this->diUtils->format('producer'));
+ $container->setAlias($this->diUtils->formatDefault('spool_producer'), $this->diUtils->format('spool_producer'));
+ }
+ }
+ }
+
+ public function createDriver(ContainerBuilder $container, array $config): string
+ {
+ $factoryId = DiUtils::create(TransportFactory::MODULE, $this->diUtils->getConfigName())->format('connection_factory');
+ $driverId = $this->diUtils->format('driver');
+ $driverFactoryId = $this->diUtils->format('driver_factory');
+
+ $container->register($driverId, DriverInterface::class)
+ ->setFactory([new Reference($driverFactoryId), 'create'])
+ ->addArgument(new Reference($factoryId))
+ ->addArgument($this->diUtils->reference('config'))
+ ->addArgument($this->diUtils->reference('route_collection'))
+ ;
+
+ if ($this->default) {
+ $container->setAlias(DriverInterface::class, $driverId);
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('driver'), $driverId);
+ }
+ }
+
+ return $driverId;
+ }
+
+ public function createFlushSpoolProducerListener(ContainerBuilder $container): void
+ {
+ $container->register($this->diUtils->format('flush_spool_producer_listener'), FlushSpoolProducerListener::class)
+ ->addArgument($this->diUtils->reference('spool_producer'))
+ ->addTag('kernel.event_subscriber')
+ ;
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/FlushSpoolProducerListener.php b/pkg/enqueue/Symfony/Client/FlushSpoolProducerListener.php
new file mode 100644
index 000000000..00543f6de
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/FlushSpoolProducerListener.php
@@ -0,0 +1,41 @@
+producer = $producer;
+ }
+
+ public function flushMessages()
+ {
+ $this->producer->flush();
+ }
+
+ public static function getSubscribedEvents(): array
+ {
+ $events = [];
+
+ if (class_exists(KernelEvents::class)) {
+ $events[KernelEvents::TERMINATE] = 'flushMessages';
+ }
+
+ if (class_exists(ConsoleEvents::class)) {
+ $events[ConsoleEvents::TERMINATE] = 'flushMessages';
+ }
+
+ return $events;
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/LazyProducer.php b/pkg/enqueue/Symfony/Client/LazyProducer.php
new file mode 100644
index 000000000..8dd3aadba
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/LazyProducer.php
@@ -0,0 +1,37 @@
+container = $container;
+ $this->producerId = $producerId;
+ }
+
+ public function sendEvent(string $topic, $message): void
+ {
+ $this->getRealProducer()->sendEvent($topic, $message);
+ }
+
+ public function sendCommand(string $command, $message, bool $needReply = false): ?Promise
+ {
+ return $this->getRealProducer()->sendCommand($command, $message, $needReply);
+ }
+
+ private function getRealProducer(): ProducerInterface
+ {
+ return $this->container->get($this->producerId);
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/Meta/QueuesCommand.php b/pkg/enqueue/Symfony/Client/Meta/QueuesCommand.php
deleted file mode 100644
index 6f37679dc..000000000
--- a/pkg/enqueue/Symfony/Client/Meta/QueuesCommand.php
+++ /dev/null
@@ -1,73 +0,0 @@
-destinationRegistry = $queueRegistry;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this
- ->setName('enqueue:queues')
- ->setAliases([
- 'enq:m:q',
- 'debug:enqueue:queues',
- ])
- ->setDescription('A command shows all available queues and some information about them.')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $table = new Table($output);
- $table->setHeaders(['Client Name', 'Transport Name', 'processors']);
-
- $count = 0;
- $firstRow = true;
- foreach ($this->destinationRegistry->getQueuesMeta() as $destination) {
- if (false == $firstRow) {
- $table->addRow(new TableSeparator());
- }
-
- $table->addRow([
- $destination->getClientName(),
- $destination->getTransportName(),
- implode(PHP_EOL, $destination->getProcessors()),
- ]);
-
- ++$count;
- $firstRow = false;
- }
-
- $output->writeln(sprintf('Found %s destinations', $count));
- $output->writeln('');
- $table->render();
- }
-}
diff --git a/pkg/enqueue/Symfony/Client/Meta/TopicsCommand.php b/pkg/enqueue/Symfony/Client/Meta/TopicsCommand.php
deleted file mode 100644
index 8314fb99a..000000000
--- a/pkg/enqueue/Symfony/Client/Meta/TopicsCommand.php
+++ /dev/null
@@ -1,69 +0,0 @@
-topicRegistry = $topicRegistry;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this
- ->setName('enqueue:topics')
- ->setAliases([
- 'enq:m:t',
- 'debug:enqueue:topics',
- ])
- ->setDescription('A command shows all available topics and some information about them.')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $table = new Table($output);
- $table->setHeaders(['Topic', 'Description', 'processors']);
-
- $count = 0;
- $firstRow = true;
- foreach ($this->topicRegistry->getTopicsMeta() as $topic) {
- if (false == $firstRow) {
- $table->addRow(new TableSeparator());
- }
-
- $table->addRow([$topic->getName(), $topic->getDescription(), implode(PHP_EOL, $topic->getProcessors())]);
-
- ++$count;
- $firstRow = false;
- }
-
- $output->writeln(sprintf('Found %s topics', $count));
- $output->writeln('');
- $table->render();
- }
-}
diff --git a/pkg/enqueue/Symfony/Client/ProduceCommand.php b/pkg/enqueue/Symfony/Client/ProduceCommand.php
new file mode 100644
index 000000000..953a76687
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/ProduceCommand.php
@@ -0,0 +1,92 @@
+container = $container;
+ $this->defaultClient = $defaultClient;
+ $this->producerIdPattern = $producerIdPattern;
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this
+ ->setDescription('Sends an event to the topic')
+ ->addArgument('message', InputArgument::REQUIRED, 'A message')
+ ->addOption('header', null, InputOption::VALUE_REQUIRED | InputOption::VALUE_IS_ARRAY, 'The message headers')
+ ->addOption('client', 'c', InputOption::VALUE_OPTIONAL, 'The client to consume messages from.', $this->defaultClient)
+ ->addOption('topic', null, InputOption::VALUE_OPTIONAL, 'The topic to send a message to')
+ ->addOption('command', null, InputOption::VALUE_OPTIONAL, 'The command to send a message to')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $topic = $input->getOption('topic');
+ $command = $input->getOption('command');
+ $message = $input->getArgument('message');
+ $headers = (array) $input->getOption('header');
+ $client = $input->getOption('client');
+
+ if ($topic && $command) {
+ throw new \LogicException('Either topic or command option should be set, both are set.');
+ }
+
+ try {
+ $producer = $this->getProducer($client);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Client "%s" is not supported.', $client), previous: $e);
+ }
+
+ if ($topic) {
+ $producer->sendEvent($topic, new Message($message, [], $headers));
+
+ $output->writeln('An event is sent');
+ } elseif ($command) {
+ $producer->sendCommand($command, $message);
+
+ $output->writeln('A command is sent');
+ } else {
+ throw new \LogicException('Either topic or command option should be set, none is set.');
+ }
+
+ return 0;
+ }
+
+ private function getProducer(string $client): ProducerInterface
+ {
+ return $this->container->get(sprintf($this->producerIdPattern, $client));
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/ProduceMessageCommand.php b/pkg/enqueue/Symfony/Client/ProduceMessageCommand.php
deleted file mode 100644
index 15f0a7874..000000000
--- a/pkg/enqueue/Symfony/Client/ProduceMessageCommand.php
+++ /dev/null
@@ -1,54 +0,0 @@
-producer = $producer;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this
- ->setName('enqueue:produce')
- ->setAliases(['enq:p'])
- ->setDescription('A command to send a message to topic')
- ->addArgument('topic', InputArgument::REQUIRED, 'A topic to send message to')
- ->addArgument('message', InputArgument::REQUIRED, 'A message to send')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $this->producer->send(
- $input->getArgument('topic'),
- $input->getArgument('message')
- );
-
- $output->writeln('Message is sent');
- }
-}
diff --git a/pkg/enqueue/Symfony/Client/RoutesCommand.php b/pkg/enqueue/Symfony/Client/RoutesCommand.php
new file mode 100644
index 000000000..04b657ef7
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/RoutesCommand.php
@@ -0,0 +1,162 @@
+container = $container;
+ $this->defaultClient = $defaultClient;
+ $this->driverIdPatter = $driverIdPatter;
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this
+ ->setAliases(['debug:enqueue:routes'])
+ ->setDescription('A command lists all registered routes.')
+ ->addOption('show-route-options', null, InputOption::VALUE_NONE, 'Adds ability to hide options.')
+ ->addOption('client', 'c', InputOption::VALUE_OPTIONAL, 'The client to consume messages from.', $this->defaultClient)
+ ;
+
+ $this->driver = null;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ try {
+ $this->driver = $this->getDriver($input->getOption('client'));
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Client "%s" is not supported.', $input->getOption('client')), previous: $e);
+ }
+
+ $routes = $this->driver->getRouteCollection()->all();
+ $output->writeln(sprintf('Found %s routes', count($routes)));
+ $output->writeln('');
+
+ if ($routes) {
+ $table = new Table($output);
+ $table->setHeaders(['Type', 'Source', 'Queue', 'Processor', 'Options']);
+
+ $firstRow = true;
+ foreach ($routes as $route) {
+ if (false == $firstRow) {
+ $table->addRow(new TableSeparator());
+
+ $firstRow = false;
+ }
+
+ if ($route->isCommand()) {
+ continue;
+ }
+
+ $table->addRow([
+ $this->formatSourceType($route),
+ $route->getSource(),
+ $this->formatQueue($route),
+ $this->formatProcessor($route),
+ $input->getOption('show-route-options') ? $this->formatOptions($route) : '(hidden)',
+ ]);
+ }
+
+ foreach ($routes as $route) {
+ if ($route->isTopic()) {
+ continue;
+ }
+
+ $table->addRow([
+ $this->formatSourceType($route),
+ $route->getSource(),
+ $this->formatQueue($route),
+ $this->formatProcessor($route),
+ $input->getOption('show-route-options') ? $this->formatOptions($route) : '(hidden)',
+ ]);
+ }
+
+ $table->render();
+ }
+
+ return 0;
+ }
+
+ private function formatSourceType(Route $route): string
+ {
+ if ($route->isCommand()) {
+ return 'command';
+ }
+
+ if ($route->isTopic()) {
+ return 'topic';
+ }
+
+ return 'unknown';
+ }
+
+ private function formatProcessor(Route $route): string
+ {
+ if ($route->isProcessorExternal()) {
+ return 'n\a (external)';
+ }
+
+ $processor = $route->getProcessor();
+
+ return $route->isProcessorExclusive() ? $processor.' (exclusive)' : $processor;
+ }
+
+ private function formatQueue(Route $route): string
+ {
+ $queue = $route->getQueue() ?: $this->driver->getConfig()->getDefaultQueue();
+
+ return $route->isPrefixQueue() ? $queue.' (prefixed)' : $queue.' (as is)';
+ }
+
+ private function formatOptions(Route $route): string
+ {
+ return var_export($route->getOptions(), true);
+ }
+
+ private function getDriver(string $client): DriverInterface
+ {
+ return $this->container->get(sprintf($this->driverIdPatter, $client));
+ }
+}
+
+function enqueue()
+{
+}
diff --git a/pkg/enqueue/Symfony/Client/SetupBrokerCommand.php b/pkg/enqueue/Symfony/Client/SetupBrokerCommand.php
index c825bd2c4..92d5ad022 100644
--- a/pkg/enqueue/Symfony/Client/SetupBrokerCommand.php
+++ b/pkg/enqueue/Symfony/Client/SetupBrokerCommand.php
@@ -3,47 +3,68 @@
namespace Enqueue\Symfony\Client;
use Enqueue\Client\DriverInterface;
+use Psr\Container\ContainerInterface;
+use Psr\Container\NotFoundExceptionInterface;
+use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\OutputInterface;
+#[AsCommand('enqueue:setup-broker')]
class SetupBrokerCommand extends Command
{
/**
- * @var DriverInterface
+ * @var ContainerInterface
*/
- private $driver;
+ private $container;
/**
- * @param DriverInterface $driver
+ * @var string
*/
- public function __construct(DriverInterface $driver)
+ private $defaultClient;
+
+ /**
+ * @var string
+ */
+ private $driverIdPattern;
+
+ public function __construct(ContainerInterface $container, string $defaultClient, string $driverIdPattern = 'enqueue.client.%s.driver')
{
- parent::__construct(null);
+ $this->container = $container;
+ $this->defaultClient = $defaultClient;
+ $this->driverIdPattern = $driverIdPattern;
- $this->driver = $driver;
+ parent::__construct();
}
- /**
- * {@inheritdoc}
- */
- protected function configure()
+ protected function configure(): void
{
$this
- ->setName('enqueue:setup-broker')
->setAliases(['enq:sb'])
- ->setDescription('Creates all required queues')
+ ->setDescription('Setup broker. Configure the broker, creates queues, topics and so on.')
+ ->addOption('client', 'c', InputOption::VALUE_OPTIONAL, 'The client to consume messages from.', $this->defaultClient)
;
}
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
- $output->writeln('Setup Broker');
+ $client = $input->getOption('client');
+
+ try {
+ $this->getDriver($client)->setupBroker(new ConsoleLogger($output));
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Client "%s" is not supported.', $client), previous: $e);
+ }
+
+ $output->writeln('Broker set up');
- $this->driver->setupBroker(new ConsoleLogger($output));
+ return 0;
+ }
+
+ private function getDriver(string $client): DriverInterface
+ {
+ return $this->container->get(sprintf($this->driverIdPattern, $client));
}
}
diff --git a/pkg/enqueue/Symfony/Client/SetupBrokerExtensionCommandTrait.php b/pkg/enqueue/Symfony/Client/SetupBrokerExtensionCommandTrait.php
index 2888f5636..bcc4f7bb2 100644
--- a/pkg/enqueue/Symfony/Client/SetupBrokerExtensionCommandTrait.php
+++ b/pkg/enqueue/Symfony/Client/SetupBrokerExtensionCommandTrait.php
@@ -10,9 +10,6 @@
trait SetupBrokerExtensionCommandTrait
{
- /**
- * {@inheritdoc}
- */
protected function configureSetupBrokerExtension()
{
$this
@@ -21,10 +18,7 @@ protected function configureSetupBrokerExtension()
}
/**
- * @param InputInterface $input
- * @param DriverInterface $driver
- *
- * @return ExtensionInterface
+ * @return ExtensionInterface|null
*/
protected function getSetupBrokerExtension(InputInterface $input, DriverInterface $driver)
{
diff --git a/pkg/enqueue/Symfony/Client/SimpleConsumeCommand.php b/pkg/enqueue/Symfony/Client/SimpleConsumeCommand.php
new file mode 100644
index 000000000..fafc35d05
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/SimpleConsumeCommand.php
@@ -0,0 +1,26 @@
+ $queueConsumer,
+ 'driver' => $driver,
+ 'processor' => $processor,
+ ]),
+ 'default',
+ 'queue_consumer',
+ 'driver',
+ 'processor'
+ );
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/SimpleProduceCommand.php b/pkg/enqueue/Symfony/Client/SimpleProduceCommand.php
new file mode 100644
index 000000000..5d7f76533
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/SimpleProduceCommand.php
@@ -0,0 +1,18 @@
+ $producer]),
+ 'default',
+ 'producer'
+ );
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/SimpleRoutesCommand.php b/pkg/enqueue/Symfony/Client/SimpleRoutesCommand.php
new file mode 100644
index 000000000..0023f14ae
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/SimpleRoutesCommand.php
@@ -0,0 +1,18 @@
+ $driver]),
+ 'default',
+ 'driver'
+ );
+ }
+}
diff --git a/pkg/enqueue/Symfony/Client/SimpleSetupBrokerCommand.php b/pkg/enqueue/Symfony/Client/SimpleSetupBrokerCommand.php
new file mode 100644
index 000000000..aae19f84b
--- /dev/null
+++ b/pkg/enqueue/Symfony/Client/SimpleSetupBrokerCommand.php
@@ -0,0 +1,18 @@
+ $driver]),
+ 'default',
+ 'driver'
+ );
+ }
+}
diff --git a/pkg/enqueue/Symfony/Consumption/ChooseLoggerCommandTrait.php b/pkg/enqueue/Symfony/Consumption/ChooseLoggerCommandTrait.php
new file mode 100644
index 000000000..c229c14b0
--- /dev/null
+++ b/pkg/enqueue/Symfony/Consumption/ChooseLoggerCommandTrait.php
@@ -0,0 +1,35 @@
+addOption('logger', null, InputOption::VALUE_OPTIONAL, 'A logger to be used. Could be "default", "null", "stdout".', 'default')
+ ;
+ }
+
+ protected function getLoggerExtension(InputInterface $input, OutputInterface $output): ?LoggerExtension
+ {
+ $logger = $input->getOption('logger');
+ switch ($logger) {
+ case 'null':
+ return new LoggerExtension(new NullLogger());
+ case 'stdout':
+ return new LoggerExtension(new ConsoleLogger($output));
+ case 'default':
+ return null;
+ default:
+ throw new \LogicException(sprintf('The logger "%s" is not supported', $logger));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Consumption/ConfigurableConsumeCommand.php b/pkg/enqueue/Symfony/Consumption/ConfigurableConsumeCommand.php
new file mode 100644
index 000000000..34cb66d57
--- /dev/null
+++ b/pkg/enqueue/Symfony/Consumption/ConfigurableConsumeCommand.php
@@ -0,0 +1,122 @@
+container = $container;
+ $this->defaultTransport = $defaultTransport;
+ $this->queueConsumerIdPattern = $queueConsumerIdPattern;
+ $this->processorRegistryIdPattern = $processorRegistryIdPattern;
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this->configureLimitsExtensions();
+ $this->configureQueueConsumerOptions();
+ $this->configureLoggerExtension();
+
+ $this
+ ->setDescription('A worker that consumes message from a broker. '.
+ 'To use this broker you have to explicitly set a queue to consume from '.
+ 'and a message processor service')
+ ->addArgument('processor', InputArgument::REQUIRED, 'A message processor.')
+ ->addArgument('queues', InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'A queue to consume from', [])
+ ->addOption('transport', 't', InputOption::VALUE_OPTIONAL, 'The transport to consume messages from.', $this->defaultTransport)
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $transport = $input->getOption('transport');
+
+ try {
+ $consumer = $this->getQueueConsumer($transport);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Transport "%s" is not supported.', $transport), previous: $e);
+ }
+
+ $this->setQueueConsumerOptions($consumer, $input);
+
+ $processor = $this->getProcessorRegistry($transport)->get($input->getArgument('processor'));
+
+ $queues = $input->getArgument('queues');
+ if (empty($queues) && $processor instanceof QueueSubscriberInterface) {
+ $queues = $processor::getSubscribedQueues();
+ }
+
+ if (empty($queues)) {
+ throw new \LogicException(sprintf('The queue is not provided. The processor must implement "%s" interface and it must return not empty array of queues or a queue set using as a second argument.', QueueSubscriberInterface::class));
+ }
+
+ $extensions = $this->getLimitsExtensions($input, $output);
+
+ if ($loggerExtension = $this->getLoggerExtension($input, $output)) {
+ array_unshift($extensions, $loggerExtension);
+ }
+
+ foreach ($queues as $queue) {
+ $consumer->bind($queue, $processor);
+ }
+
+ $consumer->consume(new ChainExtension($extensions));
+
+ return 0;
+ }
+
+ private function getQueueConsumer(string $name): QueueConsumerInterface
+ {
+ return $this->container->get(sprintf($this->queueConsumerIdPattern, $name));
+ }
+
+ private function getProcessorRegistry(string $name): ProcessorRegistryInterface
+ {
+ return $this->container->get(sprintf($this->processorRegistryIdPattern, $name));
+ }
+}
diff --git a/pkg/enqueue/Symfony/Consumption/ConsumeCommand.php b/pkg/enqueue/Symfony/Consumption/ConsumeCommand.php
new file mode 100644
index 000000000..b69ae7269
--- /dev/null
+++ b/pkg/enqueue/Symfony/Consumption/ConsumeCommand.php
@@ -0,0 +1,91 @@
+container = $container;
+ $this->defaultTransport = $defaultTransport;
+ $this->queueConsumerIdPattern = $queueConsumerIdPattern;
+
+ parent::__construct();
+ }
+
+ protected function configure(): void
+ {
+ $this->configureLimitsExtensions();
+ $this->configureQueueConsumerOptions();
+ $this->configureLoggerExtension();
+
+ $this
+ ->addOption('transport', 't', InputOption::VALUE_OPTIONAL, 'The transport to consume messages from.', $this->defaultTransport)
+ ->setDescription('A worker that consumes message from a broker. '.
+ 'To use this broker you have to configure queue consumer before adding to the command')
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $transport = $input->getOption('transport');
+
+ try {
+ // QueueConsumer must be pre configured outside of the command!
+ $consumer = $this->getQueueConsumer($transport);
+ } catch (NotFoundExceptionInterface $e) {
+ throw new \LogicException(sprintf('Transport "%s" is not supported.', $transport), previous: $e);
+ }
+
+ $this->setQueueConsumerOptions($consumer, $input);
+
+ $extensions = $this->getLimitsExtensions($input, $output);
+
+ if ($loggerExtension = $this->getLoggerExtension($input, $output)) {
+ array_unshift($extensions, $loggerExtension);
+ }
+
+ $exitStatusExtension = new ExitStatusExtension();
+ array_unshift($extensions, $exitStatusExtension);
+
+ $consumer->consume(new ChainExtension($extensions));
+
+ return $exitStatusExtension->getExitStatus() ?? 0;
+ }
+
+ private function getQueueConsumer(string $name): QueueConsumerInterface
+ {
+ return $this->container->get(sprintf($this->queueConsumerIdPattern, $name));
+ }
+}
diff --git a/pkg/enqueue/Symfony/Consumption/ConsumeMessagesCommand.php b/pkg/enqueue/Symfony/Consumption/ConsumeMessagesCommand.php
deleted file mode 100644
index 2f1ed8c7f..000000000
--- a/pkg/enqueue/Symfony/Consumption/ConsumeMessagesCommand.php
+++ /dev/null
@@ -1,65 +0,0 @@
-consumer = $consumer;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this->configureLimitsExtensions();
-
- $this
- ->setName('enqueue:transport:consume')
- ->setDescription('A worker that consumes message from a broker. '.
- 'To use this broker you have to configure queue consumer before adding to the command')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $extensions = $this->getLimitsExtensions($input, $output);
- array_unshift($extensions, new LoggerExtension(new ConsoleLogger($output)));
-
- $runtimeExtensions = new ChainExtension($extensions);
-
- try {
- $this->consumer->consume($runtimeExtensions);
- } finally {
- $this->consumer->getPsrContext()->close();
- }
- }
-}
diff --git a/pkg/enqueue/Symfony/Consumption/ContainerAwareConsumeMessagesCommand.php b/pkg/enqueue/Symfony/Consumption/ContainerAwareConsumeMessagesCommand.php
deleted file mode 100644
index ad899cae6..000000000
--- a/pkg/enqueue/Symfony/Consumption/ContainerAwareConsumeMessagesCommand.php
+++ /dev/null
@@ -1,88 +0,0 @@
-consumer = $consumer;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function configure()
- {
- $this->configureLimitsExtensions();
-
- $this
- ->setName('enqueue:transport:consume')
- ->setDescription('A worker that consumes message from a broker. '.
- 'To use this broker you have to explicitly set a queue to consume from '.
- 'and a message processor service')
- ->addArgument('queue', InputArgument::REQUIRED, 'Queues to consume from')
- ->addArgument('processor-service', InputArgument::REQUIRED, 'A message processor service')
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- protected function execute(InputInterface $input, OutputInterface $output)
- {
- $queueName = $input->getArgument('queue');
-
- /** @var Processor $processor */
- $processor = $this->container->get($input->getArgument('processor-service'));
- if (!$processor instanceof Processor) {
- throw new \LogicException(sprintf(
- 'Invalid message processor service given. It must be an instance of %s but %s',
- Processor::class,
- get_class($processor)
- ));
- }
-
- $extensions = $this->getLimitsExtensions($input, $output);
- array_unshift($extensions, new LoggerExtension(new ConsoleLogger($output)));
-
- $runtimeExtensions = new ChainExtension($extensions);
-
- try {
- $queue = $this->consumer->getPsrContext()->createQueue($queueName);
- // @todo set additional queue options
-
- $this->consumer->bind($queue, $processor);
- $this->consumer->consume($runtimeExtensions);
- } finally {
- $this->consumer->getPsrContext()->close();
- }
- }
-}
diff --git a/pkg/enqueue/Symfony/Consumption/LimitsExtensionsCommandTrait.php b/pkg/enqueue/Symfony/Consumption/LimitsExtensionsCommandTrait.php
index 34bc76d37..d8351acc5 100644
--- a/pkg/enqueue/Symfony/Consumption/LimitsExtensionsCommandTrait.php
+++ b/pkg/enqueue/Symfony/Consumption/LimitsExtensionsCommandTrait.php
@@ -5,6 +5,7 @@
use Enqueue\Consumption\Extension\LimitConsumedMessagesExtension;
use Enqueue\Consumption\Extension\LimitConsumerMemoryExtension;
use Enqueue\Consumption\Extension\LimitConsumptionTimeExtension;
+use Enqueue\Consumption\Extension\NicenessExtension;
use Enqueue\Consumption\ExtensionInterface;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
@@ -12,21 +13,16 @@
trait LimitsExtensionsCommandTrait
{
- /**
- * {@inheritdoc}
- */
protected function configureLimitsExtensions()
{
$this
->addOption('message-limit', null, InputOption::VALUE_REQUIRED, 'Consume n messages and exit')
->addOption('time-limit', null, InputOption::VALUE_REQUIRED, 'Consume messages during this time')
- ->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Consume messages until process reaches this memory limit in MB');
+ ->addOption('memory-limit', null, InputOption::VALUE_REQUIRED, 'Consume messages until process reaches this memory limit in MB')
+ ->addOption('niceness', null, InputOption::VALUE_REQUIRED, 'Set process niceness');
}
/**
- * @param InputInterface $input
- * @param OutputInterface $output
- *
* @throws \Exception
*
* @return ExtensionInterface[]
@@ -58,6 +54,11 @@ protected function getLimitsExtensions(InputInterface $input, OutputInterface $o
$extensions[] = new LimitConsumerMemoryExtension($memoryLimit);
}
+ $niceness = $input->getOption('niceness');
+ if (!empty($niceness) && is_numeric($niceness)) {
+ $extensions[] = new NicenessExtension((int) $niceness);
+ }
+
return $extensions;
}
}
diff --git a/pkg/enqueue/Symfony/Consumption/QueueConsumerOptionsCommandTrait.php b/pkg/enqueue/Symfony/Consumption/QueueConsumerOptionsCommandTrait.php
new file mode 100644
index 000000000..fd736f226
--- /dev/null
+++ b/pkg/enqueue/Symfony/Consumption/QueueConsumerOptionsCommandTrait.php
@@ -0,0 +1,24 @@
+addOption('receive-timeout', null, InputOption::VALUE_REQUIRED, 'The time in milliseconds queue consumer waits for a message.')
+ ;
+ }
+
+ protected function setQueueConsumerOptions(QueueConsumerInterface $consumer, InputInterface $input)
+ {
+ if (null !== $receiveTimeout = $input->getOption('receive-timeout')) {
+ $consumer->setReceiveTimeout((int) $receiveTimeout);
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/Consumption/SimpleConsumeCommand.php b/pkg/enqueue/Symfony/Consumption/SimpleConsumeCommand.php
new file mode 100644
index 000000000..90d0e362e
--- /dev/null
+++ b/pkg/enqueue/Symfony/Consumption/SimpleConsumeCommand.php
@@ -0,0 +1,18 @@
+ $consumer]),
+ 'default',
+ 'queue_consumer'
+ );
+ }
+}
diff --git a/pkg/enqueue/Symfony/ContainerProcessorRegistry.php b/pkg/enqueue/Symfony/ContainerProcessorRegistry.php
new file mode 100644
index 000000000..b259d2379
--- /dev/null
+++ b/pkg/enqueue/Symfony/ContainerProcessorRegistry.php
@@ -0,0 +1,29 @@
+locator = $locator;
+ }
+
+ public function get(string $processorName): Processor
+ {
+ if (false == $this->locator->has($processorName)) {
+ throw new \LogicException(sprintf('Service locator does not have a processor with name "%s".', $processorName));
+ }
+
+ return $this->locator->get($processorName);
+ }
+}
diff --git a/pkg/enqueue/Symfony/DefaultTransportFactory.php b/pkg/enqueue/Symfony/DefaultTransportFactory.php
deleted file mode 100644
index d6fe81006..000000000
--- a/pkg/enqueue/Symfony/DefaultTransportFactory.php
+++ /dev/null
@@ -1,75 +0,0 @@
-name = $name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function addConfiguration(ArrayNodeDefinition $builder)
- {
- $builder
- ->beforeNormalization()
- ->ifString()
- ->then(function ($v) {
- return ['alias' => $v];
- })
- ->end()
- ->children()
- ->scalarNode('alias')->isRequired()->cannotBeEmpty()->end()
- ;
- }
-
- /**
- * {@inheritdoc}
- */
- public function createContext(ContainerBuilder $container, array $config)
- {
- $contextId = sprintf('enqueue.transport.%s.context', $this->getName());
- $aliasId = sprintf('enqueue.transport.%s.context', $config['alias']);
-
- $container->setAlias($contextId, $aliasId);
- $container->setAlias('enqueue.transport.context', $contextId);
-
- return $contextId;
- }
-
- /**
- * {@inheritdoc}
- */
- public function createDriver(ContainerBuilder $container, array $config)
- {
- $driverId = sprintf('enqueue.client.%s.driver', $this->getName());
- $aliasId = sprintf('enqueue.client.%s.driver', $config['alias']);
-
- $container->setAlias($driverId, $aliasId);
- $container->setAlias('enqueue.client.driver', $driverId);
-
- return $driverId;
- }
-
- /**
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-}
diff --git a/pkg/enqueue/Symfony/DependencyInjection/BuildConsumptionExtensionsPass.php b/pkg/enqueue/Symfony/DependencyInjection/BuildConsumptionExtensionsPass.php
new file mode 100644
index 000000000..99f274ec5
--- /dev/null
+++ b/pkg/enqueue/Symfony/DependencyInjection/BuildConsumptionExtensionsPass.php
@@ -0,0 +1,60 @@
+hasParameter('enqueue.transports')) {
+ throw new \LogicException('The "enqueue.transports" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.transports');
+ $defaultName = $container->getParameter('enqueue.default_transport');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(TransportFactory::MODULE, $name);
+
+ $extensionsId = $diUtils->format('consumption_extensions');
+ if (false == $container->hasDefinition($extensionsId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $extensionsId));
+ }
+
+ $tags = $container->findTaggedServiceIds('enqueue.transport.consumption_extension');
+
+ $groupByPriority = [];
+ foreach ($tags as $serviceId => $tagAttributes) {
+ foreach ($tagAttributes as $tagAttribute) {
+ $transport = $tagAttribute['transport'] ?? $defaultName;
+
+ if ($transport !== $name && 'all' !== $transport) {
+ continue;
+ }
+
+ $priority = (int) ($tagAttribute['priority'] ?? 0);
+
+ $groupByPriority[$priority][] = new Reference($serviceId);
+ }
+ }
+
+ krsort($groupByPriority, \SORT_NUMERIC);
+
+ $flatExtensions = [];
+ foreach ($groupByPriority as $extension) {
+ $flatExtensions = array_merge($flatExtensions, $extension);
+ }
+
+ $extensionsService = $container->getDefinition($extensionsId);
+ $extensionsService->replaceArgument(0, array_merge(
+ $extensionsService->getArgument(0),
+ $flatExtensions
+ ));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/DependencyInjection/BuildProcessorRegistryPass.php b/pkg/enqueue/Symfony/DependencyInjection/BuildProcessorRegistryPass.php
new file mode 100644
index 000000000..cc6e04270
--- /dev/null
+++ b/pkg/enqueue/Symfony/DependencyInjection/BuildProcessorRegistryPass.php
@@ -0,0 +1,50 @@
+hasParameter('enqueue.transports')) {
+ throw new \LogicException('The "enqueue.transports" parameter must be set.');
+ }
+
+ $names = $container->getParameter('enqueue.transports');
+ $defaultName = $container->getParameter('enqueue.default_transport');
+
+ foreach ($names as $name) {
+ $diUtils = DiUtils::create(TransportFactory::MODULE, $name);
+
+ $processorRegistryId = $diUtils->format('processor_registry');
+ if (false == $container->hasDefinition($processorRegistryId)) {
+ throw new \LogicException(sprintf('Service "%s" not found', $processorRegistryId));
+ }
+
+ $tag = 'enqueue.transport.processor';
+ $map = [];
+ foreach ($container->findTaggedServiceIds($tag) as $serviceId => $tagAttributes) {
+ foreach ($tagAttributes as $tagAttribute) {
+ $transport = $tagAttribute['transport'] ?? $defaultName;
+
+ if ($transport !== $name && 'all' !== $transport) {
+ continue;
+ }
+
+ $processor = $tagAttribute['processor'] ?? $serviceId;
+
+ $map[$processor] = new Reference($serviceId);
+ }
+ }
+
+ $registry = $container->getDefinition($processorRegistryId);
+ $registry->setArgument(0, ServiceLocatorTagPass::register($container, $map, $processorRegistryId));
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/DependencyInjection/TransportFactory.php b/pkg/enqueue/Symfony/DependencyInjection/TransportFactory.php
new file mode 100644
index 000000000..944b1a30d
--- /dev/null
+++ b/pkg/enqueue/Symfony/DependencyInjection/TransportFactory.php
@@ -0,0 +1,266 @@
+default = $default;
+ $this->diUtils = DiUtils::create(self::MODULE, $name);
+ }
+
+ public static function getConfiguration(string $name = 'transport'): NodeDefinition
+ {
+ $knownSchemes = array_keys(Resources::getKnownSchemes());
+ $availableSchemes = array_keys(Resources::getAvailableSchemes());
+
+ $builder = new ArrayNodeDefinition($name);
+ $builder
+ ->info('The transport option could accept a string DSN, an array with DSN key, or null. It accept extra options. To find out what option you can set, look at connection factory constructor docblock.')
+ ->beforeNormalization()
+ ->always(function ($v) {
+ if (empty($v)) {
+ return ['dsn' => 'null:'];
+ }
+
+ if (is_array($v)) {
+ if (isset($v['factory_class']) && isset($v['factory_service'])) {
+ throw new \LogicException('Both options factory_class and factory_service are set. Please choose one.');
+ }
+
+ if (isset($v['connection_factory_class']) && (isset($v['factory_class']) || isset($v['factory_service']))) {
+ throw new \LogicException('The option connection_factory_class must not be used with factory_class or factory_service at the same time. Please choose one.');
+ }
+
+ return $v;
+ }
+
+ if (is_string($v)) {
+ return ['dsn' => $v];
+ }
+
+ throw new \LogicException(sprintf('The value must be array, null or string. Got "%s"', gettype($v)));
+ })
+ ->end()
+ ->isRequired()
+ ->ignoreExtraKeys(false)
+ ->children()
+ ->scalarNode('dsn')
+ ->cannotBeEmpty()
+ ->isRequired()
+ ->info(sprintf(
+ 'The MQ broker DSN. These schemes are supported: "%s", to use these "%s" you have to install a package.',
+ implode('", "', $knownSchemes),
+ implode('", "', $availableSchemes)
+ ))
+ ->end()
+ ->scalarNode('connection_factory_class')
+ ->info(sprintf('The connection factory class should implement "%s" interface', ConnectionFactory::class))
+ ->end()
+ ->scalarNode('factory_service')
+ ->info(sprintf('The factory class should implement "%s" interface', ConnectionFactoryFactoryInterface::class))
+ ->end()
+ ->scalarNode('factory_class')
+ ->info(sprintf('The factory service should be a class that implements "%s" interface', ConnectionFactoryFactoryInterface::class))
+ ->end()
+ ->end()
+ ;
+
+ return $builder;
+ }
+
+ public static function getQueueConsumerConfiguration(string $name = 'consumption'): ArrayNodeDefinition
+ {
+ $builder = new ArrayNodeDefinition($name);
+
+ $builder
+ ->addDefaultsIfNotSet()->children()
+ ->integerNode('receive_timeout')
+ ->min(0)
+ ->defaultValue(10000)
+ ->info('the time in milliseconds queue consumer waits for a message (10000 ms by default)')
+ ->end()
+ ;
+
+ return $builder;
+ }
+
+ public function buildConnectionFactory(ContainerBuilder $container, array $config): void
+ {
+ $factoryId = $this->diUtils->format('connection_factory');
+
+ $factoryFactoryId = $this->diUtils->format('connection_factory_factory');
+ $container->register($factoryFactoryId, $config['factory_class'] ?? ConnectionFactoryFactory::class);
+
+ $factoryFactoryService = new Reference(
+ $config['factory_service'] ?? $factoryFactoryId
+ );
+
+ unset($config['factory_service'], $config['factory_class']);
+
+ $connectionFactoryClass = $config['connection_factory_class'] ?? null;
+ unset($config['connection_factory_class']);
+
+ if (isset($connectionFactoryClass)) {
+ $container->register($factoryId, $connectionFactoryClass)
+ ->addArgument($config)
+ ;
+ } else {
+ $container->register($factoryId, ConnectionFactory::class)
+ ->setFactory([$factoryFactoryService, 'create'])
+ ->addArgument($config)
+ ;
+ }
+
+ if ($this->default) {
+ $container->setAlias(ConnectionFactory::class, $factoryId);
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('connection_factory'), $factoryId);
+ }
+ }
+ }
+
+ public function buildContext(ContainerBuilder $container, array $config): void
+ {
+ $factoryId = $this->diUtils->format('connection_factory');
+ $this->assertServiceExists($container, $factoryId);
+
+ $contextId = $this->diUtils->format('context');
+
+ $container->register($contextId, Context::class)
+ ->setFactory([new Reference($factoryId), 'createContext'])
+ ;
+
+ $this->addServiceToLocator($container, 'context');
+
+ if ($this->default) {
+ $container->setAlias(Context::class, $contextId);
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('context'), $contextId);
+ }
+ }
+ }
+
+ public function buildQueueConsumer(ContainerBuilder $container, array $config): void
+ {
+ $contextId = $this->diUtils->format('context');
+ $this->assertServiceExists($container, $contextId);
+
+ $container->setParameter($this->diUtils->format('receive_timeout'), $config['receive_timeout'] ?? 10000);
+
+ $logExtensionId = $this->diUtils->format('log_extension');
+ $container->register($logExtensionId, LogExtension::class)
+ ->addTag('enqueue.transport.consumption_extension', ['transport' => $this->diUtils->getConfigName(), 'priority' => -100])
+ ;
+
+ $container->register($this->diUtils->format('consumption_extensions'), ChainExtension::class)
+ ->addArgument([])
+ ;
+
+ $container->register($this->diUtils->format('queue_consumer'), QueueConsumer::class)
+ ->addArgument(new Reference($contextId))
+ ->addArgument(new Reference($this->diUtils->format('consumption_extensions')))
+ ->addArgument([])
+ ->addArgument(new Reference('logger', ContainerInterface::NULL_ON_INVALID_REFERENCE))
+ ->addArgument($this->diUtils->parameter('receive_timeout'))
+ ;
+
+ $container->register($this->diUtils->format('processor_registry'), ContainerProcessorRegistry::class);
+
+ $this->addServiceToLocator($container, 'queue_consumer');
+ $this->addServiceToLocator($container, 'processor_registry');
+
+ if ($this->default) {
+ $container->setAlias(QueueConsumerInterface::class, $this->diUtils->format('queue_consumer'));
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('queue_consumer'), $this->diUtils->format('queue_consumer'));
+ }
+ }
+ }
+
+ public function buildRpcClient(ContainerBuilder $container, array $config): void
+ {
+ $contextId = $this->diUtils->format('context');
+ $this->assertServiceExists($container, $contextId);
+
+ $container->register($this->diUtils->format('rpc_factory'), RpcFactory::class)
+ ->addArgument(new Reference($contextId))
+ ;
+
+ $container->register($this->diUtils->format('rpc_client'), RpcClient::class)
+ ->addArgument(new Reference($contextId))
+ ->addArgument(new Reference($this->diUtils->format('rpc_factory')))
+ ;
+
+ if ($this->default) {
+ $container->setAlias(RpcClient::class, $this->diUtils->format('rpc_client'));
+
+ if (DiUtils::DEFAULT_CONFIG !== $this->diUtils->getConfigName()) {
+ $container->setAlias($this->diUtils->formatDefault('rpc_client'), $this->diUtils->format('rpc_client'));
+ }
+ }
+ }
+
+ private function assertServiceExists(ContainerBuilder $container, string $serviceId): void
+ {
+ if (false == $container->hasDefinition($serviceId)) {
+ throw new \InvalidArgumentException(sprintf('The service "%s" does not exist.', $serviceId));
+ }
+ }
+
+ private function addServiceToLocator(ContainerBuilder $container, string $serviceName): void
+ {
+ $locatorId = 'enqueue.locator';
+
+ if ($container->hasDefinition($locatorId)) {
+ $locator = $container->getDefinition($locatorId);
+
+ $map = $locator->getArgument(0);
+ $map[$this->diUtils->format($serviceName)] = $this->diUtils->reference($serviceName);
+
+ $locator->replaceArgument(0, $map);
+ }
+ }
+}
diff --git a/pkg/enqueue/Symfony/DiUtils.php b/pkg/enqueue/Symfony/DiUtils.php
new file mode 100644
index 000000000..be45287be
--- /dev/null
+++ b/pkg/enqueue/Symfony/DiUtils.php
@@ -0,0 +1,81 @@
+moduleName = $moduleName;
+ $this->configName = $configName;
+ }
+
+ public static function create(string $moduleName, string $configName): self
+ {
+ return new self($moduleName, $configName);
+ }
+
+ public function getModuleName(): string
+ {
+ return $this->moduleName;
+ }
+
+ public function getConfigName(): string
+ {
+ return $this->configName;
+ }
+
+ public function reference(string $serviceName, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): Reference
+ {
+ return new Reference($this->format($serviceName), $invalidBehavior);
+ }
+
+ public function referenceDefault(string $serviceName, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE): Reference
+ {
+ return new Reference($this->formatDefault($serviceName), $invalidBehavior);
+ }
+
+ public function parameter(string $serviceName): string
+ {
+ $fullName = $this->format($serviceName);
+
+ return "%$fullName%";
+ }
+
+ public function parameterDefault(string $serviceName): string
+ {
+ $fullName = $this->formatDefault($serviceName);
+
+ return "%$fullName%";
+ }
+
+ public function format(string $serviceName): string
+ {
+ return $this->doFormat($this->moduleName, $this->configName, $serviceName);
+ }
+
+ public function formatDefault(string $serviceName): string
+ {
+ return $this->doFormat($this->moduleName, self::DEFAULT_CONFIG, $serviceName);
+ }
+
+ private function doFormat(string $moduleName, string $configName, string $serviceName): string
+ {
+ return sprintf('enqueue.%s.%s.%s', $moduleName, $configName, $serviceName);
+ }
+}
diff --git a/pkg/enqueue/Symfony/MissingComponentFactory.php b/pkg/enqueue/Symfony/MissingComponentFactory.php
new file mode 100644
index 000000000..94fcb8339
--- /dev/null
+++ b/pkg/enqueue/Symfony/MissingComponentFactory.php
@@ -0,0 +1,42 @@
+info($message)
+ ->beforeNormalization()
+ ->always(function () {
+ return [];
+ })
+ ->end()
+ ->validate()
+ ->always(function () use ($message) {
+ throw new \InvalidArgumentException($message);
+ })
+ ->end()
+ ;
+
+ return $node;
+ }
+}
diff --git a/pkg/enqueue/Symfony/NullTransportFactory.php b/pkg/enqueue/Symfony/NullTransportFactory.php
deleted file mode 100644
index 3da667302..000000000
--- a/pkg/enqueue/Symfony/NullTransportFactory.php
+++ /dev/null
@@ -1,71 +0,0 @@
-name = $name;
- }
-
- /**
- * {@inheritdoc}
- */
- public function addConfiguration(ArrayNodeDefinition $builder)
- {
- }
-
- /**
- * {@inheritdoc}
- */
- public function createContext(ContainerBuilder $container, array $config)
- {
- $contextId = sprintf('enqueue.transport.%s.context', $this->getName());
- $context = new Definition(NullContext::class);
-
- $container->setDefinition($contextId, $context);
-
- return $contextId;
- }
-
- /**
- * {@inheritdoc}
- */
- public function createDriver(ContainerBuilder $container, array $config)
- {
- $driver = new Definition(NullDriver::class);
- $driver->setArguments([
- new Reference(sprintf('enqueue.transport.%s.context', $this->getName())),
- new Reference('enqueue.client.config'),
- ]);
-
- $driverId = sprintf('enqueue.client.%s.driver', $this->getName());
- $container->setDefinition($driverId, $driver);
-
- return $driverId;
- }
-
- /**
- * @return string
- */
- public function getName()
- {
- return $this->name;
- }
-}
diff --git a/pkg/enqueue/Symfony/TransportFactoryInterface.php b/pkg/enqueue/Symfony/TransportFactoryInterface.php
deleted file mode 100644
index 963fd01b7..000000000
--- a/pkg/enqueue/Symfony/TransportFactoryInterface.php
+++ /dev/null
@@ -1,35 +0,0 @@
-assertClassImplements(ProcessorRegistryInterface::class, ArrayProcessorRegistry::class);
}
- public function testCouldBeConstructedWithoutAnyArgument()
- {
- new ArrayProcessorRegistry();
- }
-
public function testShouldThrowExceptionIfProcessorIsNotSet()
{
$registry = new ArrayProcessorRegistry();
@@ -50,7 +47,7 @@ public function testShouldAllowGetProcessorAddedViaAddMethod()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
+ * @return MockObject|Processor
*/
protected function createProcessorMock()
{
diff --git a/pkg/enqueue/Tests/Client/ChainExtensionTest.php b/pkg/enqueue/Tests/Client/ChainExtensionTest.php
new file mode 100644
index 000000000..0f42bcf18
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ChainExtensionTest.php
@@ -0,0 +1,155 @@
+assertClassImplements(ExtensionInterface::class, ChainExtension::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(ChainExtension::class);
+ }
+
+ public function testThrowIfArrayContainsNotExtension()
+ {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Invalid extension given');
+
+ new ChainExtension([$this->createExtension(), new \stdClass()]);
+ }
+
+ public function testShouldProxyOnPreSendEventToAllInternalExtensions()
+ {
+ $preSend = new PreSend(
+ 'aCommandOrTopic',
+ new Message(),
+ $this->createMock(ProducerInterface::class),
+ $this->createMock(DriverInterface::class)
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPreSendEvent')
+ ->with($this->identicalTo($preSend))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPreSendEvent')
+ ->with($this->identicalTo($preSend))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPreSendEvent($preSend);
+ }
+
+ public function testShouldProxyOnPreSendCommandToAllInternalExtensions()
+ {
+ $preSend = new PreSend(
+ 'aCommandOrTopic',
+ new Message(),
+ $this->createMock(ProducerInterface::class),
+ $this->createMock(DriverInterface::class)
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPreSendCommand')
+ ->with($this->identicalTo($preSend))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPreSendCommand')
+ ->with($this->identicalTo($preSend))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPreSendCommand($preSend);
+ }
+
+ public function testShouldProxyOnDriverPreSendToAllInternalExtensions()
+ {
+ $driverPreSend = new DriverPreSend(
+ new Message(),
+ $this->createMock(ProducerInterface::class),
+ $this->createMock(DriverInterface::class)
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onDriverPreSend')
+ ->with($this->identicalTo($driverPreSend))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onDriverPreSend')
+ ->with($this->identicalTo($driverPreSend))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onDriverPreSend($driverPreSend);
+ }
+
+ public function testShouldProxyOnPostSentToAllInternalExtensions()
+ {
+ $postSend = new PostSend(
+ new Message(),
+ $this->createMock(ProducerInterface::class),
+ $this->createMock(DriverInterface::class),
+ $this->createMock(Destination::class),
+ $this->createMock(TransportMessage::class)
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPostSend')
+ ->with($this->identicalTo($postSend))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPostSend')
+ ->with($this->identicalTo($postSend))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPostSend($postSend);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|ExtensionInterface
+ */
+ protected function createExtension()
+ {
+ return $this->createMock(ExtensionInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ConfigTest.php b/pkg/enqueue/Tests/Client/ConfigTest.php
index 7d2c0a499..09b80e2c1 100644
--- a/pkg/enqueue/Tests/Client/ConfigTest.php
+++ b/pkg/enqueue/Tests/Client/ConfigTest.php
@@ -3,100 +3,250 @@
namespace Enqueue\Tests\Client;
use Enqueue\Client\Config;
+use PHPUnit\Framework\TestCase;
-class ConfigTest extends \PHPUnit_Framework_TestCase
+class ConfigTest extends TestCase
{
- public function testShouldReturnRouterProcessorNameSetInConstructor()
+ public function testShouldReturnPrefixSetInConstructor()
{
$config = new Config(
- 'aPrefix',
+ 'thePrefix',
+ 'theSeparator',
'aApp',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aRouterProcessorName', $config->getRouterProcessorName());
+ $this->assertEquals('thePrefix', $config->getPrefix());
}
- public function testShouldReturnRouterTopicNameSetInConstructor()
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testShouldTrimReturnPrefixSetInConstructor(string $empty)
{
$config = new Config(
- 'aPrefix',
+ $empty,
'aApp',
+ 'theSeparator',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aRouterTopicName', $config->getRouterTopicName());
+ $this->assertSame('', $config->getPrefix());
}
- public function testShouldReturnRouterQueueNameSetInConstructor()
+ public function testShouldReturnAppNameSetInConstructor()
+ {
+ $config = new Config(
+ 'aPrefix',
+ 'theSeparator',
+ 'theApp',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueueName',
+ 'aRouterProcessorName',
+ [],
+ []
+ );
+
+ $this->assertEquals('theApp', $config->getApp());
+ }
+
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testShouldTrimReturnAppNameSetInConstructor(string $empty)
{
$config = new Config(
'aPrefix',
+ 'theSeparator',
+ $empty,
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueueName',
+ 'aRouterProcessorName',
+ [],
+ []
+ );
+
+ $this->assertSame('', $config->getApp());
+ }
+
+ public function testShouldReturnRouterProcessorNameSetInConstructor()
+ {
+ $config = new Config(
+ 'aPrefix',
+ 'theSeparator',
'aApp',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aRouterQueueName', $config->getRouterQueueName());
+ $this->assertEquals('aRouterProcessorName', $config->getRouterProcessor());
}
- public function testShouldReturnDefaultQueueNameSetInConstructor()
+ public function testShouldReturnRouterTopicNameSetInConstructor()
{
$config = new Config(
'aPrefix',
+ 'theSeparator',
'aApp',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aDefaultQueueName', $config->getDefaultProcessorQueueName());
+ $this->assertEquals('aRouterTopicName', $config->getRouterTopic());
}
- public function testShouldCreateRouterTopicName()
+ public function testShouldReturnRouterQueueNameSetInConstructor()
{
$config = new Config(
'aPrefix',
+ 'theSeparator',
'aApp',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aprefix.aname', $config->createTransportRouterTopicName('aName'));
+ $this->assertEquals('aRouterQueueName', $config->getRouterQueue());
}
- public function testShouldCreateProcessorQueueName()
+ public function testShouldReturnDefaultQueueNameSetInConstructor()
{
$config = new Config(
'aPrefix',
+ 'theSeparator',
'aApp',
'aRouterTopicName',
'aRouterQueueName',
'aDefaultQueueName',
- 'aRouterProcessorName'
+ 'aRouterProcessorName',
+ [],
+ []
);
- $this->assertEquals('aprefix.aapp.aname', $config->createTransportQueueName('aName'));
+ $this->assertEquals('aDefaultQueueName', $config->getDefaultQueue());
}
public function testShouldCreateDefaultConfig()
{
$config = Config::create();
- $this->assertSame('default', $config->getDefaultProcessorQueueName());
- $this->assertSame('router', $config->getRouterProcessorName());
- $this->assertSame('default', $config->getRouterQueueName());
- $this->assertSame('router', $config->getRouterTopicName());
+ $this->assertSame('default', $config->getDefaultQueue());
+ $this->assertSame('router', $config->getRouterProcessor());
+ $this->assertSame('default', $config->getRouterQueue());
+ $this->assertSame('router', $config->getRouterTopic());
+ }
+
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testThrowIfRouterTopicNameIsEmpty(string $empty)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Router topic is empty.');
+ new Config(
+ '',
+ '',
+ '',
+ $empty,
+ 'aRouterQueueName',
+ 'aDefaultQueueName',
+ 'aRouterProcessorName',
+ [],
+ []
+ );
+ }
+
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testThrowIfRouterQueueNameIsEmpty(string $empty)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Router queue is empty.');
+ new Config(
+ '',
+ '',
+ '',
+ 'aRouterTopicName',
+ $empty,
+ 'aDefaultQueueName',
+ 'aRouterProcessorName',
+ [],
+ []
+ );
+ }
+
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testThrowIfDefaultQueueNameIsEmpty(string $empty)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Default processor queue name is empty.');
+ new Config(
+ '',
+ '',
+ '',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ $empty,
+ 'aRouterProcessorName',
+ [],
+ []
+ );
+ }
+
+ /**
+ * @dataProvider provideEmptyStrings
+ */
+ public function testThrowIfRouterProcessorNameIsEmpty(string $empty)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Router processor name is empty.');
+ new Config(
+ '',
+ '',
+ '',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueueName',
+ $empty,
+ [],
+ []
+ );
+ }
+
+ public function provideEmptyStrings()
+ {
+ yield [''];
+
+ yield [' '];
+
+ yield [' '];
+
+ yield ["\t"];
}
}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/DelayRedeliveredMessageExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/DelayRedeliveredMessageExtensionTest.php
index 3e2a99a41..a660126ad 100644
--- a/pkg/enqueue/Tests/Client/ConsumptionExtension/DelayRedeliveredMessageExtensionTest.php
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/DelayRedeliveredMessageExtensionTest.php
@@ -4,21 +4,25 @@
use Enqueue\Client\ConsumptionExtension\DelayRedeliveredMessageExtension;
use Enqueue\Client\DriverInterface;
+use Enqueue\Client\DriverSendResult;
use Enqueue\Client\Message;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\MessageReceived;
use Enqueue\Consumption\Result;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Transport\Null\NullMessage;
-use Enqueue\Transport\Null\NullQueue;
-use Psr\Log\LoggerInterface;
-
-class DelayRedeliveredMessageExtensionTest extends \PHPUnit_Framework_TestCase
+use Enqueue\Null\NullMessage;
+use Enqueue\Null\NullQueue;
+use Enqueue\Test\TestLogger;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context as InteropContext;
+use Interop\Queue\Destination;
+use Interop\Queue\Message as TransportMessage;
+use Interop\Queue\Processor;
+use Interop\Queue\Queue;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+class DelayRedeliveredMessageExtensionTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
- {
- new DelayRedeliveredMessageExtension($this->createDriverMock(), 12345);
- }
-
public function testShouldSendDelayedMessageAndRejectOriginalMessage()
{
$queue = new NullQueue('queue');
@@ -37,6 +41,7 @@ public function testShouldSendDelayedMessageAndRejectOriginalMessage()
->expects(self::once())
->method('sendToProcessor')
->with(self::isInstanceOf(Message::class))
+ ->willReturn($this->createDriverSendResult())
;
$driver
->expects(self::once())
@@ -45,37 +50,41 @@ public function testShouldSendDelayedMessageAndRejectOriginalMessage()
->willReturn($delayedMessage)
;
- $logger = $this->createLoggerMock();
- $logger
- ->expects(self::at(0))
- ->method('debug')
- ->with('[DelayRedeliveredMessageExtension] Send delayed message')
- ;
- $logger
- ->expects(self::at(1))
- ->method('debug')
- ->with(
- '[DelayRedeliveredMessageExtension] '.
- 'Reject redelivered original message by setting reject status to context.'
- )
- ;
+ $logger = new TestLogger();
- $context = new Context($this->createPsrContextMock());
- $context->setPsrQueue($queue);
- $context->setPsrMessage($originMessage);
- $context->setLogger($logger);
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub($queue),
+ $originMessage,
+ $this->createProcessorMock(),
+ 1,
+ $logger
+ );
- self::assertNull($context->getResult());
+ $this->assertNull($messageReceived->getResult());
$extension = new DelayRedeliveredMessageExtension($driver, 12345);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($messageReceived);
- self::assertEquals(Result::REJECT, $context->getResult());
+ $result = $messageReceived->getResult();
+ $this->assertInstanceOf(Result::class, $result);
+ $this->assertSame(Result::REJECT, $result->getStatus());
+ $this->assertSame('A new copy of the message was sent with a delay. The original message is rejected', $result->getReason());
- self::assertInstanceOf(Message::class, $delayedMessage);
- self::assertEquals([
+ $this->assertInstanceOf(Message::class, $delayedMessage);
+ $this->assertEquals([
'enqueue.redelivery_count' => 1,
], $delayedMessage->getProperties());
+
+ self::assertTrue(
+ $logger->hasDebugThatContains('[DelayRedeliveredMessageExtension] Send delayed message')
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[DelayRedeliveredMessageExtension] '.
+ 'Reject redelivered original message by setting reject status to context.'
+ )
+ );
}
public function testShouldDoNothingIfMessageIsNotRedelivered()
@@ -88,36 +97,90 @@ public function testShouldDoNothingIfMessageIsNotRedelivered()
->method('sendToProcessor')
;
- $context = new Context($this->createPsrContextMock());
- $context->setPsrMessage($message);
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
$extension = new DelayRedeliveredMessageExtension($driver, 12345);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($messageReceived);
+
+ $this->assertNull($messageReceived->getResult());
+ }
+
+ public function testShouldDoNothingIfMessageIsRedeliveredButResultWasAlreadySetOnContext()
+ {
+ $message = new NullMessage();
+ $message->setRedelivered(true);
- self::assertNull($context->getResult());
+ $driver = $this->createDriverMock();
+ $driver
+ ->expects(self::never())
+ ->method('sendToProcessor')
+ ;
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+ $messageReceived->setResult(Result::ack());
+
+ $extension = new DelayRedeliveredMessageExtension($driver, 12345);
+ $extension->onMessageReceived($messageReceived);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
+ * @return MockObject
*/
- private function createDriverMock()
+ private function createDriverMock(): DriverInterface
{
return $this->createMock(DriverInterface::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|PsrContext
+ * @return MockObject
+ */
+ private function createContextMock(): InteropContext
+ {
+ return $this->createMock(InteropContext::class);
+ }
+
+ /**
+ * @return MockObject
*/
- private function createPsrContextMock()
+ private function createProcessorMock(): Processor
{
- return $this->createMock(PsrContext::class);
+ return $this->createMock(Processor::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|LoggerInterface
+ * @return MockObject|Consumer
*/
- private function createLoggerMock()
+ private function createConsumerStub(?Queue $queue): Consumer
+ {
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue ?? new NullQueue('queue'))
+ ;
+
+ return $consumerMock;
+ }
+
+ private function createDriverSendResult(): DriverSendResult
{
- return $this->createMock(LoggerInterface::class);
+ return new DriverSendResult(
+ $this->createMock(Destination::class),
+ $this->createMock(TransportMessage::class)
+ );
}
}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/ExclusiveCommandExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/ExclusiveCommandExtensionTest.php
new file mode 100644
index 000000000..b1e47c898
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/ExclusiveCommandExtensionTest.php
@@ -0,0 +1,286 @@
+assertClassImplements(MessageReceivedExtensionInterface::class, ExclusiveCommandExtension::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(ExclusiveCommandExtension::class);
+ }
+
+ public function testShouldDoNothingIfMessageHasTopicPropertySetOnPreReceive()
+ {
+ $message = new NullMessage();
+ $message->setProperty(Config::TOPIC, 'aTopic');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('createQueue')
+ ;
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $extension = new ExclusiveCommandExtension($driver);
+
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([
+ Config::TOPIC => 'aTopic',
+ ], $message->getProperties());
+ }
+
+ public function testShouldDoNothingIfMessageHasCommandPropertySetOnPreReceive()
+ {
+ $message = new NullMessage();
+ $message->setProperty(Config::COMMAND, 'aCommand');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('createQueue')
+ ;
+
+ $extension = new ExclusiveCommandExtension($driver);
+
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([
+ Config::COMMAND => 'aCommand',
+ ], $message->getProperties());
+ }
+
+ public function testShouldDoNothingIfMessageHasProcessorPropertySetOnPreReceive()
+ {
+ $message = new NullMessage();
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('createQueue')
+ ;
+
+ $extension = new ExclusiveCommandExtension($driver);
+
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([
+ Config::PROCESSOR => 'aProcessor',
+ ], $message->getProperties());
+ }
+
+ public function testShouldDoNothingIfCurrentQueueHasNoExclusiveProcessor()
+ {
+ $message = new NullMessage();
+ $queue = new NullQueue('aBarQueueName');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub($queue),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $extension = new ExclusiveCommandExtension($this->createDriverStub(new RouteCollection([])));
+
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([], $message->getProperties());
+ }
+
+ public function testShouldSetCommandPropertiesIfCurrentQueueHasExclusiveCommandProcessor()
+ {
+ $message = new NullMessage();
+ $queue = new NullQueue('fooQueue');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub($queue),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'theFooProcessor', [
+ 'exclusive' => true,
+ 'queue' => 'fooQueue',
+ ]),
+ new Route('barCommand', Route::COMMAND, 'theFooProcessor', [
+ 'exclusive' => true,
+ 'queue' => 'barQueue',
+ ]),
+ ]);
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->any())
+ ->method('createRouteQueue')
+ ->with($this->isInstanceOf(Route::class))
+ ->willReturnCallback(function (Route $route) {
+ return new NullQueue($route->getQueue());
+ })
+ ;
+
+ $extension = new ExclusiveCommandExtension($driver);
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([
+ Config::PROCESSOR => 'theFooProcessor',
+ Config::COMMAND => 'fooCommand',
+ ], $message->getProperties());
+ }
+
+ public function testShouldDoNothingIfAnotherQueue()
+ {
+ $message = new NullMessage();
+ $queue = new NullQueue('barQueue');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub($queue),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'theFooProcessor', [
+ 'exclusive' => true,
+ 'queue' => 'fooQueue',
+ ]),
+ new Route('barCommand', Route::COMMAND, 'theFooProcessor', [
+ 'exclusive' => false,
+ 'queue' => 'barQueue',
+ ]),
+ ]);
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $queueName) {
+ return new NullQueue($queueName);
+ })
+ ;
+
+ $extension = new ExclusiveCommandExtension($driver);
+ $extension->onMessageReceived($messageReceived);
+
+ self::assertNull($messageReceived->getResult());
+
+ $this->assertEquals([], $message->getProperties());
+ }
+
+ /**
+ * @return MockObject|DriverInterface
+ */
+ private function createDriverStub(?RouteCollection $routeCollection = null): DriverInterface
+ {
+ $driver = $this->createMock(DriverInterface::class);
+ $driver
+ ->expects($this->any())
+ ->method('getRouteCollection')
+ ->willReturn($routeCollection ?? new RouteCollection([]))
+ ;
+
+ return $driver;
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createContextMock(): InteropContext
+ {
+ return $this->createMock(InteropContext::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createProcessorMock(): Processor
+ {
+ return $this->createMock(Processor::class);
+ }
+
+ /**
+ * @return MockObject|Consumer
+ */
+ private function createConsumerStub(?Queue $queue): Consumer
+ {
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue ?? new NullQueue('queue'))
+ ;
+
+ return $consumerMock;
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/FlushSpoolProducerExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/FlushSpoolProducerExtensionTest.php
new file mode 100644
index 000000000..6a782c524
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/FlushSpoolProducerExtensionTest.php
@@ -0,0 +1,83 @@
+assertClassImplements(PostMessageReceivedExtensionInterface::class, FlushSpoolProducerExtension::class);
+ }
+
+ public function testShouldImplementEndExtensionInterface()
+ {
+ $this->assertClassImplements(EndExtensionInterface::class, FlushSpoolProducerExtension::class);
+ }
+
+ public function testShouldFlushSpoolProducerOnEnd()
+ {
+ $producer = $this->createSpoolProducerMock();
+ $producer
+ ->expects(self::once())
+ ->method('flush')
+ ;
+
+ $end = new End($this->createInteropContextMock(), 1, 2, new NullLogger());
+
+ $extension = new FlushSpoolProducerExtension($producer);
+ $extension->onEnd($end);
+ }
+
+ public function testShouldFlushSpoolProducerOnPostReceived()
+ {
+ $producer = $this->createSpoolProducerMock();
+ $producer
+ ->expects(self::once())
+ ->method('flush')
+ ;
+
+ $context = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
+
+ $extension = new FlushSpoolProducerExtension($producer);
+ $extension->onPostMessageReceived($context);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createInteropContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ /**
+ * @return MockObject|SpoolProducer
+ */
+ private function createSpoolProducerMock()
+ {
+ return $this->createMock(SpoolProducer::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/LogExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/LogExtensionTest.php
new file mode 100644
index 000000000..db757676b
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/LogExtensionTest.php
@@ -0,0 +1,536 @@
+assertClassImplements(StartExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementEndExtensionInterface()
+ {
+ $this->assertClassImplements(EndExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementMessageReceivedExtensionInterface()
+ {
+ $this->assertClassImplements(MessageReceivedExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementPostMessageReceivedExtensionInterface()
+ {
+ $this->assertClassImplements(PostMessageReceivedExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldSubClassOfLogExtension()
+ {
+ $this->assertClassExtends(\Enqueue\Consumption\Extension\LogExtension::class, LogExtension::class);
+ }
+
+ public function testShouldLogStartOnStart()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Consumption has started')
+ ;
+
+ $context = new Start($this->createContextMock(), $logger, [], 1, 1);
+
+ $extension = new LogExtension();
+ $extension->onStart($context);
+ }
+
+ public function testShouldLogEndOnEnd()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Consumption has ended')
+ ;
+
+ $context = new End($this->createContextMock(), 1, 2, $logger);
+
+ $extension = new LogExtension();
+ $extension->onEnd($context);
+ }
+
+ public function testShouldLogMessageReceived()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Received from {queueName} {body}', [
+ 'queueName' => 'aQueue',
+ 'redelivered' => false,
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ ])
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new MessageReceived($this->createContextMock(), $consumerMock, $message, $this->createProcessorMock(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithStringResult()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'aResult',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, 'aResult', 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogRejectedMessageAsError()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::ERROR,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'reject',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Processor::REJECT, 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithReasonResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result} {reason}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => 'aReason',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack('aReason'), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedCommandMessageWithStringResult()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {command} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::COMMAND => 'aCommand']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'aResult',
+ 'reason' => '',
+ 'command' => 'aCommand',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty(Config::COMMAND, 'aCommand');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, 'aResult', 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogRejectedCommandMessageAsError()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::ERROR,
+ '[client] Processed {command} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::COMMAND => 'aCommand']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'reject',
+ 'reason' => '',
+ 'command' => 'aCommand',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setProperty(Config::COMMAND, 'aCommand');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Processor::REJECT, 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedCommandMessageWithResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {command} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::COMMAND => 'aCommand']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => '',
+ 'command' => 'aCommand',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setProperty(Config::COMMAND, 'aCommand');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedCommandMessageWithReasonResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {command} {body} {result} {reason}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::COMMAND => 'aCommand']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => 'aReason',
+ 'command' => 'aCommand',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setProperty(Config::COMMAND, 'aCommand');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack('aReason'), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedTopicProcessorMessageWithStringResult()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {topic} -> {processor} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::TOPIC => 'aTopic', Config::PROCESSOR => 'aProcessor']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'aResult',
+ 'reason' => '',
+ 'topic' => 'aTopic',
+ 'processor' => 'aProcessor',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty(Config::TOPIC, 'aTopic');
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, 'aResult', 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogRejectedTopicProcessorMessageAsError()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::ERROR,
+ '[client] Processed {topic} -> {processor} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::TOPIC => 'aTopic', Config::PROCESSOR => 'aProcessor']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'reject',
+ 'reason' => '',
+ 'topic' => 'aTopic',
+ 'processor' => 'aProcessor',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty(Config::TOPIC, 'aTopic');
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Processor::REJECT, 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedTopicProcessorMessageWithResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {topic} -> {processor} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::TOPIC => 'aTopic', Config::PROCESSOR => 'aProcessor']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => '',
+ 'topic' => 'aTopic',
+ 'processor' => 'aProcessor',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty(Config::TOPIC, 'aTopic');
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogProcessedTopicProcessorMessageWithReasonResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ '[client] Processed {topic} -> {processor} {body} {result} {reason}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal', Config::TOPIC => 'aTopic', Config::PROCESSOR => 'aProcessor']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => 'aReason',
+ 'topic' => 'aTopic',
+ 'processor' => 'aProcessor',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty(Config::TOPIC, 'aTopic');
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack('aReason'), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createConsumerStub(Queue $queue): Consumer
+ {
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue)
+ ;
+
+ return $consumerMock;
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createProcessorMock(): Processor
+ {
+ return $this->createMock(Processor::class);
+ }
+
+ /**
+ * @return MockObject|LoggerInterface
+ */
+ private function createLogger()
+ {
+ return $this->createMock(LoggerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/SetRouterPropertiesExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/SetRouterPropertiesExtensionTest.php
index df4de33b3..d521aefca 100644
--- a/pkg/enqueue/Tests/Client/ConsumptionExtension/SetRouterPropertiesExtensionTest.php
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/SetRouterPropertiesExtensionTest.php
@@ -5,48 +5,102 @@
use Enqueue\Client\Config;
use Enqueue\Client\ConsumptionExtension\SetRouterPropertiesExtension;
use Enqueue\Client\DriverInterface;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\ExtensionInterface;
-use Enqueue\Psr\Context as PsrContext;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\MessageReceivedExtensionInterface;
+use Enqueue\Null\NullMessage;
+use Enqueue\Null\NullQueue;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullMessage;
-
-class SetRouterPropertiesExtensionTest extends \PHPUnit_Framework_TestCase
+use Interop\Queue\Consumer;
+use Interop\Queue\Context as InteropContext;
+use Interop\Queue\Processor;
+use Interop\Queue\Queue;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+class SetRouterPropertiesExtensionTest extends TestCase
{
use ClassExtensionTrait;
- public function testShouldImplementExtensionInterface()
+ public function testShouldImplementMessageReceivedExtensionInterface()
{
- $this->assertClassImplements(ExtensionInterface::class, SetRouterPropertiesExtension::class);
+ $this->assertClassImplements(MessageReceivedExtensionInterface::class, SetRouterPropertiesExtension::class);
}
- public function testCouldBeConstructedWithRequiredArguments()
+ public function testShouldSetRouterProcessorPropertyIfNotSetAndOnRouterQueue()
{
- new SetRouterPropertiesExtension($this->createDriverMock());
+ $config = Config::create('test', '.', '', '', 'router-queue', '', 'router-processor-name');
+ $queue = new NullQueue('test.router-queue');
+
+ $driver = $this->createDriverMock();
+ $driver
+ ->expects($this->once())
+ ->method('getConfig')
+ ->willReturn($config)
+ ;
+
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+
+ $message = new NullMessage();
+ $message->setProperty(Config::TOPIC, 'aTopic');
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(new NullQueue('test.router-queue')),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $extension = new SetRouterPropertiesExtension($driver);
+ $extension->onMessageReceived($messageReceived);
+
+ $this->assertEquals([
+ Config::PROCESSOR => 'router-processor-name',
+ Config::TOPIC => 'aTopic',
+ ], $message->getProperties());
}
- public function testShouldSetRouterProcessorPropertyIfNotSet()
+ public function testShouldNotSetRouterProcessorPropertyIfNotSetAndNotOnRouterQueue()
{
- $config = new Config('', '', '', 'router-queue', '', 'router-processor-name');
+ $config = Config::create('test', '', '', 'router-queue', '', 'router-processor-name');
+ $queue = new NullQueue('test.router-queue');
$driver = $this->createDriverMock();
$driver
- ->expects(self::exactly(2))
+ ->expects($this->once())
->method('getConfig')
->willReturn($config)
;
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+
$message = new NullMessage();
+ $message->setProperty(Config::TOPIC, 'aTopic');
- $context = new Context($this->createPsrContextMock());
- $context->setPsrMessage($message);
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(new NullQueue('test.another-queue')),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
$extension = new SetRouterPropertiesExtension($driver);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($messageReceived);
$this->assertEquals([
- 'enqueue.processor_name' => 'router-processor-name',
- 'enqueue.processor_queue_name' => 'router-queue',
+ Config::TOPIC => 'aTopic',
], $message->getProperties());
}
@@ -54,37 +108,91 @@ public function testShouldNotSetAnyPropertyIfProcessorNamePropertyAlreadySet()
{
$driver = $this->createDriverMock();
$driver
- ->expects(self::never())
+ ->expects($this->never())
->method('getConfig')
;
$message = new NullMessage();
- $message->setProperty(Config::PARAMETER_PROCESSOR_NAME, 'non-router-processor');
+ $message->setProperty(Config::PROCESSOR, 'non-router-processor');
- $context = new Context($this->createPsrContextMock());
- $context->setPsrMessage($message);
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
$extension = new SetRouterPropertiesExtension($driver);
- $extension->onPreReceived($context);
+ $extension->onMessageReceived($messageReceived);
$this->assertEquals([
- 'enqueue.processor_name' => 'non-router-processor',
+ 'enqueue.processor' => 'non-router-processor',
], $message->getProperties());
}
+ public function testShouldSkipMessagesWithoutTopicPropertySet()
+ {
+ $driver = $this->createDriverMock();
+ $driver
+ ->expects($this->never())
+ ->method('getConfig')
+ ;
+
+ $message = new NullMessage();
+
+ $messageReceived = new MessageReceived(
+ $this->createContextMock(),
+ $this->createConsumerStub(null),
+ $message,
+ $this->createProcessorMock(),
+ 1,
+ new NullLogger()
+ );
+
+ $extension = new SetRouterPropertiesExtension($driver);
+ $extension->onMessageReceived($messageReceived);
+
+ $this->assertEquals([], $message->getProperties());
+ }
+
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|PsrContext
+ * @return MockObject|InteropContext
*/
- protected function createPsrContextMock()
+ protected function createContextMock(): InteropContext
{
- return $this->createMock(PsrContext::class);
+ return $this->createMock(InteropContext::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
+ * @return MockObject|DriverInterface
*/
- protected function createDriverMock()
+ protected function createDriverMock(): DriverInterface
{
return $this->createMock(DriverInterface::class);
}
+
+ /**
+ * @return MockObject
+ */
+ private function createProcessorMock(): Processor
+ {
+ return $this->createMock(Processor::class);
+ }
+
+ /**
+ * @return MockObject|Consumer
+ */
+ private function createConsumerStub(?Queue $queue): Consumer
+ {
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue ?? new NullQueue('queue'))
+ ;
+
+ return $consumerMock;
+ }
}
diff --git a/pkg/enqueue/Tests/Client/ConsumptionExtension/SetupBrokerExtensionTest.php b/pkg/enqueue/Tests/Client/ConsumptionExtension/SetupBrokerExtensionTest.php
index 8cd460258..fbd367975 100644
--- a/pkg/enqueue/Tests/Client/ConsumptionExtension/SetupBrokerExtensionTest.php
+++ b/pkg/enqueue/Tests/Client/ConsumptionExtension/SetupBrokerExtensionTest.php
@@ -4,29 +4,26 @@
use Enqueue\Client\ConsumptionExtension\SetupBrokerExtension;
use Enqueue\Client\DriverInterface;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\ExtensionInterface;
-use Enqueue\Psr\Context as PsrContext;
+use Enqueue\Consumption\Context\Start;
+use Enqueue\Consumption\StartExtensionInterface;
use Enqueue\Test\ClassExtensionTrait;
+use Interop\Queue\Context as InteropContext;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
-class SetupBrokerExtensionTest extends \PHPUnit_Framework_TestCase
+class SetupBrokerExtensionTest extends TestCase
{
use ClassExtensionTrait;
- public function testShouldImplementExtensionInterface()
+ public function testShouldImplementStartExtensionInterface()
{
- $this->assertClassImplements(ExtensionInterface::class, SetupBrokerExtension::class);
- }
-
- public function testCouldBeConstructedWithRequiredArguments()
- {
- new SetupBrokerExtension($this->createDriverMock());
+ $this->assertClassImplements(StartExtensionInterface::class, SetupBrokerExtension::class);
}
public function testShouldSetupBroker()
{
- $logger = new NullLogger('');
+ $logger = new NullLogger();
$driver = $this->createDriverMock();
$driver
@@ -35,8 +32,7 @@ public function testShouldSetupBroker()
->with($this->identicalTo($logger))
;
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($logger);
+ $context = new Start($this->createMock(InteropContext::class), $logger, [], 0, 0);
$extension = new SetupBrokerExtension($driver);
$extension->onStart($context);
@@ -44,7 +40,7 @@ public function testShouldSetupBroker()
public function testShouldSetupBrokerOnlyOnce()
{
- $logger = new NullLogger('');
+ $logger = new NullLogger();
$driver = $this->createDriverMock();
$driver
@@ -53,8 +49,7 @@ public function testShouldSetupBrokerOnlyOnce()
->with($this->identicalTo($logger))
;
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($logger);
+ $context = new Start($this->createMock(InteropContext::class), $logger, [], 0, 0);
$extension = new SetupBrokerExtension($driver);
$extension->onStart($context);
@@ -62,7 +57,7 @@ public function testShouldSetupBrokerOnlyOnce()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
+ * @return MockObject|DriverInterface
*/
private function createDriverMock()
{
diff --git a/pkg/enqueue/Tests/Client/DelegateProcessorTest.php b/pkg/enqueue/Tests/Client/DelegateProcessorTest.php
index 6a6f466a6..9743cf4f3 100644
--- a/pkg/enqueue/Tests/Client/DelegateProcessorTest.php
+++ b/pkg/enqueue/Tests/Client/DelegateProcessorTest.php
@@ -4,35 +4,30 @@
use Enqueue\Client\Config;
use Enqueue\Client\DelegateProcessor;
-use Enqueue\Client\ProcessorRegistryInterface;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Processor;
-use Enqueue\Transport\Null\NullMessage;
+use Enqueue\Null\NullMessage;
+use Enqueue\ProcessorRegistryInterface;
+use Interop\Queue\Context;
+use Interop\Queue\Processor;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
-class DelegateProcessorTest extends \PHPUnit_Framework_TestCase
+class DelegateProcessorTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
- {
- new DelegateProcessor($this->createProcessorRegistryMock());
- }
-
public function testShouldThrowExceptionIfProcessorNameIsNotSet()
{
- $this->setExpectedException(
- \LogicException::class,
- 'Got message without required parameter: "enqueue.processor_name"'
- );
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Got message without required parameter: "enqueue.processor"');
$processor = new DelegateProcessor($this->createProcessorRegistryMock());
- $processor->process(new NullMessage(), $this->createPsrContextMock());
+ $processor->process(new NullMessage(), $this->createContextMock());
}
public function testShouldProcessMessage()
{
- $session = $this->createPsrContextMock();
+ $session = $this->createContextMock();
$message = new NullMessage();
$message->setProperties([
- Config::PARAMETER_PROCESSOR_NAME => 'processor-name',
+ Config::PROCESSOR => 'processor-name',
]);
$processor = $this->createProcessorMock();
@@ -40,7 +35,7 @@ public function testShouldProcessMessage()
->expects($this->once())
->method('process')
->with($this->identicalTo($message), $this->identicalTo($session))
- ->will($this->returnValue('return-value'))
+ ->willReturn('return-value')
;
$processorRegistry = $this->createProcessorRegistryMock();
@@ -48,7 +43,7 @@ public function testShouldProcessMessage()
->expects($this->once())
->method('get')
->with('processor-name')
- ->will($this->returnValue($processor))
+ ->willReturn($processor)
;
$processor = new DelegateProcessor($processorRegistry);
@@ -58,7 +53,7 @@ public function testShouldProcessMessage()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|ProcessorRegistryInterface
+ * @return MockObject|ProcessorRegistryInterface
*/
protected function createProcessorRegistryMock()
{
@@ -66,15 +61,15 @@ protected function createProcessorRegistryMock()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Context
+ * @return MockObject|Context
*/
- protected function createPsrContextMock()
+ protected function createContextMock()
{
return $this->createMock(Context::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
+ * @return MockObject|Processor
*/
protected function createProcessorMock()
{
diff --git a/pkg/enqueue/Tests/Client/Driver/AmqpDriverTest.php b/pkg/enqueue/Tests/Client/Driver/AmqpDriverTest.php
new file mode 100644
index 000000000..2cfb170b9
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/AmqpDriverTest.php
@@ -0,0 +1,360 @@
+assertClassImplements(DriverInterface::class, AmqpDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, AmqpDriver::class);
+ }
+
+ public function testThrowIfPriorityIsNotSupportedOnCreateTransportMessage()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setPriority('invalidPriority');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMessage())
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Cant convert client priority "invalidPriority" to transport one. Could be one of "enqueue.message_queue.client.very_low_message_priority", "enqueue.message_queue.client.low_message_priority", "enqueue.message_queue.client.normal_message_priority');
+ $driver->createTransportMessage($clientMessage);
+ }
+
+ public function testShouldSetExpirationHeaderFromClientMessageExpireInMillisecondsOnCreateTransportMessage()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setExpire(333);
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMessage())
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var AmqpMessage $transportMessage */
+ $transportMessage = $driver->createTransportMessage($clientMessage);
+
+ $this->assertSame(333000, $transportMessage->getExpiration());
+ $this->assertSame('333000', $transportMessage->getHeader('expiration'));
+ }
+
+ public function testShouldSetPersistedDeliveryModeOnCreateTransportMessage()
+ {
+ $clientMessage = new Message();
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMessage())
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var AmqpMessage $transportMessage */
+ $transportMessage = $driver->createTransportMessage($clientMessage);
+
+ $this->assertSame(AmqpMessage::DELIVERY_MODE_PERSISTENT, $transportMessage->getDeliveryMode());
+ }
+
+ public function testShouldCreateDurableQueue()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($this->createQueue('aName'))
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var AmqpQueue $queue */
+ $queue = $driver->createQueue('aName');
+
+ $this->assertSame(AmqpQueue::FLAG_DURABLE, $queue->getFlags());
+ }
+
+ public function testShouldResetPriorityAndExpirationAndNeverCallProducerDeliveryDelayOnSendMessageToRouter()
+ {
+ $topic = $this->createTopic('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($topic), $this->identicalTo($transportMessage))
+ ;
+ $producer
+ ->expects($this->never())
+ ->method('setDeliveryDelay')
+ ;
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createTopic')
+ ->willReturn($topic)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setExpire(123);
+ $message->setPriority(MessagePriority::HIGH);
+
+ $driver->sendToRouter($message);
+
+ $this->assertNull($transportMessage->getExpiration());
+ $this->assertNull($transportMessage->getPriority());
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routerTopic = $this->createTopic('');
+ $routerQueue = $this->createQueue('');
+ $processorWithDefaultQueue = $this->createQueue('default');
+ $processorWithCustomQueue = $this->createQueue('custom');
+ $context = $this->createContextMock();
+ // setup router
+ $context
+ ->expects($this->at(0))
+ ->method('createTopic')
+ ->willReturn($routerTopic)
+ ;
+ $context
+ ->expects($this->at(1))
+ ->method('declareTopic')
+ ->with($this->identicalTo($routerTopic))
+ ;
+
+ $context
+ ->expects($this->at(2))
+ ->method('createQueue')
+ ->willReturn($routerQueue)
+ ;
+ $context
+ ->expects($this->at(3))
+ ->method('declareQueue')
+ ->with($this->identicalTo($routerQueue))
+ ;
+
+ $context
+ ->expects($this->at(4))
+ ->method('bind')
+ ->with($this->isInstanceOf(AmqpBind::class))
+ ;
+
+ // setup processor with default queue
+ $context
+ ->expects($this->at(5))
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($processorWithDefaultQueue)
+ ;
+ $context
+ ->expects($this->at(6))
+ ->method('declareQueue')
+ ->with($this->identicalTo($processorWithDefaultQueue))
+ ;
+
+ $context
+ ->expects($this->at(7))
+ ->method('createQueue')
+ ->with($this->getCustomQueueTransportName())
+ ->willReturn($processorWithCustomQueue)
+ ;
+ $context
+ ->expects($this->at(8))
+ ->method('declareQueue')
+ ->with($this->identicalTo($processorWithCustomQueue))
+ ;
+
+ $driver = new AmqpDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('aTopic', Route::TOPIC, 'aProcessor'),
+ new Route('aCommand', Route::COMMAND, 'aProcessor', ['queue' => 'custom']),
+ ])
+ );
+ $driver->setupBroker();
+ }
+
+ public function testShouldNotDeclareSameQueues()
+ {
+ $context = $this->createContextMock();
+
+ // setup processor with default queue
+ $context
+ ->expects($this->any())
+ ->method('createTopic')
+ ->willReturn($this->createTopic(''))
+ ;
+ $context
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturn($this->createQueue('custom'))
+ ;
+ $context
+ ->expects($this->exactly(2))
+ ->method('declareQueue')
+ ;
+
+ $driver = new AmqpDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('aTopic', Route::TOPIC, 'aProcessor', ['queue' => 'custom']),
+ new Route('aCommand', Route::COMMAND, 'aProcessor', ['queue' => 'custom']),
+ ])
+ );
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new AmqpDriver(...$args);
+ }
+
+ /**
+ * @return AmqpContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(AmqpContext::class);
+ }
+
+ /**
+ * @return AmqpProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(AmqpProducer::class);
+ }
+
+ /**
+ * @return AmqpQueue
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new AmqpQueue($name);
+ }
+
+ protected function createTopic(string $name): AmqpTopic
+ {
+ return new AmqpTopic($name);
+ }
+
+ /**
+ * @return AmqpMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new AmqpMessage();
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return 'aprefix.router';
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ Assert::assertArraySubset([
+ 'hkey' => 'hval',
+ 'delivery_mode' => AmqpMessage::DELIVERY_MODE_PERSISTENT,
+ 'content_type' => 'ContentType',
+ 'expiration' => '123000',
+ 'priority' => 3,
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply_to' => 'theReplyTo',
+ 'correlation_id' => 'theCorrelationId',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/DbalDriverTest.php b/pkg/enqueue/Tests/Client/Driver/DbalDriverTest.php
new file mode 100644
index 000000000..554a399f9
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/DbalDriverTest.php
@@ -0,0 +1,101 @@
+assertClassImplements(DriverInterface::class, DbalDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, DbalDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('getTableName')
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createDataBaseTable')
+ ;
+
+ $driver = new DbalDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new DbalDriver(...$args);
+ }
+
+ /**
+ * @return DbalContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(DbalContext::class);
+ }
+
+ /**
+ * @return DbalProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(DbalProducer::class);
+ }
+
+ /**
+ * @return DbalDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new DbalDestination($name);
+ }
+
+ /**
+ * @return DbalDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new DbalDestination($name);
+ }
+
+ /**
+ * @return DbalMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new DbalMessage();
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/FsDriverTest.php b/pkg/enqueue/Tests/Client/Driver/FsDriverTest.php
new file mode 100644
index 000000000..f1cd02f9b
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/FsDriverTest.php
@@ -0,0 +1,125 @@
+assertClassImplements(DriverInterface::class, FsDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, FsDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routerQueue = new FsDestination(TempFile::generate());
+
+ $processorQueue = new FsDestination(TempFile::generate());
+
+ $context = $this->createContextMock();
+ // setup router
+ $context
+ ->expects($this->at(0))
+ ->method('createQueue')
+ ->willReturn($routerQueue)
+ ;
+ $context
+ ->expects($this->at(1))
+ ->method('declareDestination')
+ ->with($this->identicalTo($routerQueue))
+ ;
+ // setup processor queue
+ $context
+ ->expects($this->at(2))
+ ->method('createQueue')
+ ->willReturn($processorQueue)
+ ;
+ $context
+ ->expects($this->at(3))
+ ->method('declareDestination')
+ ->with($this->identicalTo($processorQueue))
+ ;
+
+ $routeCollection = new RouteCollection([
+ new Route('aTopic', Route::TOPIC, 'aProcessor'),
+ ]);
+
+ $driver = new FsDriver(
+ $context,
+ $this->createDummyConfig(),
+ $routeCollection
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new FsDriver(...$args);
+ }
+
+ /**
+ * @return FsContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(FsContext::class);
+ }
+
+ /**
+ * @return FsProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(FsProducer::class);
+ }
+
+ /**
+ * @return FsDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new FsDestination(new \SplFileInfo($name));
+ }
+
+ /**
+ * @return FsDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new FsDestination(new \SplFileInfo($name));
+ }
+
+ /**
+ * @return FsMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new FsMessage();
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/GenericDriverTest.php b/pkg/enqueue/Tests/Client/Driver/GenericDriverTest.php
new file mode 100644
index 000000000..78f7f6e83
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/GenericDriverTest.php
@@ -0,0 +1,83 @@
+assertClassImplements(DriverInterface::class, GenericDriver::class);
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new GenericDriver(...$args);
+ }
+
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(InteropProducer::class);
+ }
+
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new NullQueue($name);
+ }
+
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new NullTopic($name);
+ }
+
+ protected function createMessage(): InteropMessage
+ {
+ return new NullMessage();
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ Assert::assertArraySubset([
+ 'hkey' => 'hval',
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply_to' => 'theReplyTo',
+ 'correlation_id' => 'theCorrelationId',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/GenericDriverTestsTrait.php b/pkg/enqueue/Tests/Client/Driver/GenericDriverTestsTrait.php
new file mode 100644
index 000000000..d5ad498a9
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/GenericDriverTestsTrait.php
@@ -0,0 +1,1249 @@
+createDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $this->assertInstanceOf(DriverInterface::class, $driver);
+ }
+
+ public function testShouldReturnContextSetInConstructor()
+ {
+ $context = $this->createContextMock();
+
+ $driver = $this->createDriver($context, $this->createDummyConfig(), new RouteCollection([]));
+
+ $this->assertSame($context, $driver->getContext());
+ }
+
+ public function testShouldReturnConfigObjectSetInConstructor()
+ {
+ $config = $this->createDummyConfig();
+
+ $driver = $this->createDriver($this->createContextMock(), $config, new RouteCollection([]));
+
+ $this->assertSame($config, $driver->getConfig());
+ }
+
+ public function testShouldReturnRouteCollectionSetInConstructor()
+ {
+ $routeCollection = new RouteCollection([]);
+
+ /** @var DriverInterface $driver */
+ $driver = $this->createDriver($this->createContextMock(), $this->createDummyConfig(), $routeCollection);
+
+ $this->assertSame($routeCollection, $driver->getRouteCollection());
+ }
+
+ public function testShouldCreateAndReturnQueueInstanceWithPrefixAndAppName()
+ {
+ $expectedQueue = $this->createQueue('aName');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getPrefixAppFooQueueTransportName())
+ ->willReturn($expectedQueue)
+ ;
+
+ $config = new Config(
+ 'aPrefix',
+ '.',
+ 'anAppName',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueue',
+ 'aRouterProcessor',
+ [],
+ []
+ );
+
+ $driver = $this->createDriver($context, $config, new RouteCollection([]));
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $this->assertSame($expectedQueue, $queue);
+ }
+
+ public function testShouldCreateAndReturnQueueInstanceWithPrefixWithoutAppName()
+ {
+ $expectedQueue = $this->createQueue('aName');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getPrefixFooQueueTransportName())
+ ->willReturn($expectedQueue)
+ ;
+
+ $config = new Config(
+ 'aPrefix',
+ '.',
+ '',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueue',
+ 'aRouterProcessor',
+ [],
+ []
+ );
+
+ $driver = $this->createDriver($context, $config, new RouteCollection([]));
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $this->assertSame($expectedQueue, $queue);
+ }
+
+ public function testShouldCreateAndReturnQueueInstanceWithAppNameAndWithoutPrefix()
+ {
+ $expectedQueue = $this->createQueue('aName');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getAppFooQueueTransportName())
+ ->willReturn($expectedQueue)
+ ;
+
+ $config = new Config(
+ '',
+ '.',
+ 'anAppName',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueue',
+ 'aRouterProcessor',
+ [],
+ []
+ );
+
+ $driver = $this->createDriver($context, $config, new RouteCollection([]));
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $this->assertSame($expectedQueue, $queue);
+ }
+
+ public function testShouldCreateAndReturnQueueInstanceWithoutPrefixAndAppName()
+ {
+ $expectedQueue = $this->createQueue('aName');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('afooqueue')
+ ->willReturn($expectedQueue)
+ ;
+
+ $config = new Config(
+ '',
+ '.',
+ '',
+ 'aRouterTopicName',
+ 'aRouterQueueName',
+ 'aDefaultQueue',
+ 'aRouterProcessor',
+ [],
+ []
+ );
+
+ $driver = $this->createDriver($context, $config, new RouteCollection([]));
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $this->assertSame($expectedQueue, $queue);
+ }
+
+ public function testShouldCreateAndReturnQueueInstance()
+ {
+ $expectedQueue = $this->createQueue('aName');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getPrefixFooQueueTransportName())
+ ->willReturn($expectedQueue)
+ ;
+
+ $driver = $this->createDriver($context, $this->createDummyConfig(), new RouteCollection([]));
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $this->assertSame($expectedQueue, $queue);
+ }
+
+ public function testShouldCreateClientMessageFromTransportOne()
+ {
+ $transportMessage = $this->createMessage();
+ $transportMessage->setBody('body');
+ $transportMessage->setHeaders(['hkey' => 'hval']);
+ $transportMessage->setProperty('pkey', 'pval');
+ $transportMessage->setProperty(Config::CONTENT_TYPE, 'theContentType');
+ $transportMessage->setProperty(Config::EXPIRE, '22');
+ $transportMessage->setProperty(Config::PRIORITY, MessagePriority::HIGH);
+ $transportMessage->setProperty('enqueue.delay', '44');
+ $transportMessage->setMessageId('theMessageId');
+ $transportMessage->setTimestamp(1000);
+ $transportMessage->setReplyTo('theReplyTo');
+ $transportMessage->setCorrelationId('theCorrelationId');
+
+ $driver = $this->createDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $clientMessage = $driver->createClientMessage($transportMessage);
+
+ $this->assertClientMessage($clientMessage);
+ }
+
+ public function testShouldCreateTransportMessageFromClientOne()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setBody('body');
+ $clientMessage->setHeaders(['hkey' => 'hval']);
+ $clientMessage->setProperties(['pkey' => 'pval']);
+ $clientMessage->setContentType('ContentType');
+ $clientMessage->setExpire(123);
+ $clientMessage->setDelay(345);
+ $clientMessage->setPriority(MessagePriority::HIGH);
+ $clientMessage->setMessageId('theMessageId');
+ $clientMessage->setTimestamp(1000);
+ $clientMessage->setReplyTo('theReplyTo');
+ $clientMessage->setCorrelationId('theCorrelationId');
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMessage())
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $transportMessage = $driver->createTransportMessage($clientMessage);
+
+ $this->assertTransportMessage($transportMessage);
+ }
+
+ public function testShouldSendMessageToRouter()
+ {
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->willReturnCallback(function (Destination $topic, InteropMessage $message) use ($transportMessage) {
+ $this->assertSame(
+ $this->getRouterTransportName(),
+ $topic instanceof InteropTopic ? $topic->getTopicName() : $topic->getQueueName());
+ $this->assertSame($transportMessage, $message);
+ })
+ ;
+ $context = $this->createContextStub();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+
+ $driver->sendToRouter($message);
+ }
+
+ public function testShouldNotInitDeliveryDelayOnSendMessageToRouter()
+ {
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $producer
+ ->expects($this->never())
+ ->method('setDeliveryDelay')
+ ;
+
+ $context = $this->createContextStub();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setDelay(456);
+ $message->setProperty(Config::TOPIC, 'topic');
+
+ $driver->sendToRouter($message);
+ }
+
+ public function testShouldNotInitTimeToLiveOnSendMessageToRouter()
+ {
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $producer
+ ->expects($this->never())
+ ->method('setTimeToLive')
+ ;
+
+ $context = $this->createContextStub();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setExpire(456);
+ $message->setProperty(Config::TOPIC, 'topic');
+
+ $driver->sendToRouter($message);
+ }
+
+ public function testShouldNotInitPriorityOnSendMessageToRouter()
+ {
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $producer
+ ->expects($this->never())
+ ->method('setPriority')
+ ;
+
+ $context = $this->createContextStub();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setPriority(MessagePriority::HIGH);
+ $message->setProperty(Config::TOPIC, 'topic');
+
+ $driver->sendToRouter($message);
+ }
+
+ public function testThrowIfTopicIsNotSetOnSendToRouter()
+ {
+ $driver = $this->createDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Topic name parameter is required but is not set');
+
+ $driver->sendToRouter(new Message());
+ }
+
+ public function testThrowIfCommandSetOnSendToRouter()
+ {
+ $driver = $this->createDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'aCommand');
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Command must not be send to router but go directly to its processor.');
+
+ $driver->sendToRouter($message);
+ }
+
+ public function testShouldSendMessageToRouterProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $config = $this->createDummyConfig();
+
+ $driver = $this->createDriver(
+ $context,
+ $config,
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor', [
+ 'queue' => 'custom',
+ ]),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, $config->getRouterProcessor());
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSendTopicMessageToProcessorToDefaultQueue()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSendTopicMessageToProcessorToCustomQueue()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getCustomQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldInitDeliveryDelayIfDelayPropertyOnSendToProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('setDeliveryDelay')
+ ->with(456000)
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setDelay(456);
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSetInitTimeToLiveIfExpirePropertyOnSendToProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('setTimeToLive')
+ ->with(678000)
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setExpire(678);
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSetInitPriorityIfPriorityPropertyOnSendToProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('setPriority')
+ ->with(3)
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setPriority(MessagePriority::HIGH);
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testThrowIfNoRouteFoundForTopicMessageOnSendToProcessor()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->never())
+ ->method('createProducer')
+ ;
+ $context
+ ->expects($this->never())
+ ->method('createMessage')
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('There is no route for topic "topic" and processor "processor"');
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSetRouterProcessorIfProcessorPropertyEmptyOnSendToProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'expectedProcessor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+
+ $driver->sendToProcessor($message);
+
+ $this->assertSame('router', $message->getProperty(Config::PROCESSOR));
+ }
+
+ public function testShouldSendCommandMessageToProcessorToDefaultQueue()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'processor'),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldSendCommandMessageToProcessorToCustomQueue()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getCustomQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'processor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testThrowIfNoRouteFoundForCommandMessageOnSendToProcessor()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->never())
+ ->method('createProducer')
+ ;
+ $context
+ ->expects($this->never())
+ ->method('createMessage')
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('There is no route for command "command".');
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldOverwriteProcessorPropertySetByOneFromCommandRouteOnSendToProcessor()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($transportMessage))
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with($this->getCustomQueueTransportName())
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'expectedProcessor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setProperty(Config::PROCESSOR, 'processorShouldBeOverwritten');
+
+ $driver->sendToProcessor($message);
+
+ $this->assertSame('expectedProcessor', $message->getProperty(Config::PROCESSOR));
+ }
+
+ public function testShouldNotInitDeliveryDelayOnSendMessageToProcessorIfPropertyNull()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->never())
+ ->method('setDeliveryDelay')
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'expectedProcessor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setDelay(null);
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldNotInitPriorityOnSendMessageToProcessorIfPropertyNull()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->never())
+ ->method('setPriority')
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'expectedProcessor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setPriority(null);
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldNotInitTimeToLiveOnSendMessageToProcessorIfPropertyNull()
+ {
+ $queue = $this->createQueue('');
+ $transportMessage = $this->createMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->never())
+ ->method('setTimeToLive')
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ;
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('command', Route::COMMAND, 'expectedProcessor', ['queue' => 'custom']),
+ ])
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'command');
+ $message->setExpire(null);
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testThrowIfNeitherTopicNorCommandAreSentOnSendToProcessor()
+ {
+ $driver = $this->createDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Queue name parameter is required but is not set');
+
+ $message = new Message();
+ $message->setProperty(Config::PROCESSOR, 'processor');
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Either topic or command parameter must be set.');
+ $driver->sendToProcessor($message);
+ }
+
+ abstract protected function createDriver(...$args): DriverInterface;
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ abstract protected function createContextMock(): Context;
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ abstract protected function createProducerMock(): InteropProducer;
+
+ abstract protected function createQueue(string $name): InteropQueue;
+
+ abstract protected function createTopic(string $name): InteropTopic;
+
+ abstract protected function createMessage(): InteropMessage;
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ protected function createContextStub(): Context
+ {
+ $context = $this->createContextMock();
+
+ $context
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $name) {
+ return $this->createQueue($name);
+ })
+ ;
+
+ $context
+ ->expects($this->any())
+ ->method('createTopic')
+ ->willReturnCallback(function (string $name) {
+ return $this->createTopic($name);
+ })
+ ;
+
+ return $context;
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ $this->assertEquals([
+ 'hkey' => 'hval',
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply_to' => 'theReplyTo',
+ 'correlation_id' => 'theCorrelationId',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+
+ protected function assertClientMessage(Message $clientMessage): void
+ {
+ $this->assertSame('body', $clientMessage->getBody());
+ Assert::assertArraySubset([
+ 'hkey' => 'hval',
+ ], $clientMessage->getHeaders());
+ Assert::assertArraySubset([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'theContentType',
+ Config::EXPIRE => '22',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::DELAY => '44',
+ ], $clientMessage->getProperties());
+ $this->assertSame('theMessageId', $clientMessage->getMessageId());
+ $this->assertSame(22, $clientMessage->getExpire());
+ $this->assertSame(44, $clientMessage->getDelay());
+ $this->assertSame(MessagePriority::HIGH, $clientMessage->getPriority());
+ $this->assertSame('theContentType', $clientMessage->getContentType());
+ $this->assertSame(1000, $clientMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $clientMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $clientMessage->getCorrelationId());
+ }
+
+ protected function createDummyConfig(): Config
+ {
+ return Config::create('aPrefix');
+ }
+
+ protected function getDefaultQueueTransportName(): string
+ {
+ return 'aprefix.default';
+ }
+
+ protected function getCustomQueueTransportName(): string
+ {
+ return 'aprefix.custom';
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return 'aprefix.default';
+ }
+
+ protected function getPrefixAppFooQueueTransportName(): string
+ {
+ return 'aprefix.anappname.afooqueue';
+ }
+
+ protected function getPrefixFooQueueTransportName(): string
+ {
+ return 'aprefix.afooqueue';
+ }
+
+ protected function getAppFooQueueTransportName(): string
+ {
+ return 'anappname.afooqueue';
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/GpsDriverTest.php b/pkg/enqueue/Tests/Client/Driver/GpsDriverTest.php
new file mode 100644
index 000000000..c0cac0458
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/GpsDriverTest.php
@@ -0,0 +1,142 @@
+assertClassImplements(DriverInterface::class, GpsDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, GpsDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routerTopic = new GpsTopic('');
+ $routerQueue = new GpsQueue('');
+
+ $processorTopic = new GpsTopic($this->getDefaultQueueTransportName());
+ $processorQueue = new GpsQueue($this->getDefaultQueueTransportName());
+
+ $context = $this->createContextMock();
+ // setup router
+ $context
+ ->expects($this->at(0))
+ ->method('createTopic')
+ ->willReturn($routerTopic)
+ ;
+ $context
+ ->expects($this->at(1))
+ ->method('createQueue')
+ ->willReturn($routerQueue)
+ ;
+ $context
+ ->expects($this->at(2))
+ ->method('subscribe')
+ ->with($this->identicalTo($routerTopic), $this->identicalTo($routerQueue))
+ ;
+ $context
+ ->expects($this->at(3))
+ ->method('createQueue')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($processorQueue)
+ ;
+ // setup processor queue
+ $context
+ ->expects($this->at(4))
+ ->method('createTopic')
+ ->with($this->getDefaultQueueTransportName())
+ ->willReturn($processorTopic)
+ ;
+ $context
+ ->expects($this->at(5))
+ ->method('subscribe')
+ ->with($this->identicalTo($processorTopic), $this->identicalTo($processorQueue))
+ ;
+
+ $driver = new GpsDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('aTopic', Route::TOPIC, 'aProcessor'),
+ ])
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new GpsDriver(...$args);
+ }
+
+ /**
+ * @return GpsContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(GpsContext::class);
+ }
+
+ /**
+ * @return GpsProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(GpsProducer::class);
+ }
+
+ /**
+ * @return GpsQueue
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new GpsQueue($name);
+ }
+
+ /**
+ * @return GpsTopic
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new GpsTopic($name);
+ }
+
+ /**
+ * @return GpsMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new GpsMessage();
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return 'aprefix.router';
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/MongodbDriverTest.php b/pkg/enqueue/Tests/Client/Driver/MongodbDriverTest.php
new file mode 100644
index 000000000..697c757d8
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/MongodbDriverTest.php
@@ -0,0 +1,105 @@
+assertClassImplements(DriverInterface::class, MongodbDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, MongodbDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createCollection')
+ ;
+ $context
+ ->expects($this->once())
+ ->method('getConfig')
+ ->willReturn([
+ 'dbname' => 'aDb',
+ 'collection_name' => 'aCol',
+ ])
+ ;
+
+ $driver = new MongodbDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new MongodbDriver(...$args);
+ }
+
+ /**
+ * @return MongodbContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(MongodbContext::class);
+ }
+
+ /**
+ * @return MongodbProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(MongodbProducer::class);
+ }
+
+ /**
+ * @return MongodbDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new MongodbDestination($name);
+ }
+
+ /**
+ * @return MongodbDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new MongodbDestination($name);
+ }
+
+ /**
+ * @return MongodbMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new MongodbMessage();
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/RabbitMqDriverTest.php b/pkg/enqueue/Tests/Client/Driver/RabbitMqDriverTest.php
new file mode 100644
index 000000000..b209d85bc
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/RabbitMqDriverTest.php
@@ -0,0 +1,139 @@
+assertClassImplements(DriverInterface::class, RabbitMqDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, RabbitMqDriver::class);
+ }
+
+ public function testShouldBeSubClassOfAmqpDriver()
+ {
+ $this->assertClassExtends(AmqpDriver::class, RabbitMqDriver::class);
+ }
+
+ public function testShouldCreateQueueWithMaxPriorityArgument()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($this->createQueue('aName'))
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var AmqpQueue $queue */
+ $queue = $driver->createQueue('aName');
+
+ $this->assertSame(['x-max-priority' => 4], $queue->getArguments());
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new RabbitMqDriver(...$args);
+ }
+
+ /**
+ * @return AmqpContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(AmqpContext::class);
+ }
+
+ /**
+ * @return AmqpProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(AmqpProducer::class);
+ }
+
+ /**
+ * @return AmqpQueue
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new AmqpQueue($name);
+ }
+
+ protected function createTopic(string $name): AmqpTopic
+ {
+ return new AmqpTopic($name);
+ }
+
+ /**
+ * @return AmqpMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new AmqpMessage();
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return 'aprefix.router';
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ Assert::assertArraySubset([
+ 'hkey' => 'hval',
+ 'delivery_mode' => AmqpMessage::DELIVERY_MODE_PERSISTENT,
+ 'content_type' => 'ContentType',
+ 'expiration' => '123000',
+ 'priority' => 3,
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply_to' => 'theReplyTo',
+ 'correlation_id' => 'theCorrelationId',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/RabbitMqStompDriverTest.php b/pkg/enqueue/Tests/Client/Driver/RabbitMqStompDriverTest.php
new file mode 100644
index 000000000..9fc72be1e
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/RabbitMqStompDriverTest.php
@@ -0,0 +1,590 @@
+assertClassImplements(DriverInterface::class, RabbitMqStompDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, RabbitMqStompDriver::class);
+ }
+
+ public function testShouldBeSubClassOfStompDriver()
+ {
+ $this->assertClassExtends(StompDriver::class, RabbitMqStompDriver::class);
+ }
+
+ public function testShouldCreateAndReturnStompQueueInstance()
+ {
+ $expectedQueue = new StompDestination(ExtensionType::RABBITMQ);
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('aprefix.afooqueue')
+ ->willReturn($expectedQueue)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([]),
+ $this->createManagementClientMock()
+ );
+
+ $queue = $driver->createQueue('aFooQueue');
+
+ $expectedHeaders = [
+ 'durable' => true,
+ 'auto-delete' => false,
+ 'exclusive' => false,
+ 'x-max-priority' => 4,
+ ];
+
+ $this->assertSame($expectedQueue, $queue);
+ $this->assertTrue($queue->isDurable());
+ $this->assertFalse($queue->isAutoDelete());
+ $this->assertFalse($queue->isExclusive());
+ $this->assertSame($expectedHeaders, $queue->getHeaders());
+ }
+
+ public function testThrowIfClientPriorityInvalidOnCreateTransportMessage()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setPriority('unknown');
+
+ $transportMessage = new StompMessage();
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([]),
+ $this->createManagementClientMock()
+ );
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Cant convert client priority to transport: "unknown"');
+
+ $driver->createTransportMessage($clientMessage);
+ }
+
+ public function testThrowIfDelayIsSetButDelayPluginInstalledOptionIsFalse()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setDelay(123);
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn(new StompMessage())
+ ;
+
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => false]
+ );
+
+ $driver = $this->createDriver(
+ $context,
+ $config,
+ new RouteCollection([]),
+ $this->createManagementClientMock()
+ );
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The message delaying is not supported. In order to use delay feature install RabbitMQ delay plugin.');
+
+ $driver->createTransportMessage($clientMessage);
+ }
+
+ public function testShouldSetXDelayHeaderIfDelayPluginInstalledOptionIsTrue()
+ {
+ $clientMessage = new Message();
+ $clientMessage->setDelay(123);
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn(new StompMessage())
+ ;
+
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => true]
+ );
+
+ $driver = $this->createDriver(
+ $context,
+ $config,
+ new RouteCollection([]),
+ $this->createManagementClientMock()
+ );
+
+ $transportMessage = $driver->createTransportMessage($clientMessage);
+
+ $this->assertSame('123000', $transportMessage->getHeader('x-delay'));
+ }
+
+ public function testShouldInitDeliveryDelayIfDelayPropertyOnSendToProcessor()
+ {
+ $this->shouldSendMessageToDelayExchangeIfDelaySet();
+ }
+
+ public function shouldSendMessageToDelayExchangeIfDelaySet()
+ {
+ $queue = new StompDestination(ExtensionType::RABBITMQ);
+ $queue->setStompName('queueName');
+
+ $delayTopic = new StompDestination(ExtensionType::RABBITMQ);
+ $delayTopic->setStompName('delayTopic');
+
+ $transportMessage = new StompMessage();
+
+ $producer = $this->createProducerMock();
+ $producer
+ ->expects($this->at(0))
+ ->method('setDeliveryDelay')
+ ->with(10000)
+ ;
+ $producer
+ ->expects($this->at(1))
+ ->method('setDeliveryDelay')
+ ->with(null)
+ ;
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($delayTopic), $this->identicalTo($transportMessage))
+ ;
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($queue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createTopic')
+ ->willReturn($delayTopic)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($transportMessage)
+ ;
+
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => true]
+ );
+
+ $driver = $this->createDriver(
+ $context,
+ $config,
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ]),
+ $this->createManagementClientMock()
+ );
+
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topic');
+ $message->setProperty(Config::PROCESSOR, 'processor');
+ $message->setDelay(10);
+
+ $driver->sendToProcessor($message);
+ }
+
+ public function testShouldNotSetupBrokerIfManagementPluginInstalledOptionIsNotEnabled()
+ {
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['management_plugin_installed' => false]
+ );
+
+ $driver = $this->createDriver(
+ $this->createContextMock(),
+ $config,
+ new RouteCollection([]),
+ $this->createManagementClientMock()
+ );
+
+ $logger = new TestLogger();
+
+ $driver->setupBroker($logger);
+
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Could not setup broker. The option `management_plugin_installed` is not enabled. Please enable that option and install rabbit management plugin'
+ )
+ );
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ]);
+
+ $managementClient = $this->createManagementClientMock();
+ $managementClient
+ ->expects($this->at(0))
+ ->method('declareExchange')
+ ->with('aprefix.router', [
+ 'type' => 'fanout',
+ 'durable' => true,
+ 'auto_delete' => false,
+ ])
+ ;
+ $managementClient
+ ->expects($this->at(1))
+ ->method('declareQueue')
+ ->with('aprefix.default', [
+ 'durable' => true,
+ 'auto_delete' => false,
+ 'arguments' => [
+ 'x-max-priority' => 4,
+ ],
+ ])
+ ;
+ $managementClient
+ ->expects($this->at(2))
+ ->method('bind')
+ ->with('aprefix.router', 'aprefix.default', 'aprefix.default')
+ ;
+ $managementClient
+ ->expects($this->at(3))
+ ->method('declareQueue')
+ ->with('aprefix.default', [
+ 'durable' => true,
+ 'auto_delete' => false,
+ 'arguments' => [
+ 'x-max-priority' => 4,
+ ],
+ ])
+ ;
+
+ $contextMock = $this->createContextMock();
+ $contextMock
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $name) {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_QUEUE);
+ $destination->setStompName($name);
+
+ return $destination;
+ })
+ ;
+
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => false, 'management_plugin_installed' => true]
+ );
+
+ $driver = $this->createDriver(
+ $contextMock,
+ $config,
+ $routeCollection,
+ $managementClient
+ );
+
+ $logger = new TestLogger();
+
+ $driver->setupBroker($logger);
+
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Declare router exchange: aprefix.router'
+ )
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Declare router queue: aprefix.default'
+ )
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Bind router queue to exchange: aprefix.default -> aprefix.router'
+ )
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Declare processor queue: aprefix.default'
+ )
+ );
+ }
+
+ public function testSetupBrokerShouldCreateDelayExchangeIfEnabled()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ]);
+
+ $managementClient = $this->createManagementClientMock();
+ $managementClient
+ ->expects($this->at(4))
+ ->method('declareExchange')
+ ->with('aprefix.default.delayed', [
+ 'type' => 'x-delayed-message',
+ 'durable' => true,
+ 'auto_delete' => false,
+ 'arguments' => [
+ 'x-delayed-type' => 'direct',
+ ],
+ ])
+ ;
+ $managementClient
+ ->expects($this->at(5))
+ ->method('bind')
+ ->with('aprefix.default.delayed', 'aprefix.default', 'aprefix.default')
+ ;
+
+ $config = Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => true, 'management_plugin_installed' => true]
+ );
+
+ $contextMock = $this->createContextMock();
+ $contextMock
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $name) {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_QUEUE);
+ $destination->setStompName($name);
+
+ return $destination;
+ })
+ ;
+ $contextMock
+ ->expects($this->any())
+ ->method('createTopic')
+ ->willReturnCallback(function (string $name) {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_TOPIC);
+ $destination->setStompName($name);
+
+ return $destination;
+ })
+ ;
+
+ $driver = $this->createDriver(
+ $contextMock,
+ $config,
+ $routeCollection,
+ $managementClient
+ );
+
+ $logger = new TestLogger();
+
+ $driver->setupBroker($logger);
+
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Declare delay exchange: aprefix.default.delayed'
+ )
+ );
+ self::assertTrue(
+ $logger->hasDebugThatContains(
+ '[RabbitMqStompDriver] Bind processor queue to delay exchange: aprefix.default -> aprefix.default.delayed'
+ )
+ );
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new RabbitMqStompDriver(
+ $args[0],
+ $args[1],
+ $args[2],
+ isset($args[3]) ? $args[3] : $this->createManagementClientMock()
+ );
+ }
+
+ /**
+ * @return StompContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(StompContext::class);
+ }
+
+ /**
+ * @return StompProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(StompProducer::class);
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_QUEUE);
+ $destination->setStompName($name);
+
+ return $destination;
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_TOPIC);
+ $destination->setStompName($name);
+
+ return $destination;
+ }
+
+ /**
+ * @return StompMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new StompMessage();
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ $this->assertEquals([
+ 'hkey' => 'hval',
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply-to' => 'theReplyTo',
+ 'persistent' => true,
+ 'correlation_id' => 'theCorrelationId',
+ 'expiration' => '123000',
+ 'priority' => 3,
+ 'x-delay' => '345000',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+
+ protected function createDummyConfig(): Config
+ {
+ return Config::create(
+ 'aPrefix',
+ '.',
+ '',
+ null,
+ null,
+ null,
+ null,
+ ['delay_plugin_installed' => true, 'management_plugin_installed' => true]
+ );
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return '/topic/aprefix.router';
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createManagementClientMock(): StompManagementClient
+ {
+ return $this->createMock(StompManagementClient::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/RdKafkaDriverTest.php b/pkg/enqueue/Tests/Client/Driver/RdKafkaDriverTest.php
new file mode 100644
index 000000000..c5e40e71d
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/RdKafkaDriverTest.php
@@ -0,0 +1,122 @@
+assertClassImplements(DriverInterface::class, RdKafkaDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, RdKafkaDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routerTopic = new RdKafkaTopic('');
+ $routerQueue = new RdKafkaTopic('');
+
+ $processorTopic = new RdKafkaTopic('');
+
+ $context = $this->createContextMock();
+
+ $context
+ ->expects($this->at(0))
+ ->method('createQueue')
+ ->willReturn($routerTopic)
+ ;
+ $context
+ ->expects($this->at(1))
+ ->method('createQueue')
+ ->willReturn($routerQueue)
+ ;
+ $context
+ ->expects($this->at(2))
+ ->method('createQueue')
+ ->willReturn($processorTopic)
+ ;
+
+ $driver = new RdKafkaDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new RdKafkaDriver(...$args);
+ }
+
+ /**
+ * @return RdKafkaContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(RdKafkaContext::class);
+ }
+
+ /**
+ * @return RdKafkaProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(RdKafkaProducer::class);
+ }
+
+ /**
+ * @return RdKafkaTopic
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new RdKafkaTopic($name);
+ }
+
+ protected function createTopic(string $name): RdKafkaTopic
+ {
+ return new RdKafkaTopic($name);
+ }
+
+ /**
+ * @return RdKafkaMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new RdKafkaMessage();
+ }
+
+ /**
+ * @return Config
+ */
+ private function createDummyConfig()
+ {
+ return Config::create('aPrefix');
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/SqsDriverTest.php b/pkg/enqueue/Tests/Client/Driver/SqsDriverTest.php
new file mode 100644
index 000000000..2e3005e6a
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/SqsDriverTest.php
@@ -0,0 +1,153 @@
+assertClassImplements(DriverInterface::class, SqsDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, SqsDriver::class);
+ }
+
+ public function testShouldSetupBroker()
+ {
+ $routerQueue = new SqsDestination('');
+ $processorQueue = new SqsDestination('');
+
+ $context = $this->createContextMock();
+ // setup router
+ $context
+ ->expects($this->at(0))
+ ->method('createQueue')
+ ->with('aprefix_dot_default')
+ ->willReturn($routerQueue)
+ ;
+ $context
+ ->expects($this->at(1))
+ ->method('declareQueue')
+ ->with($this->identicalTo($routerQueue))
+ ;
+ // setup processor queue
+ $context
+ ->expects($this->at(2))
+ ->method('createQueue')
+ ->with('aprefix_dot_default')
+ ->willReturn($processorQueue)
+ ;
+ $context
+ ->expects($this->at(3))
+ ->method('declareQueue')
+ ->with($this->identicalTo($processorQueue))
+ ;
+
+ $driver = new SqsDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ])
+ );
+
+ $driver->setupBroker();
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new SqsDriver(...$args);
+ }
+
+ /**
+ * @return SqsContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(SqsContext::class);
+ }
+
+ /**
+ * @return SqsProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(SqsProducer::class);
+ }
+
+ /**
+ * @return SqsDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ return new SqsDestination($name);
+ }
+
+ /**
+ * @return SqsDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ return new SqsDestination($name);
+ }
+
+ /**
+ * @return SqsMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new SqsMessage();
+ }
+
+ protected function getPrefixAppFooQueueTransportName(): string
+ {
+ return 'aprefix_dot_anappname_dot_afooqueue';
+ }
+
+ protected function getPrefixFooQueueTransportName(): string
+ {
+ return 'aprefix_dot_afooqueue';
+ }
+
+ protected function getAppFooQueueTransportName(): string
+ {
+ return 'anappname_dot_afooqueue';
+ }
+
+ protected function getDefaultQueueTransportName(): string
+ {
+ return 'aprefix_dot_default';
+ }
+
+ protected function getCustomQueueTransportName(): string
+ {
+ return 'aprefix_dot_custom';
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return 'aprefix_dot_default';
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/StompDriverTest.php b/pkg/enqueue/Tests/Client/Driver/StompDriverTest.php
new file mode 100644
index 000000000..8f777fdbd
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/StompDriverTest.php
@@ -0,0 +1,191 @@
+assertClassImplements(DriverInterface::class, StompDriver::class);
+ }
+
+ public function testShouldBeSubClassOfGenericDriver()
+ {
+ $this->assertClassExtends(GenericDriver::class, StompDriver::class);
+ }
+
+ public function testSetupBrokerShouldOnlyLogMessageThatStompDoesNotSupportBrokerSetup()
+ {
+ $driver = new StompDriver(
+ $this->createContextMock(),
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ $logger = $this->createLoggerMock();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[StompDriver] Stomp protocol does not support broker configuration')
+ ;
+
+ $driver->setupBroker($logger);
+ }
+
+ public function testShouldCreateDurableQueue()
+ {
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createQueue')
+ ->willReturn($this->createQueue('aName'))
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var StompDestination $queue */
+ $queue = $driver->createQueue('aName');
+
+ $this->assertTrue($queue->isDurable());
+ $this->assertFalse($queue->isAutoDelete());
+ $this->assertFalse($queue->isExclusive());
+ }
+
+ public function testShouldSetPersistedTrueOnCreateTransportMessage()
+ {
+ $clientMessage = new Message();
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createMessage')
+ ->willReturn($this->createMessage())
+ ;
+
+ $driver = $this->createDriver(
+ $context,
+ $this->createDummyConfig(),
+ new RouteCollection([])
+ );
+
+ /** @var StompMessage $transportMessage */
+ $transportMessage = $driver->createTransportMessage($clientMessage);
+
+ $this->assertTrue($transportMessage->isPersistent());
+ }
+
+ protected function createDriver(...$args): DriverInterface
+ {
+ return new StompDriver(...$args);
+ }
+
+ /**
+ * @return StompContext
+ */
+ protected function createContextMock(): Context
+ {
+ return $this->createMock(StompContext::class);
+ }
+
+ /**
+ * @return StompProducer
+ */
+ protected function createProducerMock(): InteropProducer
+ {
+ return $this->createMock(StompProducer::class);
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function createQueue(string $name): InteropQueue
+ {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_QUEUE);
+ $destination->setStompName($name);
+
+ return $destination;
+ }
+
+ /**
+ * @return StompDestination
+ */
+ protected function createTopic(string $name): InteropTopic
+ {
+ $destination = new StompDestination(ExtensionType::RABBITMQ);
+ $destination->setType(StompDestination::TYPE_TOPIC);
+ $destination->setStompName($name);
+
+ return $destination;
+ }
+
+ /**
+ * @return StompMessage
+ */
+ protected function createMessage(): InteropMessage
+ {
+ return new StompMessage();
+ }
+
+ protected function assertTransportMessage(InteropMessage $transportMessage): void
+ {
+ $this->assertSame('body', $transportMessage->getBody());
+ $this->assertEquals([
+ 'hkey' => 'hval',
+ 'message_id' => 'theMessageId',
+ 'timestamp' => 1000,
+ 'reply-to' => 'theReplyTo',
+ 'persistent' => true,
+ 'correlation_id' => 'theCorrelationId',
+ ], $transportMessage->getHeaders());
+ $this->assertEquals([
+ 'pkey' => 'pval',
+ Config::CONTENT_TYPE => 'ContentType',
+ Config::PRIORITY => MessagePriority::HIGH,
+ Config::EXPIRE => 123,
+ Config::DELAY => 345,
+ ], $transportMessage->getProperties());
+ $this->assertSame('theMessageId', $transportMessage->getMessageId());
+ $this->assertSame(1000, $transportMessage->getTimestamp());
+ $this->assertSame('theReplyTo', $transportMessage->getReplyTo());
+ $this->assertSame('theCorrelationId', $transportMessage->getCorrelationId());
+ }
+
+ protected function createLoggerMock(): LoggerInterface
+ {
+ return $this->createMock(LoggerInterface::class);
+ }
+
+ protected function getRouterTransportName(): string
+ {
+ return '/topic/aprefix.router';
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Driver/StompManagementClientTest.php b/pkg/enqueue/Tests/Client/Driver/StompManagementClientTest.php
new file mode 100644
index 000000000..081a62c5f
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Driver/StompManagementClientTest.php
@@ -0,0 +1,114 @@
+createExchangeMock();
+ $exchange
+ ->expects($this->once())
+ ->method('create')
+ ->with('vhost', 'name', ['options'])
+ ->willReturn([])
+ ;
+
+ $client = $this->createClientMock();
+ $client
+ ->expects($this->once())
+ ->method('exchanges')
+ ->willReturn($exchange)
+ ;
+
+ $management = new StompManagementClient($client, 'vhost');
+ $management->declareExchange('name', ['options']);
+ }
+
+ public function testCouldDeclareQueue()
+ {
+ $queue = $this->createQueueMock();
+ $queue
+ ->expects($this->once())
+ ->method('create')
+ ->with('vhost', 'name', ['options'])
+ ->willReturn([])
+ ;
+
+ $client = $this->createClientMock();
+ $client
+ ->expects($this->once())
+ ->method('queues')
+ ->willReturn($queue)
+ ;
+
+ $management = new StompManagementClient($client, 'vhost');
+ $management->declareQueue('name', ['options']);
+ }
+
+ public function testCouldBind()
+ {
+ $binding = $this->createBindingMock();
+ $binding
+ ->expects($this->once())
+ ->method('create')
+ ->with('vhost', 'exchange', 'queue', 'routing-key', ['arguments'])
+ ->willReturn([])
+ ;
+
+ $client = $this->createClientMock();
+ $client
+ ->expects($this->once())
+ ->method('bindings')
+ ->willReturn($binding)
+ ;
+
+ $management = new StompManagementClient($client, 'vhost');
+ $management->bind('exchange', 'queue', 'routing-key', ['arguments']);
+ }
+
+ public function testCouldCreateNewInstanceUsingFactory()
+ {
+ $instance = StompManagementClient::create('', '');
+
+ $this->assertInstanceOf(StompManagementClient::class, $instance);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|Client
+ */
+ private function createClientMock()
+ {
+ return $this->createMock(Client::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|Exchange
+ */
+ private function createExchangeMock()
+ {
+ return $this->createMock(Exchange::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|Queue
+ */
+ private function createQueueMock()
+ {
+ return $this->createMock(Queue::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|Binding
+ */
+ private function createBindingMock()
+ {
+ return $this->createMock(Binding::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/DriverFactoryTest.php b/pkg/enqueue/Tests/Client/DriverFactoryTest.php
new file mode 100644
index 000000000..3d9d7b9b5
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/DriverFactoryTest.php
@@ -0,0 +1,186 @@
+assertTrue($rc->implementsInterface(DriverFactoryInterface::class));
+ }
+
+ public function testShouldBeFinal()
+ {
+ $rc = new \ReflectionClass(DriverFactory::class);
+
+ $this->assertTrue($rc->isFinal());
+ }
+
+ public function testThrowIfPackageThatSupportSchemeNotInstalled()
+ {
+ $scheme = 'scheme5b7aa7d7cd213';
+ $class = 'ConnectionClass5b7aa7d7cd213';
+
+ Resources::addDriver($class, [$scheme], [], ['thePackage', 'theOtherPackage']);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('To use given scheme "scheme5b7aa7d7cd213" a package has to be installed. Run "composer req thePackage theOtherPackage" to add it.');
+ $factory = new DriverFactory();
+
+ $factory->create($this->createConnectionFactoryMock(), $this->createDummyConfig($scheme.'://foo'), new RouteCollection([]));
+ }
+
+ public function testThrowIfSchemeIsNotKnown()
+ {
+ $scheme = 'scheme5b7aa862e70a5';
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('A given scheme "scheme5b7aa862e70a5" is not supported. Maybe it is a custom driver, make sure you registered it with "Enqueue\Client\Resources::addDriver".');
+
+ $factory = new DriverFactory();
+
+ $factory->create($this->createConnectionFactoryMock(), $this->createDummyConfig($scheme.'://foo'), new RouteCollection([]));
+ }
+
+ public function testThrowIfDsnInvalid()
+ {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The DSN is invalid. It does not have scheme separator ":".');
+
+ $factory = new DriverFactory();
+
+ $factory->create($this->createConnectionFactoryMock(), $this->createDummyConfig('invalidDsn'), new RouteCollection([]));
+ }
+
+ /**
+ * @dataProvider provideDSN
+ */
+ public function testReturnsExpectedFactories(
+ string $dsn,
+ string $connectionFactoryClass,
+ string $contextClass,
+ array $conifg,
+ string $expectedDriverClass,
+ ) {
+ $connectionFactoryMock = $this->createMock($connectionFactoryClass);
+ $connectionFactoryMock
+ ->expects($this->once())
+ ->method('createContext')
+ ->willReturn($this->createMock($contextClass))
+ ;
+
+ $driverFactory = new DriverFactory();
+
+ $driver = $driverFactory->create($connectionFactoryMock, $this->createDummyConfig($dsn), new RouteCollection([]));
+
+ $this->assertInstanceOf($expectedDriverClass, $driver);
+ }
+
+ public static function provideDSN()
+ {
+ yield ['null:', NullConnectionFactory::class, NullContext::class, [], GenericDriver::class];
+
+ yield ['amqp:', AmqpConnectionFactory::class, AmqpContext::class, [], AmqpDriver::class];
+
+ yield ['amqp+rabbitmq:', AmqpConnectionFactory::class, AmqpContext::class, [], RabbitMqDriver::class];
+
+ yield ['mysql:', DbalConnectionFactory::class, DbalContext::class, [], DbalDriver::class];
+
+ yield ['file:', FsConnectionFactory::class, FsContext::class, [], FsDriver::class];
+
+ // https://github.com/php-enqueue/enqueue-dev/issues/511
+ // yield ['gearman:', GearmanConnectionFactory::class, NullContext::class, [], NullDriver::class];
+
+ yield ['gps:', GpsConnectionFactory::class, GpsContext::class, [], GpsDriver::class];
+
+ yield ['mongodb:', MongodbConnectionFactory::class, MongodbContext::class, [], MongodbDriver::class];
+
+ yield ['kafka:', RdKafkaConnectionFactory::class, RdKafkaContext::class, [], RdKafkaDriver::class];
+
+ yield ['redis:', RedisConnectionFactory::class, RedisContext::class, [], GenericDriver::class];
+
+ yield ['redis+predis:', RedisConnectionFactory::class, RedisContext::class, [], GenericDriver::class];
+
+ yield ['sqs:', SqsConnectionFactory::class, SqsContext::class, [], SqsDriver::class];
+
+ yield ['stomp:', StompConnectionFactory::class, StompContext::class, [], StompDriver::class];
+
+ yield ['stomp+rabbitmq:', StompConnectionFactory::class, StompContext::class, [], RabbitMqStompDriver::class];
+
+ yield ['stomp+foo+bar:', StompConnectionFactory::class, StompContext::class, [], StompDriver::class];
+
+ yield ['gearman:', GearmanConnectionFactory::class, GearmanContext::class, [], GenericDriver::class];
+
+ yield ['beanstalk:', PheanstalkConnectionFactory::class, PheanstalkContext::class, [], GenericDriver::class];
+ }
+
+ private function createDummyConfig(string $dsn): Config
+ {
+ return Config::create(
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ null,
+ ['dsn' => $dsn],
+ []
+ );
+ }
+
+ private function createConnectionFactoryMock(): ConnectionFactory
+ {
+ return $this->createMock(ConnectionFactory::class);
+ }
+
+ private function createConfigMock(): Config
+ {
+ return $this->createMock(Config::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/DriverPreSendTest.php b/pkg/enqueue/Tests/Client/DriverPreSendTest.php
new file mode 100644
index 000000000..32af2a81f
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/DriverPreSendTest.php
@@ -0,0 +1,84 @@
+createProducerMock();
+ $expectedDriver = $this->createDriverMock();
+
+ $context = new DriverPreSend(
+ $expectedMessage,
+ $expectedProducer,
+ $expectedDriver
+ );
+
+ $this->assertSame($expectedMessage, $context->getMessage());
+ $this->assertSame($expectedProducer, $context->getProducer());
+ $this->assertSame($expectedDriver, $context->getDriver());
+ }
+
+ public function testShouldAllowGetCommand()
+ {
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'theCommand');
+
+ $context = new DriverPreSend(
+ $message,
+ $this->createProducerMock(),
+ $this->createDriverMock()
+ );
+
+ $this->assertFalse($context->isEvent());
+ $this->assertSame('theCommand', $context->getCommand());
+ }
+
+ public function testShouldAllowGetTopic()
+ {
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'theTopic');
+
+ $context = new DriverPreSend(
+ $message,
+ $this->createProducerMock(),
+ $this->createDriverMock()
+ );
+
+ $this->assertTrue($context->isEvent());
+ $this->assertSame('theTopic', $context->getTopic());
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverMock(): DriverInterface
+ {
+ return $this->createMock(DriverInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createProducerMock(): ProducerInterface
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/Extension/PrepareBodyExtensionTest.php b/pkg/enqueue/Tests/Client/Extension/PrepareBodyExtensionTest.php
new file mode 100644
index 000000000..c3032ccc8
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/Extension/PrepareBodyExtensionTest.php
@@ -0,0 +1,131 @@
+assertTrue($rc->implementsInterface(PreSendEventExtensionInterface::class));
+ $this->assertTrue($rc->implementsInterface(PreSendCommandExtensionInterface::class));
+ }
+
+ /**
+ * @dataProvider provideMessages
+ *
+ * @param mixed|null $contentType
+ */
+ public function testShouldSendStringUnchangedAndAddPlainTextContentTypeIfEmpty(
+ $body,
+ $contentType,
+ string $expectedBody,
+ string $expectedContentType,
+ ) {
+ $message = new Message($body);
+ $message->setContentType($contentType);
+
+ $context = $this->createDummyPreSendContext('aTopic', $message);
+
+ $extension = new PrepareBodyExtension();
+
+ $extension->onPreSendEvent($context);
+
+ $this->assertSame($expectedBody, $message->getBody());
+ $this->assertSame($expectedContentType, $message->getContentType());
+ }
+
+ public function testThrowIfBodyIsObject()
+ {
+ $message = new Message(new \stdClass());
+
+ $context = $this->createDummyPreSendContext('aTopic', $message);
+
+ $extension = new PrepareBodyExtension();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The message\'s body must be either null, scalar, array or object (implements \JsonSerializable). Got: stdClass');
+
+ $extension->onPreSendEvent($context);
+ }
+
+ public function testThrowIfBodyIsArrayWithObjectsInsideOnSend()
+ {
+ $message = new Message(['foo' => new \stdClass()]);
+
+ $context = $this->createDummyPreSendContext('aTopic', $message);
+
+ $extension = new PrepareBodyExtension();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The message\'s body must be an array of scalars. Found not scalar in the array: stdClass');
+
+ $extension->onPreSendEvent($context);
+ }
+
+ public function testShouldThrowExceptionIfBodyIsArrayWithObjectsInSubArraysInsideOnSend()
+ {
+ $message = new Message(['foo' => ['bar' => new \stdClass()]]);
+
+ $context = $this->createDummyPreSendContext('aTopic', $message);
+
+ $extension = new PrepareBodyExtension();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The message\'s body must be an array of scalars. Found not scalar in the array: stdClass');
+
+ $extension->onPreSendEvent($context);
+ }
+
+ public static function provideMessages()
+ {
+ yield ['theBody', null, 'theBody', 'text/plain'];
+
+ yield ['theBody', 'foo/bar', 'theBody', 'foo/bar'];
+
+ yield [12345, null, '12345', 'text/plain'];
+
+ yield [12345, 'foo/bar', '12345', 'foo/bar'];
+
+ yield [12.345, null, '12.345', 'text/plain'];
+
+ yield [12.345, 'foo/bar', '12.345', 'foo/bar'];
+
+ yield [true, null, '1', 'text/plain'];
+
+ yield [true, 'foo/bar', '1', 'foo/bar'];
+
+ yield [null, null, '', 'text/plain'];
+
+ yield [null, 'foo/bar', '', 'foo/bar'];
+
+ yield [['foo' => 'fooVal'], null, '{"foo":"fooVal"}', 'application/json'];
+
+ yield [['foo' => 'fooVal'], 'foo/bar', '{"foo":"fooVal"}', 'foo/bar'];
+
+ yield [new JsonSerializableObject(), null, '{"foo":"fooVal"}', 'application/json'];
+
+ yield [new JsonSerializableObject(), 'foo/bar', '{"foo":"fooVal"}', 'foo/bar'];
+ }
+
+ private function createDummyPreSendContext($commandOrTopic, $message): PreSend
+ {
+ return new PreSend(
+ $commandOrTopic,
+ $message,
+ $this->createMock(ProducerInterface::class),
+ $this->createMock(DriverInterface::class)
+ );
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/MessagePriorityTest.php b/pkg/enqueue/Tests/Client/MessagePriorityTest.php
index 5778e0299..a677884d2 100644
--- a/pkg/enqueue/Tests/Client/MessagePriorityTest.php
+++ b/pkg/enqueue/Tests/Client/MessagePriorityTest.php
@@ -3,8 +3,9 @@
namespace Enqueue\Tests\Client;
use Enqueue\Client\MessagePriority;
+use PHPUnit\Framework\TestCase;
-class MessagePriorityTest extends \PHPUnit_Framework_TestCase
+class MessagePriorityTest extends TestCase
{
public function testShouldVeryLowPriorityHasExpectedValue()
{
diff --git a/pkg/enqueue/Tests/Client/MessageProducerTest.php b/pkg/enqueue/Tests/Client/MessageProducerTest.php
deleted file mode 100644
index b85aa1b98..000000000
--- a/pkg/enqueue/Tests/Client/MessageProducerTest.php
+++ /dev/null
@@ -1,349 +0,0 @@
-createDriverStub());
- }
-
- public function testShouldSendMessageToRouter()
- {
- $message = new Message();
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- $expectedProperties = [
- 'enqueue.topic_name' => 'topic',
- ];
-
- self::assertEquals($expectedProperties, $message->getProperties());
- }
-
- public function testShouldSendMessageWithNormalPriorityByDefault()
- {
- $message = new Message();
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertSame(MessagePriority::NORMAL, $message->getPriority());
- }
-
- public function testShouldSendMessageWithCustomPriority()
- {
- $message = new Message();
- $message->setPriority(MessagePriority::HIGH);
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertSame(MessagePriority::HIGH, $message->getPriority());
- }
-
- public function testShouldSendMessageWithGeneratedMessageId()
- {
- $message = new Message();
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertNotEmpty($message->getMessageId());
- }
-
- public function testShouldSendMessageWithCustomMessageId()
- {
- $message = new Message();
- $message->setMessageId('theCustomMessageId');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertSame('theCustomMessageId', $message->getMessageId());
- }
-
- public function testShouldSendMessageWithGeneratedTimestamp()
- {
- $message = new Message();
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertNotEmpty($message->getTimestamp());
- }
-
- public function testShouldSendMessageWithCustomTimestamp()
- {
- $message = new Message();
- $message->setTimestamp('theCustomTimestamp');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->with(self::identicalTo($message))
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
-
- self::assertSame('theCustomTimestamp', $message->getTimestamp());
- }
-
- public function testShouldSendStringAsPlainText()
- {
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertSame('theStringMessage', $message->getBody());
- self::assertSame('text/plain', $message->getContentType());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', 'theStringMessage');
- }
-
- public function testShouldSendArrayAsJsonString()
- {
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertSame('{"foo":"fooVal"}', $message->getBody());
- self::assertSame('application/json', $message->getContentType());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', ['foo' => 'fooVal']);
- }
-
- public function testShouldConvertMessageArrayBodyJsonString()
- {
- $message = new Message();
- $message->setBody(['foo' => 'fooVal']);
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertSame('{"foo":"fooVal"}', $message->getBody());
- self::assertSame('application/json', $message->getContentType());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send('topic', $message);
- }
-
- public function testSendShouldForceScalarsToStringAndSetTextContentType()
- {
- $queue = new NullQueue('');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertEquals('text/plain', $message->getContentType());
-
- self::assertInternalType('string', $message->getBody());
- self::assertEquals('12345', $message->getBody());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send($queue, 12345);
- }
-
- public function testSendShouldForceMessageScalarsBodyToStringAndSetTextContentType()
- {
- $queue = new NullQueue('');
-
- $message = new Message();
- $message->setBody(12345);
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertEquals('text/plain', $message->getContentType());
-
- self::assertInternalType('string', $message->getBody());
- self::assertEquals('12345', $message->getBody());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send($queue, $message);
- }
-
- public function testSendShouldForceNullToEmptyStringAndSetTextContentType()
- {
- $queue = new NullQueue('');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertEquals('text/plain', $message->getContentType());
-
- self::assertInternalType('string', $message->getBody());
- self::assertEquals('', $message->getBody());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send($queue, null);
- }
-
- public function testSendShouldForceNullBodyToEmptyStringAndSetTextContentType()
- {
- $queue = new NullQueue('');
-
- $message = new Message();
- $message->setBody(null);
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->once())
- ->method('sendToRouter')
- ->willReturnCallback(function (Message $message) {
- self::assertEquals('text/plain', $message->getContentType());
-
- self::assertInternalType('string', $message->getBody());
- self::assertEquals('', $message->getBody());
- })
- ;
-
- $producer = new MessageProducer($driver);
- $producer->send($queue, $message);
- }
-
- public function testShouldThrowExceptionIfBodyIsObjectOnSend()
- {
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->never())
- ->method('sendToRouter')
- ;
-
- $producer = new MessageProducer($driver);
-
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('The message\'s body must be either null, scalar or array. Got: stdClass');
-
- $producer->send('topic', new \stdClass());
- }
-
- public function testShouldThrowExceptionIfBodyIsArrayWithObjectsInsideOnSend()
- {
- $queue = new NullQueue('queue');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->never())
- ->method('sendToRouter')
- ;
-
- $producer = new MessageProducer($driver);
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The message\'s body must be an array of scalars. Found not scalar in the array: stdClass');
-
- $producer->send($queue, ['foo' => new \stdClass()]);
- }
-
- public function testShouldThrowExceptionIfBodyIsArrayWithObjectsInSubArraysInsideOnSend()
- {
- $queue = new NullQueue('queue');
-
- $driver = $this->createDriverStub();
- $driver
- ->expects($this->never())
- ->method('sendToRouter')
- ;
-
- $producer = new MessageProducer($driver);
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('The message\'s body must be an array of scalars. Found not scalar in the array: stdClass');
-
- $producer->send($queue, ['foo' => ['bar' => new \stdClass()]]);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
- */
- protected function createDriverStub()
- {
- return $this->createMock(DriverInterface::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Client/MessageTest.php b/pkg/enqueue/Tests/Client/MessageTest.php
index 73425c786..a9a1d956c 100644
--- a/pkg/enqueue/Tests/Client/MessageTest.php
+++ b/pkg/enqueue/Tests/Client/MessageTest.php
@@ -3,12 +3,26 @@
namespace Enqueue\Tests\Client;
use Enqueue\Client\Message;
+use PHPUnit\Framework\TestCase;
-class MessageTest extends \PHPUnit_Framework_TestCase
+class MessageTest extends TestCase
{
- public function testCouldBeConstructedWithoutAnyArguments()
+ public function testCouldBeConstructedWithoutArguments()
{
- new Message();
+ $message = new Message();
+
+ $this->assertSame('', $message->getBody());
+ $this->assertSame([], $message->getProperties());
+ $this->assertSame([], $message->getHeaders());
+ }
+
+ public function testCouldBeConstructedWithOptionalArguments()
+ {
+ $message = new Message('theBody', ['barProp' => 'barPropVal'], ['fooHeader' => 'fooHeaderVal']);
+
+ $this->assertSame('theBody', $message->getBody());
+ $this->assertSame(['barProp' => 'barPropVal'], $message->getProperties());
+ $this->assertSame(['fooHeader' => 'fooHeaderVal'], $message->getHeaders());
}
public function testShouldAllowGetPreviouslySetBody()
@@ -38,6 +52,15 @@ public function testShouldAllowGetPreviouslySetDelay()
self::assertSame('theDelay', $message->getDelay());
}
+ public function testShouldAllowGetPreviouslySetScope()
+ {
+ $message = new Message();
+
+ $message->setScope('theScope');
+
+ self::assertSame('theScope', $message->getScope());
+ }
+
public function testShouldAllowGetPreviouslySetExpire()
{
$message = new Message();
@@ -81,6 +104,31 @@ public function testShouldSetEmptyArrayAsDefaultHeadersInConstructor()
self::assertSame([], $message->getHeaders());
}
+ public function testShouldSetMessageBusScopeInConstructor()
+ {
+ $message = new Message();
+
+ self::assertSame(Message::SCOPE_MESSAGE_BUS, $message->getScope());
+ }
+
+ public function testShouldAllowGetPreviouslySetReplyTo()
+ {
+ $message = new Message();
+
+ $message->setReplyTo('theReplyTo');
+
+ self::assertSame('theReplyTo', $message->getReplyTo());
+ }
+
+ public function testShouldAllowGetPreviouslySetCorrelationId()
+ {
+ $message = new Message();
+
+ $message->setCorrelationId('theCorrelationId');
+
+ self::assertSame('theCorrelationId', $message->getCorrelationId());
+ }
+
public function testShouldAllowGetPreviouslySetHeaders()
{
$message = new Message();
diff --git a/pkg/enqueue/Tests/Client/Meta/QueueMetaRegistryTest.php b/pkg/enqueue/Tests/Client/Meta/QueueMetaRegistryTest.php
deleted file mode 100644
index 028ab941e..000000000
--- a/pkg/enqueue/Tests/Client/Meta/QueueMetaRegistryTest.php
+++ /dev/null
@@ -1,134 +0,0 @@
- [],
- 'anotherQueueName' => [],
- ];
-
- $registry = new QueueMetaRegistry($this->createConfig(), $meta);
-
- $this->assertAttributeEquals($meta, 'meta', $registry);
- }
-
- public function testShouldAllowAddQueueMetaUsingAddMethod()
- {
- $registry = new QueueMetaRegistry($this->createConfig(), []);
-
- $registry->add('theFooQueueName', 'theTransportQueueName');
- $registry->add('theBarQueueName');
-
- $this->assertAttributeSame([
- 'theFooQueueName' => [
- 'transportName' => 'theTransportQueueName',
- 'processors' => [],
- ],
- 'theBarQueueName' => [
- 'transportName' => null,
- 'processors' => [],
- ],
- ], 'meta', $registry);
- }
-
- public function testShouldAllowAddSubscriber()
- {
- $registry = new QueueMetaRegistry($this->createConfig(), []);
-
- $registry->addProcessor('theFooQueueName', 'theFooProcessorName');
- $registry->addProcessor('theFooQueueName', 'theBarProcessorName');
- $registry->addProcessor('theBarQueueName', 'theBazProcessorName');
-
- $this->assertAttributeSame([
- 'theFooQueueName' => [
- 'transportName' => null,
- 'processors' => ['theFooProcessorName', 'theBarProcessorName'],
- ],
- 'theBarQueueName' => [
- 'transportName' => null,
- 'processors' => ['theBazProcessorName'],
- ],
- ], 'meta', $registry);
- }
-
- public function testThrowIfThereIsNotMetaForRequestedClientQueueName()
- {
- $registry = new QueueMetaRegistry($this->createConfig(), []);
-
- $this->expectException(\InvalidArgumentException::class);
- $this->expectExceptionMessage('The queue meta not found. Requested name `aName`');
- $registry->getQueueMeta('aName');
- }
-
- public function testShouldAllowGetQueueByNameWithDefaultInfo()
- {
- $queues = [
- 'theQueueName' => [],
- ];
-
- $registry = new QueueMetaRegistry($this->createConfig(), $queues);
-
- $queue = $registry->getQueueMeta('theQueueName');
-
- $this->assertInstanceOf(QueueMeta::class, $queue);
- $this->assertSame('theQueueName', $queue->getClientName());
- $this->assertSame('aprefix.anappname.thequeuename', $queue->getTransportName());
- $this->assertSame([], $queue->getProcessors());
- }
-
- public function testShouldAllowGetQueueByNameWithCustomInfo()
- {
- $queues = [
- 'theClientQueueName' => ['transportName' => 'theTransportName', 'processors' => ['theSubscriber']],
- ];
-
- $registry = new QueueMetaRegistry($this->createConfig(), $queues);
-
- $queue = $registry->getQueueMeta('theClientQueueName');
- $this->assertInstanceOf(QueueMeta::class, $queue);
- $this->assertSame('theClientQueueName', $queue->getClientName());
- $this->assertSame('theTransportName', $queue->getTransportName());
- $this->assertSame(['theSubscriber'], $queue->getProcessors());
- }
-
- public function testShouldAllowGetAllQueues()
- {
- $queues = [
- 'fooQueueName' => [],
- 'barQueueName' => [],
- ];
-
- $registry = new QueueMetaRegistry($this->createConfig(), $queues);
-
- $queues = $registry->getQueuesMeta();
- $this->assertInstanceOf(\Generator::class, $queues);
-
- $queues = iterator_to_array($queues);
- /* @var QueueMeta[] $queues */
-
- $this->assertContainsOnly(QueueMeta::class, $queues);
- $this->assertCount(2, $queues);
-
- $this->assertSame('fooQueueName', $queues[0]->getClientName());
- $this->assertSame('aprefix.anappname.fooqueuename', $queues[0]->getTransportName());
-
- $this->assertSame('barQueueName', $queues[1]->getClientName());
- $this->assertSame('aprefix.anappname.barqueuename', $queues[1]->getTransportName());
- }
-
- /**
- * @return Config
- */
- private function createConfig()
- {
- return new Config('aPrefix', 'anAppName', 'aRouterTopic', 'aRouterQueueName', 'aDefaultQueueName', 'aRouterProcessorName');
- }
-}
diff --git a/pkg/enqueue/Tests/Client/Meta/QueueMetaTest.php b/pkg/enqueue/Tests/Client/Meta/QueueMetaTest.php
deleted file mode 100644
index b184c06a6..000000000
--- a/pkg/enqueue/Tests/Client/Meta/QueueMetaTest.php
+++ /dev/null
@@ -1,38 +0,0 @@
-assertAttributeEquals('aClientName', 'clientName', $destination);
- $this->assertAttributeEquals('aTransportName', 'transportName', $destination);
- $this->assertAttributeEquals([], 'processors', $destination);
- }
-
- public function testShouldAllowGetClientNameSetInConstructor()
- {
- $destination = new QueueMeta('theClientName', 'aTransportName');
-
- $this->assertSame('theClientName', $destination->getClientName());
- }
-
- public function testShouldAllowGetTransportNameSetInConstructor()
- {
- $destination = new QueueMeta('aClientName', 'theTransportName');
-
- $this->assertSame('theTransportName', $destination->getTransportName());
- }
-
- public function testShouldAllowGetSubscribersSetInConstructor()
- {
- $destination = new QueueMeta('aClientName', 'aTransportName', ['aSubscriber']);
-
- $this->assertSame(['aSubscriber'], $destination->getProcessors());
- }
-}
diff --git a/pkg/enqueue/Tests/Client/Meta/TopicMetaRegistryTest.php b/pkg/enqueue/Tests/Client/Meta/TopicMetaRegistryTest.php
deleted file mode 100644
index 9f41e4dca..000000000
--- a/pkg/enqueue/Tests/Client/Meta/TopicMetaRegistryTest.php
+++ /dev/null
@@ -1,123 +0,0 @@
- [],
- 'anotherTopicName' => [],
- ];
-
- $registry = new TopicMetaRegistry($topics);
-
- $this->assertAttributeEquals($topics, 'meta', $registry);
- }
-
- public function testShouldAllowAddTopicMetaUsingAddMethod()
- {
- $registry = new TopicMetaRegistry([]);
-
- $registry->add('theFooTopicName', 'aDescription');
- $registry->add('theBarTopicName');
-
- $this->assertAttributeSame([
- 'theFooTopicName' => [
- 'description' => 'aDescription',
- 'processors' => [],
- ],
- 'theBarTopicName' => [
- 'description' => null,
- 'processors' => [],
- ],
- ], 'meta', $registry);
- }
-
- public function testShouldAllowAddSubscriber()
- {
- $registry = new TopicMetaRegistry([]);
-
- $registry->addProcessor('theFooTopicName', 'theFooProcessorName');
- $registry->addProcessor('theFooTopicName', 'theBarProcessorName');
- $registry->addProcessor('theBarTopicName', 'theBazProcessorName');
-
- $this->assertAttributeSame([
- 'theFooTopicName' => [
- 'description' => null,
- 'processors' => ['theFooProcessorName', 'theBarProcessorName'],
- ],
- 'theBarTopicName' => [
- 'description' => null,
- 'processors' => ['theBazProcessorName'],
- ],
- ], 'meta', $registry);
- }
-
- public function testThrowIfThereIsNotMetaForRequestedTopicName()
- {
- $registry = new TopicMetaRegistry([]);
-
- $this->setExpectedException(
- \InvalidArgumentException::class,
- 'The topic meta not found. Requested name `aName`'
- );
- $registry->getTopicMeta('aName');
- }
-
- public function testShouldAllowGetTopicByNameWithDefaultInfo()
- {
- $topics = [
- 'theTopicName' => [],
- ];
-
- $registry = new TopicMetaRegistry($topics);
-
- $topic = $registry->getTopicMeta('theTopicName');
- $this->assertInstanceOf(TopicMeta::class, $topic);
- $this->assertSame('theTopicName', $topic->getName());
- $this->assertSame('', $topic->getDescription());
- $this->assertSame([], $topic->getProcessors());
- }
-
- public function testShouldAllowGetTopicByNameWithCustomInfo()
- {
- $topics = [
- 'theTopicName' => ['description' => 'theDescription', 'processors' => ['theSubscriber']],
- ];
-
- $registry = new TopicMetaRegistry($topics);
-
- $topic = $registry->getTopicMeta('theTopicName');
- $this->assertInstanceOf(TopicMeta::class, $topic);
- $this->assertSame('theTopicName', $topic->getName());
- $this->assertSame('theDescription', $topic->getDescription());
- $this->assertSame(['theSubscriber'], $topic->getProcessors());
- }
-
- public function testShouldAllowGetAllTopics()
- {
- $topics = [
- 'fooTopicName' => [],
- 'barTopicName' => [],
- ];
-
- $registry = new TopicMetaRegistry($topics);
-
- $topics = $registry->getTopicsMeta();
- $this->assertInstanceOf(\Generator::class, $topics);
-
- $topics = iterator_to_array($topics);
- /* @var TopicMeta[] $topics */
-
- $this->assertContainsOnly(TopicMeta::class, $topics);
- $this->assertCount(2, $topics);
-
- $this->assertSame('fooTopicName', $topics[0]->getName());
- $this->assertSame('barTopicName', $topics[1]->getName());
- }
-}
diff --git a/pkg/enqueue/Tests/Client/Meta/TopicMetaTest.php b/pkg/enqueue/Tests/Client/Meta/TopicMetaTest.php
deleted file mode 100644
index ac5c2992d..000000000
--- a/pkg/enqueue/Tests/Client/Meta/TopicMetaTest.php
+++ /dev/null
@@ -1,56 +0,0 @@
-assertAttributeEquals('aName', 'name', $topic);
- $this->assertAttributeEquals('', 'description', $topic);
- $this->assertAttributeEquals([], 'processors', $topic);
- }
-
- public function testCouldBeConstructedWithNameAndDescriptionOnly()
- {
- $topic = new TopicMeta('aName', 'aDescription');
-
- $this->assertAttributeEquals('aName', 'name', $topic);
- $this->assertAttributeEquals('aDescription', 'description', $topic);
- $this->assertAttributeEquals([], 'processors', $topic);
- }
-
- public function testCouldBeConstructedWithNameAndDescriptionAndSubscribers()
- {
- $topic = new TopicMeta('aName', 'aDescription', ['aSubscriber']);
-
- $this->assertAttributeEquals('aName', 'name', $topic);
- $this->assertAttributeEquals('aDescription', 'description', $topic);
- $this->assertAttributeEquals(['aSubscriber'], 'processors', $topic);
- }
-
- public function testShouldAllowGetNameSetInConstructor()
- {
- $topic = new TopicMeta('theName', 'aDescription');
-
- $this->assertSame('theName', $topic->getName());
- }
-
- public function testShouldAllowGetDescriptionSetInConstructor()
- {
- $topic = new TopicMeta('aName', 'theDescription');
-
- $this->assertSame('theDescription', $topic->getDescription());
- }
-
- public function testShouldAllowGetSubscribersSetInConstructor()
- {
- $topic = new TopicMeta('aName', '', ['aSubscriber']);
-
- $this->assertSame(['aSubscriber'], $topic->getProcessors());
- }
-}
diff --git a/pkg/enqueue/Tests/Client/NullDriverTest.php b/pkg/enqueue/Tests/Client/NullDriverTest.php
deleted file mode 100644
index 00aee3e9c..000000000
--- a/pkg/enqueue/Tests/Client/NullDriverTest.php
+++ /dev/null
@@ -1,200 +0,0 @@
-createMessageProducer();
- $producer
- ->expects(self::once())
- ->method('send')
- ->with(self::identicalTo($topic), self::identicalTo($transportMessage))
- ;
-
- $context = $this->createContextMock();
- $context
- ->expects($this->once())
- ->method('createTopic')
- ->willReturn($topic)
- ;
- $context
- ->expects($this->once())
- ->method('createMessage')
- ->willReturn($transportMessage)
- ;
- $context
- ->expects($this->once())
- ->method('createProducer')
- ->willReturn($producer)
- ;
-
- $driver = new NullDriver($context, $config);
-
- $driver->sendToRouter(new Message());
- }
-
- public function testShouldSendMessageToProcessor()
- {
- $config = new Config('', '', '', '', '', '');
- $queue = new NullQueue('');
-
- $transportMessage = new NullMessage();
-
- $producer = $this->createMessageProducer();
- $producer
- ->expects(self::once())
- ->method('send')
- ->with(self::identicalTo($queue), self::identicalTo($transportMessage))
- ;
-
- $context = $this->createContextMock();
- $context
- ->expects($this->once())
- ->method('createQueue')
- ->willReturn($queue)
- ;
- $context
- ->expects($this->once())
- ->method('createMessage')
- ->willReturn($transportMessage)
- ;
- $context
- ->expects($this->once())
- ->method('createProducer')
- ->willReturn($producer)
- ;
-
- $driver = new NullDriver($context, $config);
-
- $driver->sendToProcessor(new Message());
- }
-
- public function testShouldConvertClientMessageToTransportMessage()
- {
- $config = new Config('', '', '', '', '', '');
-
- $message = new Message();
- $message->setBody('theBody');
- $message->setContentType('theContentType');
- $message->setMessageId('theMessageId');
- $message->setTimestamp(12345);
- $message->setDelay(123);
- $message->setExpire(345);
- $message->setPriority(MessagePriority::LOW);
- $message->setHeaders(['theHeaderFoo' => 'theFoo']);
- $message->setProperties(['thePropertyBar' => 'theBar']);
-
- $transportMessage = new NullMessage();
-
- $context = $this->createContextMock();
- $context
- ->expects($this->once())
- ->method('createMessage')
- ->willReturn($transportMessage)
- ;
-
- $driver = new NullDriver($context, $config);
-
- $transportMessage = $driver->createTransportMessage($message);
-
- self::assertSame('theBody', $transportMessage->getBody());
- self::assertSame([
- 'theHeaderFoo' => 'theFoo',
- 'content_type' => 'theContentType',
- 'expiration' => 345,
- 'delay' => 123,
- 'priority' => MessagePriority::LOW,
- 'timestamp' => 12345,
- 'message_id' => 'theMessageId',
- ], $transportMessage->getHeaders());
- self::assertSame([
- 'thePropertyBar' => 'theBar',
- ], $transportMessage->getProperties());
- }
-
- public function testShouldConvertTransportMessageToClientMessage()
- {
- $config = new Config('', '', '', '', '', '');
-
- $message = new NullMessage();
- $message->setBody('theBody');
- $message->setHeaders(['theHeaderFoo' => 'theFoo']);
- $message->setTimestamp(12345);
- $message->setMessageId('theMessageId');
- $message->setHeader('priority', MessagePriority::LOW);
- $message->setHeader('content_type', 'theContentType');
- $message->setHeader('delay', 123);
- $message->setHeader('expiration', 345);
- $message->setProperties(['thePropertyBar' => 'theBar']);
-
- $driver = new NullDriver($this->createContextMock(), $config);
-
- $clientMessage = $driver->createClientMessage($message);
-
- self::assertSame('theBody', $clientMessage->getBody());
- self::assertSame(MessagePriority::LOW, $clientMessage->getPriority());
- self::assertSame('theContentType', $clientMessage->getContentType());
- self::assertSame(123, $clientMessage->getDelay());
- self::assertSame(345, $clientMessage->getExpire());
- self::assertEquals([
- 'theHeaderFoo' => 'theFoo',
- 'content_type' => 'theContentType',
- 'expiration' => 345,
- 'delay' => 123,
- 'priority' => MessagePriority::LOW,
- 'timestamp' => 12345,
- 'message_id' => 'theMessageId',
- ], $clientMessage->getHeaders());
- self::assertSame([
- 'thePropertyBar' => 'theBar',
- ], $clientMessage->getProperties());
- }
-
- public function testShouldReturnConfigInstance()
- {
- $config = new Config('', '', '', '', '', '');
-
- $driver = new NullDriver($this->createContextMock(), $config);
- $result = $driver->getConfig();
-
- self::assertSame($config, $result);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|NullContext
- */
- private function createContextMock()
- {
- return $this->createMock(NullContext::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|NullProducer
- */
- private function createMessageProducer()
- {
- return $this->createMock(NullProducer::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Client/PostSendTest.php b/pkg/enqueue/Tests/Client/PostSendTest.php
new file mode 100644
index 000000000..ba51710e7
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/PostSendTest.php
@@ -0,0 +1,112 @@
+createProducerMock();
+ $expectedDriver = $this->createDriverMock();
+ $expectedDestination = $this->createDestinationMock();
+ $expectedTransportMessage = $this->createTransportMessageMock();
+
+ $context = new PostSend(
+ $expectedMessage,
+ $expectedProducer,
+ $expectedDriver,
+ $expectedDestination,
+ $expectedTransportMessage
+ );
+
+ $this->assertSame($expectedMessage, $context->getMessage());
+ $this->assertSame($expectedProducer, $context->getProducer());
+ $this->assertSame($expectedDriver, $context->getDriver());
+ $this->assertSame($expectedDestination, $context->getTransportDestination());
+ $this->assertSame($expectedTransportMessage, $context->getTransportMessage());
+ }
+
+ public function testShouldAllowGetCommand()
+ {
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'theCommand');
+
+ $context = new PostSend(
+ $message,
+ $this->createProducerMock(),
+ $this->createDriverMock(),
+ $this->createDestinationMock(),
+ $this->createTransportMessageMock()
+ );
+
+ $this->assertFalse($context->isEvent());
+ $this->assertSame('theCommand', $context->getCommand());
+ }
+
+ public function testShouldAllowGetTopic()
+ {
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'theTopic');
+
+ $context = new PostSend(
+ $message,
+ $this->createProducerMock(),
+ $this->createDriverMock(),
+ $this->createDestinationMock(),
+ $this->createTransportMessageMock()
+ );
+
+ $this->assertTrue($context->isEvent());
+ $this->assertSame('theTopic', $context->getTopic());
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverMock(): DriverInterface
+ {
+ return $this->createMock(DriverInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createProducerMock(): ProducerInterface
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|Destination
+ */
+ private function createDestinationMock(): Destination
+ {
+ return $this->createMock(Destination::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|TransportMessage
+ */
+ private function createTransportMessageMock(): TransportMessage
+ {
+ return $this->createMock(TransportMessage::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/PreSendTest.php b/pkg/enqueue/Tests/Client/PreSendTest.php
new file mode 100644
index 000000000..01a7e5055
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/PreSendTest.php
@@ -0,0 +1,116 @@
+createProducerMock();
+ $expectedDriver = $this->createDriverMock();
+
+ $context = new PreSend(
+ $expectedCommandOrTopic,
+ $expectedMessage,
+ $expectedProducer,
+ $expectedDriver
+ );
+
+ $this->assertSame($expectedCommandOrTopic, $context->getTopic());
+ $this->assertSame($expectedCommandOrTopic, $context->getCommand());
+ $this->assertSame($expectedMessage, $context->getMessage());
+ $this->assertSame($expectedProducer, $context->getProducer());
+ $this->assertSame($expectedDriver, $context->getDriver());
+
+ $this->assertEquals($expectedMessage, $context->getOriginalMessage());
+ $this->assertNotSame($expectedMessage, $context->getOriginalMessage());
+ }
+
+ public function testCouldChangeTopic()
+ {
+ $context = new PreSend(
+ 'aCommandOrTopic',
+ new Message(),
+ $this->createProducerMock(),
+ $this->createDriverMock()
+ );
+
+ // guard
+ $this->assertSame('aCommandOrTopic', $context->getTopic());
+
+ $context->changeTopic('theChangedTopic');
+
+ $this->assertSame('theChangedTopic', $context->getTopic());
+ }
+
+ public function testCouldChangeCommand()
+ {
+ $context = new PreSend(
+ 'aCommandOrTopic',
+ new Message(),
+ $this->createProducerMock(),
+ $this->createDriverMock()
+ );
+
+ // guard
+ $this->assertSame('aCommandOrTopic', $context->getCommand());
+
+ $context->changeCommand('theChangedCommand');
+
+ $this->assertSame('theChangedCommand', $context->getCommand());
+ }
+
+ public function testCouldChangeBody()
+ {
+ $context = new PreSend(
+ 'aCommandOrTopic',
+ new Message('aBody'),
+ $this->createProducerMock(),
+ $this->createDriverMock()
+ );
+
+ // guard
+ $this->assertSame('aBody', $context->getMessage()->getBody());
+ $this->assertNull($context->getMessage()->getContentType());
+
+ $context->changeBody('theChangedBody');
+ $this->assertSame('theChangedBody', $context->getMessage()->getBody());
+ $this->assertNull($context->getMessage()->getContentType());
+
+ $context->changeBody('theChangedBodyAgain', 'foo/bar');
+ $this->assertSame('theChangedBodyAgain', $context->getMessage()->getBody());
+ $this->assertSame('foo/bar', $context->getMessage()->getContentType());
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverMock(): DriverInterface
+ {
+ return $this->createMock(DriverInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createProducerMock(): ProducerInterface
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ProducerSendCommandTest.php b/pkg/enqueue/Tests/Client/ProducerSendCommandTest.php
new file mode 100644
index 000000000..9500e9d62
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ProducerSendCommandTest.php
@@ -0,0 +1,537 @@
+createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ $expectedProperties = [
+ 'enqueue.command' => 'command',
+ ];
+
+ self::assertEquals($expectedProperties, $message->getProperties());
+ }
+
+ public function testShouldSendCommandWithReply()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+
+ $expectedPromiseMock = $this->createMock(Promise::class);
+
+ $rpcFactoryMock = $this->createRpcFactoryMock();
+ $rpcFactoryMock
+ ->expects($this->once())
+ ->method('createReplyTo')
+ ->willReturn('theReplyQueue')
+ ;
+ $rpcFactoryMock
+ ->expects($this->once())
+ ->method('createPromise')
+ ->with(
+ 'theReplyQueue',
+ $this->logicalNot($this->isEmpty()),
+ 60000
+ )
+ ->willReturn($expectedPromiseMock)
+ ;
+
+ $producer = new Producer($driver, $rpcFactoryMock);
+ $actualPromise = $producer->sendCommand('command', $message, true);
+
+ $this->assertSame($expectedPromiseMock, $actualPromise);
+
+ self::assertEquals('theReplyQueue', $message->getReplyTo());
+ self::assertNotEmpty($message->getCorrelationId());
+ }
+
+ public function testShouldSendCommandWithReplyAndCustomReplyQueueAndCorrelationId()
+ {
+ $message = new Message();
+ $message->setReplyTo('theCustomReplyQueue');
+ $message->setCorrelationId('theCustomCorrelationId');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+
+ $expectedPromiseMock = $this->createMock(Promise::class);
+
+ $rpcFactoryMock = $this->createRpcFactoryMock();
+ $rpcFactoryMock
+ ->expects($this->never())
+ ->method('createReplyTo')
+ ;
+ $rpcFactoryMock
+ ->expects($this->once())
+ ->method('createPromise')
+ ->with(
+ 'theCustomReplyQueue',
+ 'theCustomCorrelationId',
+ 60000
+ )
+ ->willReturn($expectedPromiseMock)
+ ;
+
+ $producer = new Producer($driver, $rpcFactoryMock);
+ $actualPromise = $producer->sendCommand('command', $message, true);
+
+ $this->assertSame($expectedPromiseMock, $actualPromise);
+
+ self::assertEquals('theCustomReplyQueue', $message->getReplyTo());
+ self::assertSame('theCustomCorrelationId', $message->getCorrelationId());
+ }
+
+ public function testShouldOverwriteExpectedMessageProperties()
+ {
+ $message = new Message();
+ $message->setProperty(Config::COMMAND, 'commandShouldBeOverwritten');
+ $message->setScope('scopeShouldBeOverwritten');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('expectedCommand', $message);
+
+ $expectedProperties = [
+ 'enqueue.command' => 'expectedCommand',
+ ];
+
+ self::assertEquals($expectedProperties, $message->getProperties());
+ self::assertSame(Message::SCOPE_APP, $message->getScope());
+ }
+
+ public function testShouldSendCommandWithoutPriorityByDefault()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertNull($message->getPriority());
+ }
+
+ public function testShouldSendCommandWithCustomPriority()
+ {
+ $message = new Message();
+ $message->setPriority(MessagePriority::HIGH);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertSame(MessagePriority::HIGH, $message->getPriority());
+ }
+
+ public function testShouldSendCommandWithGeneratedMessageId()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertNotEmpty($message->getMessageId());
+ }
+
+ public function testShouldSendCommandWithCustomMessageId()
+ {
+ $message = new Message();
+ $message->setMessageId('theCustomMessageId');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertSame('theCustomMessageId', $message->getMessageId());
+ }
+
+ public function testShouldSendCommandWithGeneratedTimestamp()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertNotEmpty($message->getTimestamp());
+ }
+
+ public function testShouldSendCommandWithCustomTimestamp()
+ {
+ $message = new Message();
+ $message->setTimestamp('theCustomTimestamp');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+
+ self::assertSame('theCustomTimestamp', $message->getTimestamp());
+ }
+
+ public function testShouldSerializeMessageToJsonByDefault()
+ {
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) {
+ $this->assertSame('{"foo":"fooVal"}', $message->getBody());
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', ['foo' => 'fooVal']);
+ }
+
+ public function testShouldSerializeMessageByCustomExtension()
+ {
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) {
+ $this->assertSame('theCommandBodySerializedByCustomExtension', $message->getBody());
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), new ChainExtension([new CustomPrepareBodyClientExtension()]));
+ $producer->sendCommand('command', ['foo' => 'fooVal']);
+ }
+
+ public function testShouldSendCommandToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) {
+ self::assertSame('aBody', $message->getBody());
+ self::assertNull($message->getProperty(Config::PROCESSOR));
+ self::assertSame('command', $message->getProperty(Config::COMMAND));
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendCommand('command', $message);
+ }
+
+ public function testThrowWhenProcessorNamePropertySetToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+ $message->setProperty(Config::PROCESSOR, 'aCustomProcessor');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The enqueue.processor property must not be set.');
+ $producer->sendCommand('command', $message);
+ }
+
+ public function testShouldCallPreSendCommandExtensionMethodWhenSendToBus()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_MESSAGE_BUS);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPreSendCommand')
+ ->willReturnCallback(function (PreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('command', $context->getCommand());
+
+ $this->assertEquals($message, $context->getOriginalMessage());
+ $this->assertNotSame($message, $context->getOriginalMessage());
+ });
+
+ $extension
+ ->expects($this->never())
+ ->method('onPreSendEvent')
+ ;
+
+ $producer->sendCommand('command', $message);
+ }
+
+ public function testShouldCallPreSendCommandExtensionMethodWhenSendToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPreSendCommand')
+ ->willReturnCallback(function (PreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('command', $context->getCommand());
+
+ $this->assertEquals($message, $context->getOriginalMessage());
+ $this->assertNotSame($message, $context->getOriginalMessage());
+ });
+
+ $extension
+ ->expects($this->never())
+ ->method('onPreSendEvent')
+ ;
+
+ $producer->sendCommand('command', $message);
+ }
+
+ public function testShouldCallPreDriverSendExtensionMethod()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onDriverPreSend')
+ ->willReturnCallback(function (DriverPreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('command', $context->getCommand());
+
+ $this->assertTrue($context->isEvent());
+ });
+
+ $producer->sendCommand('command', $message);
+ }
+
+ public function testShouldCallPostSendExtensionMethod()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPostSend')
+ ->willReturnCallback(function (PostSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('command', $context->getCommand());
+
+ $this->assertFalse($context->isEvent());
+ });
+
+ $producer->sendCommand('command', $message);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createRpcFactoryMock(): RpcFactory
+ {
+ return $this->createMock(RpcFactory::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverStub(): DriverInterface
+ {
+ $config = new Config(
+ 'a_prefix',
+ '.',
+ 'an_app',
+ 'a_router_topic',
+ 'a_router_queue',
+ 'a_default_processor_queue',
+ 'a_router_processor_name',
+ [],
+ []
+ );
+
+ $driverMock = $this->createMock(DriverInterface::class);
+ $driverMock
+ ->expects($this->any())
+ ->method('getConfig')
+ ->willReturn($config)
+ ;
+
+ return $driverMock;
+ }
+
+ private function createDriverSendResult(): DriverSendResult
+ {
+ return new DriverSendResult(
+ $this->createMock(Destination::class),
+ $this->createMock(TransportMessage::class)
+ );
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ProducerSendEventTest.php b/pkg/enqueue/Tests/Client/ProducerSendEventTest.php
new file mode 100644
index 000000000..c92b49560
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ProducerSendEventTest.php
@@ -0,0 +1,557 @@
+createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ $expectedProperties = [
+ 'enqueue.topic' => 'topic',
+ ];
+
+ self::assertEquals($expectedProperties, $message->getProperties());
+ }
+
+ public function testShouldOverwriteTopicProperty()
+ {
+ $message = new Message();
+ $message->setProperty(Config::TOPIC, 'topicShouldBeOverwritten');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('expectedTopic', $message);
+
+ $expectedProperties = [
+ 'enqueue.topic' => 'expectedTopic',
+ ];
+
+ self::assertEquals($expectedProperties, $message->getProperties());
+ }
+
+ public function testShouldSendEventWithoutPriorityByDefault()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertNull($message->getPriority());
+ }
+
+ public function testShouldSendEventWithCustomPriority()
+ {
+ $message = new Message();
+ $message->setPriority(MessagePriority::HIGH);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertSame(MessagePriority::HIGH, $message->getPriority());
+ }
+
+ public function testShouldSendEventWithGeneratedMessageId()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertNotEmpty($message->getMessageId());
+ }
+
+ public function testShouldSendEventWithCustomMessageId()
+ {
+ $message = new Message();
+ $message->setMessageId('theCustomMessageId');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertSame('theCustomMessageId', $message->getMessageId());
+ }
+
+ public function testShouldSendEventWithGeneratedTimestamp()
+ {
+ $message = new Message();
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertNotEmpty($message->getTimestamp());
+ }
+
+ public function testShouldSendEventWithCustomTimestamp()
+ {
+ $message = new Message();
+ $message->setTimestamp('theCustomTimestamp');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->with(self::identicalTo($message))
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+
+ self::assertSame('theCustomTimestamp', $message->getTimestamp());
+ }
+
+ public function testShouldSerializeMessageToJsonByDefault()
+ {
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->willReturnCallback(function (Message $message) {
+ $this->assertSame('{"foo":"fooVal"}', $message->getBody());
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', ['foo' => 'fooVal']);
+ }
+
+ public function testShouldSerializeMessageByCustomExtension()
+ {
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->willReturnCallback(function (Message $message) {
+ $this->assertSame('theEventBodySerializedByCustomExtension', $message->getBody());
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), new ChainExtension([new CustomPrepareBodyClientExtension()]));
+ $producer->sendEvent('topic', ['foo' => 'fooVal']);
+ }
+
+ public function testThrowIfSendEventToMessageBusWithProcessorNamePropertySet()
+ {
+ $message = new Message();
+ $message->setBody('');
+ $message->setProperty(Config::PROCESSOR, 'aProcessor');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The enqueue.processor property must not be set.');
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldSendEventToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) {
+ self::assertSame('aBody', $message->getBody());
+
+ // null means a driver sends a message to router processor.
+ self::assertNull($message->getProperty(Config::PROCESSOR));
+
+ return $this->createDriverSendResult();
+ })
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testThrowWhenProcessorNamePropertySetToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+ $message->setProperty(Config::PROCESSOR, 'aCustomProcessor');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The enqueue.processor property must not be set.');
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testThrowIfUnSupportedScopeGivenOnSend()
+ {
+ $message = new Message();
+ $message->setScope('iDontKnowScope');
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->never())
+ ->method('sendToRouter')
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ;
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock());
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The message scope "iDontKnowScope" is not supported.');
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPreSendEventExtensionMethodWhenSendToBus()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_MESSAGE_BUS);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPreSendEvent')
+ ->willReturnCallback(function (PreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertEquals($message, $context->getOriginalMessage());
+ $this->assertNotSame($message, $context->getOriginalMessage());
+ });
+
+ $extension
+ ->expects($this->never())
+ ->method('onPreSendCommand')
+ ;
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPreSendEventExtensionMethodWhenSendToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPreSendEvent')
+ ->willReturnCallback(function (PreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertEquals($message, $context->getOriginalMessage());
+ $this->assertNotSame($message, $context->getOriginalMessage());
+ });
+
+ $extension
+ ->expects($this->never())
+ ->method('onPreSendCommand')
+ ;
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPreDriverSendExtensionMethodWhenSendToMessageBus()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_MESSAGE_BUS);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onDriverPreSend')
+ ->willReturnCallback(function (DriverPreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertTrue($context->isEvent());
+ });
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPreDriverSendExtensionMethodWhenSendToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onDriverPreSend')
+ ->willReturnCallback(function (DriverPreSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertTrue($context->isEvent());
+ });
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPostSendExtensionMethodWhenSendToMessageBus()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_MESSAGE_BUS);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToRouter')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPostSend')
+ ->willReturnCallback(function (PostSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertTrue($context->isEvent());
+ });
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ public function testShouldCallPostSendExtensionMethodWhenSendToApplicationRouter()
+ {
+ $message = new Message();
+ $message->setBody('aBody');
+ $message->setScope(Message::SCOPE_APP);
+
+ $driver = $this->createDriverStub();
+ $driver
+ ->expects($this->once())
+ ->method('sendToProcessor')
+ ->willReturn($this->createDriverSendResult())
+ ;
+
+ $extension = $this->createMock(ExtensionInterface::class);
+
+ $producer = new Producer($driver, $this->createRpcFactoryMock(), $extension);
+
+ $extension
+ ->expects($this->at(0))
+ ->method('onPostSend')
+ ->willReturnCallback(function (PostSend $context) use ($message, $producer, $driver) {
+ $this->assertSame($message, $context->getMessage());
+ $this->assertSame($producer, $context->getProducer());
+ $this->assertSame($driver, $context->getDriver());
+ $this->assertSame('topic', $context->getTopic());
+
+ $this->assertTrue($context->isEvent());
+ });
+
+ $producer->sendEvent('topic', $message);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createRpcFactoryMock(): RpcFactory
+ {
+ return $this->createMock(RpcFactory::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverStub(): DriverInterface
+ {
+ $config = new Config(
+ 'a_prefix',
+ '.',
+ 'an_app',
+ 'a_router_topic',
+ 'a_router_queue',
+ 'a_default_processor_queue',
+ 'a_router_processor_name',
+ [],
+ []
+ );
+
+ $driverMock = $this->createMock(DriverInterface::class);
+ $driverMock
+ ->expects($this->any())
+ ->method('getConfig')
+ ->willReturn($config)
+ ;
+
+ return $driverMock;
+ }
+
+ private function createDriverSendResult(): DriverSendResult
+ {
+ return new DriverSendResult(
+ $this->createMock(Destination::class),
+ $this->createMock(TransportMessage::class)
+ );
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ProducerTest.php b/pkg/enqueue/Tests/Client/ProducerTest.php
new file mode 100644
index 000000000..23b004ac3
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ProducerTest.php
@@ -0,0 +1,41 @@
+createMock(RpcFactory::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createDriverMock(): DriverInterface
+ {
+ return $this->createMock(DriverInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/ResourcesTest.php b/pkg/enqueue/Tests/Client/ResourcesTest.php
new file mode 100644
index 000000000..e79fb9dda
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/ResourcesTest.php
@@ -0,0 +1,159 @@
+assertTrue($rc->isFinal());
+ }
+
+ public function testShouldConstructorBePrivate()
+ {
+ $rc = new \ReflectionClass(Resources::class);
+
+ $this->assertTrue($rc->getConstructor()->isPrivate());
+ }
+
+ public function testShouldGetAvailableDriverInExpectedFormat()
+ {
+ $availableDrivers = Resources::getAvailableDrivers();
+
+ self::assertIsArray($availableDrivers);
+ $this->assertGreaterThan(0, count($availableDrivers));
+
+ $driverInfo = $availableDrivers[0];
+
+ $this->assertArrayHasKey('driverClass', $driverInfo);
+ $this->assertSame(AmqpDriver::class, $driverInfo['driverClass']);
+
+ $this->assertArrayHasKey('schemes', $driverInfo);
+ $this->assertSame(['amqp', 'amqps'], $driverInfo['schemes']);
+
+ $this->assertArrayHasKey('requiredSchemeExtensions', $driverInfo);
+ $this->assertSame([], $driverInfo['requiredSchemeExtensions']);
+
+ $this->assertArrayHasKey('packages', $driverInfo);
+ $this->assertSame(['enqueue/enqueue', 'enqueue/amqp-bunny'], $driverInfo['packages']);
+ }
+
+ public function testShouldGetAvailableDriverWithRequiredExtensionInExpectedFormat()
+ {
+ $availableDrivers = Resources::getAvailableDrivers();
+
+ self::assertIsArray($availableDrivers);
+ $this->assertGreaterThan(0, count($availableDrivers));
+
+ $driverInfo = $availableDrivers[1];
+
+ $this->assertArrayHasKey('driverClass', $driverInfo);
+ $this->assertSame(RabbitMqDriver::class, $driverInfo['driverClass']);
+
+ $this->assertArrayHasKey('schemes', $driverInfo);
+ $this->assertSame(['amqp', 'amqps'], $driverInfo['schemes']);
+
+ $this->assertArrayHasKey('requiredSchemeExtensions', $driverInfo);
+ $this->assertSame(['rabbitmq'], $driverInfo['requiredSchemeExtensions']);
+
+ $this->assertArrayHasKey('packages', $driverInfo);
+ $this->assertSame(['enqueue/enqueue', 'enqueue/amqp-bunny'], $driverInfo['packages']);
+ }
+
+ public function testShouldGetKnownDriversInExpectedFormat()
+ {
+ $knownDrivers = Resources::getAvailableDrivers();
+
+ self::assertIsArray($knownDrivers);
+ $this->assertGreaterThan(0, count($knownDrivers));
+
+ $driverInfo = $knownDrivers[0];
+
+ $this->assertArrayHasKey('driverClass', $driverInfo);
+ $this->assertSame(AmqpDriver::class, $driverInfo['driverClass']);
+
+ $this->assertArrayHasKey('schemes', $driverInfo);
+ $this->assertSame(['amqp', 'amqps'], $driverInfo['schemes']);
+
+ $this->assertArrayHasKey('requiredSchemeExtensions', $driverInfo);
+ $this->assertSame([], $driverInfo['requiredSchemeExtensions']);
+
+ $this->assertArrayHasKey('packages', $driverInfo);
+ $this->assertSame(['enqueue/enqueue', 'enqueue/amqp-bunny'], $driverInfo['packages']);
+ }
+
+ public function testThrowsIfDriverClassNotImplementDriverFactoryInterfaceOnAddDriver()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The driver class "stdClass" must implement "Enqueue\Client\DriverInterface" interface.');
+
+ Resources::addDriver(\stdClass::class, [], [], ['foo']);
+ }
+
+ public function testThrowsIfNoSchemesProvidedOnAddDriver()
+ {
+ $driverClass = $this->getMockClass(DriverInterface::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Schemes could not be empty.');
+
+ Resources::addDriver($driverClass, [], [], ['foo']);
+ }
+
+ public function testThrowsIfNoPackageProvidedOnAddDriver()
+ {
+ $driverClass = $this->getMockClass(DriverInterface::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Packages could not be empty.');
+
+ Resources::addDriver($driverClass, ['foo'], [], []);
+ }
+
+ public function testShouldAllowRegisterDriverThatIsNotInstalled()
+ {
+ Resources::addDriver('theDriverClass', ['foo'], ['barExtension'], ['foo']);
+
+ $availableDrivers = Resources::getKnownDrivers();
+
+ $driverInfo = end($availableDrivers);
+
+ $this->assertSame('theDriverClass', $driverInfo['driverClass']);
+ }
+
+ public function testShouldAllowGetPreviouslyRegisteredDriver()
+ {
+ $driverClass = $this->getMockClass(DriverInterface::class);
+
+ Resources::addDriver(
+ $driverClass,
+ ['fooscheme', 'barscheme'],
+ ['fooextension', 'barextension'],
+ ['foo/bar']
+ );
+
+ $availableDrivers = Resources::getAvailableDrivers();
+
+ $driverInfo = end($availableDrivers);
+
+ $this->assertArrayHasKey('driverClass', $driverInfo);
+ $this->assertSame($driverClass, $driverInfo['driverClass']);
+
+ $this->assertArrayHasKey('schemes', $driverInfo);
+ $this->assertSame(['fooscheme', 'barscheme'], $driverInfo['schemes']);
+
+ $this->assertArrayHasKey('requiredSchemeExtensions', $driverInfo);
+ $this->assertSame(['fooextension', 'barextension'], $driverInfo['requiredSchemeExtensions']);
+
+ $this->assertArrayHasKey('packages', $driverInfo);
+ $this->assertSame(['foo/bar'], $driverInfo['packages']);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/RouterProcessorTest.php b/pkg/enqueue/Tests/Client/RouterProcessorTest.php
index 498e31fb5..7d2971189 100644
--- a/pkg/enqueue/Tests/Client/RouterProcessorTest.php
+++ b/pkg/enqueue/Tests/Client/RouterProcessorTest.php
@@ -4,105 +4,218 @@
use Enqueue\Client\Config;
use Enqueue\Client\DriverInterface;
+use Enqueue\Client\DriverSendResult;
use Enqueue\Client\Message;
+use Enqueue\Client\Route;
+use Enqueue\Client\RouteCollection;
use Enqueue\Client\RouterProcessor;
use Enqueue\Consumption\Result;
-use Enqueue\Transport\Null\NullContext;
-use Enqueue\Transport\Null\NullMessage;
-
-class RouterProcessorTest extends \PHPUnit_Framework_TestCase
+use Enqueue\Null\NullContext;
+use Enqueue\Null\NullMessage;
+use Enqueue\Test\ClassExtensionTrait;
+use Enqueue\Test\ReadAttributeTrait;
+use Interop\Queue\Destination;
+use Interop\Queue\Message as TransportMessage;
+use Interop\Queue\Processor;
+use PHPUnit\Framework\TestCase;
+
+class RouterProcessorTest extends TestCase
{
- public function testCouldBeConstructedWithDriverAsFirstArgument()
+ use ClassExtensionTrait;
+ use ReadAttributeTrait;
+
+ public function testShouldImplementProcessorInterface()
+ {
+ $this->assertClassImplements(Processor::class, RouterProcessor::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(RouterProcessor::class);
+ }
+
+ public function testCouldBeConstructedWithDriver()
{
- new RouterProcessor($this->createDriverMock());
+ $driver = $this->createDriverStub();
+
+ $processor = new RouterProcessor($driver);
+
+ $this->assertAttributeSame($driver, 'driver', $processor);
}
- public function testCouldBeConstructedWithSessionAndRoutes()
+ public function testShouldRejectIfTopicNotSet()
{
- $routes = [
- 'aTopicName' => [['aProcessorName', 'aQueueName']],
- 'anotherTopicName' => [['aProcessorName', 'aQueueName']],
- ];
+ $router = new RouterProcessor($this->createDriverStub());
- $router = new RouterProcessor($this->createDriverMock(), $routes);
+ $result = $router->process(new NullMessage(), new NullContext());
- $this->assertAttributeEquals($routes, 'routes', $router);
+ $this->assertEquals(Result::REJECT, $result->getStatus());
+ $this->assertEquals('Topic property "enqueue.topic" is required but not set or empty.', $result->getReason());
}
- public function testShouldThrowExceptionIfTopicNameParameterIsNotSet()
+ public function testShouldRejectIfCommandSet()
{
- $router = new RouterProcessor($this->createDriverMock());
+ $router = new RouterProcessor($this->createDriverStub());
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Got message without required parameter: "enqueue.topic_name"');
+ $message = new NullMessage();
+ $message->setProperty(Config::COMMAND, 'aCommand');
- $router->process(new NullMessage(), new NullContext());
+ $result = $router->process($message, new NullContext());
+
+ $this->assertEquals(Result::REJECT, $result->getStatus());
+ $this->assertEquals('Unexpected command "aCommand" got. Command must not go to the router.', $result->getReason());
}
- public function testShouldRouteOriginalMessageToRecipient()
+ public function testShouldRouteOriginalMessageToAllRecipients()
{
$message = new NullMessage();
$message->setBody('theBody');
$message->setHeaders(['aHeader' => 'aHeaderVal']);
- $message->setProperties(['aProp' => 'aPropVal', Config::PARAMETER_TOPIC_NAME => 'theTopicName']);
+ $message->setProperties(['aProp' => 'aPropVal', Config::TOPIC => 'theTopicName']);
- $clientMessage = new Message();
+ /** @var Message[] $routedMessages */
+ $routedMessages = new \ArrayObject();
- $routedMessage = null;
+ $routeCollection = new RouteCollection([
+ new Route('theTopicName', Route::TOPIC, 'aFooProcessor'),
+ new Route('theTopicName', Route::TOPIC, 'aBarProcessor'),
+ new Route('theTopicName', Route::TOPIC, 'aBazProcessor'),
+ ]);
- $driver = $this->createDriverMock();
+ $driver = $this->createDriverStub($routeCollection);
$driver
- ->expects($this->once())
+ ->expects($this->exactly(3))
->method('sendToProcessor')
- ->with($this->identicalTo($clientMessage))
+ ->willReturnCallback(function (Message $message) use ($routedMessages) {
+ $routedMessages->append($message);
+
+ return $this->createDriverSendResult();
+ })
;
$driver
- ->expects($this->once())
+ ->expects($this->exactly(3))
->method('createClientMessage')
- ->willReturnCallback(function (NullMessage $message) use (&$routedMessage, $clientMessage) {
- $routedMessage = $message;
+ ->willReturnCallback(function (NullMessage $message) {
+ return new Message($message->getBody(), $message->getProperties(), $message->getHeaders());
+ })
+ ;
+
+ $processor = new RouterProcessor($driver);
+
+ $result = $processor->process($message, new NullContext());
+
+ $this->assertEquals(Result::ACK, $result->getStatus());
+ $this->assertEquals('Routed to "3" event subscribers', $result->getReason());
+
+ $this->assertContainsOnly(Message::class, $routedMessages);
+ $this->assertCount(3, $routedMessages);
+
+ $this->assertSame('aFooProcessor', $routedMessages[0]->getProperty(Config::PROCESSOR));
+ $this->assertSame('aBarProcessor', $routedMessages[1]->getProperty(Config::PROCESSOR));
+ $this->assertSame('aBazProcessor', $routedMessages[2]->getProperty(Config::PROCESSOR));
+ }
+
+ public function testShouldDoNothingIfNoRoutes()
+ {
+ $message = new NullMessage();
+ $message->setBody('theBody');
+ $message->setHeaders(['aHeader' => 'aHeaderVal']);
+ $message->setProperties(['aProp' => 'aPropVal', Config::TOPIC => 'theTopicName']);
- return $clientMessage;
+ /** @var Message[] $routedMessages */
+ $routedMessages = new \ArrayObject();
+
+ $routeCollection = new RouteCollection([]);
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->never())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) use ($routedMessages) {
+ $routedMessages->append($message);
+ })
+ ;
+ $driver
+ ->expects($this->never())
+ ->method('createClientMessage')
+ ->willReturnCallback(function (NullMessage $message) {
+ return new Message($message->getBody(), $message->getProperties(), $message->getHeaders());
})
;
- $routes = [
- 'theTopicName' => [['aFooProcessor', 'aQueueName']],
- ];
+ $processor = new RouterProcessor($driver);
- $router = new RouterProcessor($driver, $routes);
+ $result = $processor->process($message, new NullContext());
- $result = $router->process($message, new NullContext());
+ $this->assertEquals(Result::ACK, $result->getStatus());
+ $this->assertEquals('Routed to "0" event subscribers', $result->getReason());
- $this->assertEquals(Result::ACK, $result);
- $this->assertEquals([
- 'aProp' => 'aPropVal',
- 'enqueue.topic_name' => 'theTopicName',
- 'enqueue.processor_name' => 'aFooProcessor',
- 'enqueue.processor_queue_name' => 'aQueueName',
- ], $routedMessage->getProperties());
+ $this->assertCount(0, $routedMessages);
}
- public function testShouldAddRoute()
+ public function testShouldDoNotModifyOriginalMessage()
{
- $router = new RouterProcessor($this->createDriverMock(), []);
+ $message = new NullMessage();
+ $message->setBody('theBody');
+ $message->setHeaders(['aHeader' => 'aHeaderVal']);
+ $message->setProperties(['aProp' => 'aPropVal', Config::TOPIC => 'theTopicName']);
- $this->assertAttributeSame([], 'routes', $router);
+ /** @var Message[] $routedMessages */
+ $routedMessages = new \ArrayObject();
- $router->add('theTopicName', 'theQueueName', 'aProcessorName');
+ $routeCollection = new RouteCollection([
+ new Route('theTopicName', Route::TOPIC, 'aFooProcessor'),
+ new Route('theTopicName', Route::TOPIC, 'aBarProcessor'),
+ ]);
- $this->assertAttributeSame([
- 'theTopicName' => [
- ['aProcessorName', 'theQueueName'],
- ],
- ], 'routes', $router);
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->atLeastOnce())
+ ->method('sendToProcessor')
+ ->willReturnCallback(function (Message $message) use ($routedMessages) {
+ $routedMessages->append($message);
+
+ return $this->createDriverSendResult();
+ });
+ $driver
+ ->expects($this->atLeastOnce())
+ ->method('createClientMessage')
+ ->willReturnCallback(function (NullMessage $message) {
+ return new Message($message->getBody(), $message->getProperties(), $message->getHeaders());
+ });
+
+ $processor = new RouterProcessor($driver);
+
+ $result = $processor->process($message, new NullContext());
+
+ // guard
+ $this->assertEquals(Result::ACK, $result->getStatus());
+
+ $this->assertSame('theBody', $message->getBody());
+ $this->assertSame(['aProp' => 'aPropVal', Config::TOPIC => 'theTopicName'], $message->getProperties());
+ $this->assertSame(['aHeader' => 'aHeaderVal'], $message->getHeaders());
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
+ * @return \PHPUnit\Framework\MockObject\MockObject|DriverInterface
*/
- protected function createDriverMock()
+ private function createDriverStub(?RouteCollection $routeCollection = null): DriverInterface
+ {
+ $driver = $this->createMock(DriverInterface::class);
+ $driver
+ ->expects($this->any())
+ ->method('getRouteCollection')
+ ->willReturn($routeCollection ?? new RouteCollection([]))
+ ;
+
+ return $driver;
+ }
+
+ private function createDriverSendResult(): DriverSendResult
{
- return $this->createMock(DriverInterface::class);
+ return new DriverSendResult(
+ $this->createMock(Destination::class),
+ $this->createMock(TransportMessage::class)
+ );
}
}
diff --git a/pkg/enqueue/Tests/Client/SpoolProducerTest.php b/pkg/enqueue/Tests/Client/SpoolProducerTest.php
new file mode 100644
index 000000000..014fe4962
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/SpoolProducerTest.php
@@ -0,0 +1,158 @@
+createProducerMock();
+ $realProducer
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $realProducer
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $producer = new SpoolProducer($realProducer);
+ $producer->sendEvent('foo_topic', $message);
+ $producer->sendEvent('bar_topic', $message);
+ }
+
+ public function testShouldQueueCommandMessageOnSend()
+ {
+ $message = new Message();
+
+ $realProducer = $this->createProducerMock();
+ $realProducer
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $realProducer
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $producer = new SpoolProducer($realProducer);
+ $producer->sendCommand('foo_command', $message);
+ $producer->sendCommand('bar_command', $message);
+ }
+
+ public function testShouldSendQueuedEventMessagesOnFlush()
+ {
+ $message = new Message();
+ $message->setScope('third');
+
+ $realProducer = $this->createProducerMock();
+ $realProducer
+ ->expects($this->at(0))
+ ->method('sendEvent')
+ ->with('foo_topic', 'first')
+ ;
+ $realProducer
+ ->expects($this->at(1))
+ ->method('sendEvent')
+ ->with('bar_topic', ['second'])
+ ;
+ $realProducer
+ ->expects($this->at(2))
+ ->method('sendEvent')
+ ->with('baz_topic', $this->identicalTo($message))
+ ;
+ $realProducer
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $producer = new SpoolProducer($realProducer);
+
+ $producer->sendEvent('foo_topic', 'first');
+ $producer->sendEvent('bar_topic', ['second']);
+ $producer->sendEvent('baz_topic', $message);
+
+ $producer->flush();
+ }
+
+ public function testShouldSendQueuedCommandMessagesOnFlush()
+ {
+ $message = new Message();
+ $message->setScope('third');
+
+ $realProducer = $this->createProducerMock();
+ $realProducer
+ ->expects($this->at(0))
+ ->method('sendCommand')
+ ->with('foo_command', 'first')
+ ;
+ $realProducer
+ ->expects($this->at(1))
+ ->method('sendCommand')
+ ->with('bar_command', ['second'])
+ ;
+ $realProducer
+ ->expects($this->at(2))
+ ->method('sendCommand')
+ ->with('baz_command', $this->identicalTo($message))
+ ;
+
+ $producer = new SpoolProducer($realProducer);
+
+ $producer->sendCommand('foo_command', 'first');
+ $producer->sendCommand('bar_command', ['second']);
+ $producer->sendCommand('baz_command', $message);
+
+ $producer->flush();
+ }
+
+ public function testShouldSendImmediatelyCommandMessageWithNeedReplyTrue()
+ {
+ $message = new Message();
+ $message->setScope('third');
+
+ $promise = $this->createMock(Promise::class);
+
+ $realProducer = $this->createProducerMock();
+ $realProducer
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $realProducer
+ ->expects($this->once())
+ ->method('sendCommand')
+ ->with('foo_command', 'first')
+ ->willReturn($promise)
+ ;
+
+ $producer = new SpoolProducer($realProducer);
+
+ $actualPromise = $producer->sendCommand('foo_command', 'first', true);
+
+ $this->assertSame($promise, $actualPromise);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|ProducerInterface
+ */
+ protected function createProducerMock()
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Client/TraceableMessageProducerTest.php b/pkg/enqueue/Tests/Client/TraceableMessageProducerTest.php
deleted file mode 100644
index 683b4dc80..000000000
--- a/pkg/enqueue/Tests/Client/TraceableMessageProducerTest.php
+++ /dev/null
@@ -1,200 +0,0 @@
-assertClassImplements(MessageProducerInterface::class, TraceableMessageProducer::class);
- }
-
- public function testCouldBeConstructedWithInternalMessageProducer()
- {
- new TraceableMessageProducer($this->createMessageProducer());
- }
-
- public function testShouldPassAllArgumentsToInternalMessageProducerSendMethod()
- {
- $topic = 'theTopic';
- $body = 'theBody';
-
- $internalMessageProducer = $this->createMessageProducer();
- $internalMessageProducer
- ->expects($this->once())
- ->method('send')
- ->with($topic, $body)
- ;
-
- $messageProducer = new TraceableMessageProducer($internalMessageProducer);
-
- $messageProducer->send($topic, $body);
- }
-
- public function testShouldCollectInfoIfStringGivenAsMessage()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', 'aFooBody');
-
- $this->assertSame([
- [
- 'topic' => 'aFooTopic',
- 'body' => 'aFooBody',
- 'headers' => [],
- 'properties' => [],
- 'priority' => null,
- 'expire' => null,
- 'delay' => null,
- 'timestamp' => null,
- 'contentType' => null,
- 'messageId' => null,
- ],
- ], $messageProducer->getTraces());
- }
-
- public function testShouldCollectInfoIfArrayGivenAsMessage()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', ['foo' => 'fooVal', 'bar' => 'barVal']);
-
- $this->assertSame([
- [
- 'topic' => 'aFooTopic',
- 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
- 'headers' => [],
- 'properties' => [],
- 'priority' => null,
- 'expire' => null,
- 'delay' => null,
- 'timestamp' => null,
- 'contentType' => null,
- 'messageId' => null,
- ],
- ], $messageProducer->getTraces());
- }
-
- public function testShouldCollectInfoIfMessageObjectGivenAsMessage()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $message = new Message();
- $message->setBody(['foo' => 'fooVal', 'bar' => 'barVal']);
- $message->setProperty('fooProp', 'fooVal');
- $message->setHeader('fooHeader', 'fooVal');
- $message->setContentType('theContentType');
- $message->setDelay('theDelay');
- $message->setExpire('theExpire');
- $message->setMessageId('theMessageId');
- $message->setPriority('theMessagePriority');
- $message->setTimestamp('theTimestamp');
-
- $messageProducer->send('aFooTopic', $message);
-
- $this->assertSame([
- [
- 'topic' => 'aFooTopic',
- 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
- 'headers' => ['fooHeader' => 'fooVal'],
- 'properties' => ['fooProp' => 'fooVal'],
- 'priority' => 'theMessagePriority',
- 'expire' => 'theExpire',
- 'delay' => 'theDelay',
- 'timestamp' => 'theTimestamp',
- 'contentType' => 'theContentType',
- 'messageId' => 'theMessageId',
- ],
- ], $messageProducer->getTraces());
- }
-
- public function testShouldAllowGetInfoSentToSameTopic()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', 'aFooBody');
- $messageProducer->send('aFooTopic', 'aFooBody');
-
- $this->assertArraySubset([
- ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
- ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
- ], $messageProducer->getTraces());
- }
-
- public function testShouldAllowGetInfoSentToDifferentTopics()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', 'aFooBody');
- $messageProducer->send('aBarTopic', 'aBarBody');
-
- $this->assertArraySubset([
- ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
- ['topic' => 'aBarTopic', 'body' => 'aBarBody'],
- ], $messageProducer->getTraces());
- }
-
- public function testShouldAllowGetInfoSentToSpecialTopicTopics()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', 'aFooBody');
- $messageProducer->send('aBarTopic', 'aBarBody');
-
- $this->assertArraySubset([
- ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
- ], $messageProducer->getTopicTraces('aFooTopic'));
-
- $this->assertArraySubset([
- ['topic' => 'aBarTopic', 'body' => 'aBarBody'],
- ], $messageProducer->getTopicTraces('aBarTopic'));
- }
-
- public function testShouldNotStoreAnythingIfInternalMessageProducerThrowsException()
- {
- $internalMessageProducer = $this->createMessageProducer();
- $internalMessageProducer
- ->expects($this->once())
- ->method('send')
- ->willThrowException(new \Exception())
- ;
-
- $messageProducer = new TraceableMessageProducer($internalMessageProducer);
-
- $this->expectException(\Exception::class);
-
- try {
- $messageProducer->send('aFooTopic', 'aFooBody');
- } finally {
- $this->assertEmpty($messageProducer->getTraces());
- }
- }
-
- public function testShouldAllowClearStoredTraces()
- {
- $messageProducer = new TraceableMessageProducer($this->createMessageProducer());
-
- $messageProducer->send('aFooTopic', 'aFooBody');
-
- //guard
- $this->assertNotEmpty($messageProducer->getTraces());
-
- $messageProducer->clearTraces();
- $this->assertSame([], $messageProducer->getTraces());
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|MessageProducerInterface
- */
- protected function createMessageProducer()
- {
- return $this->createMock(MessageProducerInterface::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Client/TraceableProducerTest.php b/pkg/enqueue/Tests/Client/TraceableProducerTest.php
new file mode 100644
index 000000000..b0df066ce
--- /dev/null
+++ b/pkg/enqueue/Tests/Client/TraceableProducerTest.php
@@ -0,0 +1,371 @@
+assertClassImplements(ProducerInterface::class, TraceableProducer::class);
+ }
+
+ public function testShouldPassAllArgumentsToInternalEventMessageProducerSendMethod()
+ {
+ $topic = 'theTopic';
+ $body = 'theBody';
+
+ $internalMessageProducer = $this->createProducerMock();
+ $internalMessageProducer
+ ->expects($this->once())
+ ->method('sendEvent')
+ ->with($topic, $body)
+ ;
+
+ $producer = new TraceableProducer($internalMessageProducer);
+
+ $producer->sendEvent($topic, $body);
+ }
+
+ public function testShouldCollectInfoIfStringGivenAsEventMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => 'aFooTopic',
+ 'command' => null,
+ 'body' => 'aFooBody',
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldCollectInfoIfArrayGivenAsEventMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', ['foo' => 'fooVal', 'bar' => 'barVal']);
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => 'aFooTopic',
+ 'command' => null,
+ 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldCollectInfoIfEventMessageObjectGivenAsMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $message = new Message();
+ $message->setBody(['foo' => 'fooVal', 'bar' => 'barVal']);
+ $message->setProperty('fooProp', 'fooVal');
+ $message->setHeader('fooHeader', 'fooVal');
+ $message->setContentType('theContentType');
+ $message->setDelay('theDelay');
+ $message->setExpire('theExpire');
+ $message->setMessageId('theMessageId');
+ $message->setPriority('theMessagePriority');
+ $message->setTimestamp('theTimestamp');
+
+ $producer->sendEvent('aFooTopic', $message);
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => 'aFooTopic',
+ 'command' => null,
+ 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
+ 'headers' => ['fooHeader' => 'fooVal'],
+ 'properties' => ['fooProp' => 'fooVal'],
+ 'priority' => 'theMessagePriority',
+ 'expire' => 'theExpire',
+ 'delay' => 'theDelay',
+ 'timestamp' => 'theTimestamp',
+ 'contentType' => 'theContentType',
+ 'messageId' => 'theMessageId',
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldNotStoreAnythingIfInternalEventMessageProducerThrowsException()
+ {
+ $internalMessageProducer = $this->createProducerMock();
+ $internalMessageProducer
+ ->expects($this->once())
+ ->method('sendEvent')
+ ->willThrowException(new \Exception())
+ ;
+
+ $producer = new TraceableProducer($internalMessageProducer);
+
+ $this->expectException(\Exception::class);
+
+ try {
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+ } finally {
+ $this->assertEmpty($producer->getTraces());
+ }
+ }
+
+ public function testShouldPassAllArgumentsToInternalCommandMessageProducerSendMethod()
+ {
+ $command = 'theCommand';
+ $body = 'theBody';
+
+ $internalMessageProducer = $this->createProducerMock();
+ $internalMessageProducer
+ ->expects($this->once())
+ ->method('sendCommand')
+ ->with($command, $body)
+ ;
+
+ $producer = new TraceableProducer($internalMessageProducer);
+
+ $producer->sendCommand($command, $body);
+ }
+
+ public function testShouldCollectInfoIfStringGivenAsCommandMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => null,
+ 'command' => 'aFooCommand',
+ 'body' => 'aFooBody',
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldCollectInfoIfArrayGivenAsCommandMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendCommand('aFooCommand', ['foo' => 'fooVal', 'bar' => 'barVal']);
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => null,
+ 'command' => 'aFooCommand',
+ 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
+ 'headers' => [],
+ 'properties' => [],
+ 'priority' => null,
+ 'expire' => null,
+ 'delay' => null,
+ 'timestamp' => null,
+ 'contentType' => null,
+ 'messageId' => null,
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldCollectInfoIfCommandMessageObjectGivenAsMessage()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $message = new Message();
+ $message->setBody(['foo' => 'fooVal', 'bar' => 'barVal']);
+ $message->setProperty('fooProp', 'fooVal');
+ $message->setHeader('fooHeader', 'fooVal');
+ $message->setContentType('theContentType');
+ $message->setDelay('theDelay');
+ $message->setExpire('theExpire');
+ $message->setMessageId('theMessageId');
+ $message->setPriority('theMessagePriority');
+ $message->setTimestamp('theTimestamp');
+
+ $producer->sendCommand('aFooCommand', $message);
+
+ Assert::assertArraySubset([
+ [
+ 'topic' => null,
+ 'command' => 'aFooCommand',
+ 'body' => ['foo' => 'fooVal', 'bar' => 'barVal'],
+ 'headers' => ['fooHeader' => 'fooVal'],
+ 'properties' => ['fooProp' => 'fooVal'],
+ 'priority' => 'theMessagePriority',
+ 'expire' => 'theExpire',
+ 'delay' => 'theDelay',
+ 'timestamp' => 'theTimestamp',
+ 'contentType' => 'theContentType',
+ 'messageId' => 'theMessageId',
+ ],
+ ], $producer->getTraces());
+
+ $this->assertArrayHasKey('sentAt', $producer->getTraces()[0]);
+ }
+
+ public function testShouldNotStoreAnythingIfInternalCommandMessageProducerThrowsException()
+ {
+ $internalMessageProducer = $this->createProducerMock();
+ $internalMessageProducer
+ ->expects($this->once())
+ ->method('sendCommand')
+ ->willThrowException(new \Exception())
+ ;
+
+ $producer = new TraceableProducer($internalMessageProducer);
+
+ $this->expectException(\Exception::class);
+
+ try {
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+ } finally {
+ $this->assertEmpty($producer->getTraces());
+ }
+ }
+
+ public function testShouldAllowGetInfoSentToSameTopic()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+
+ Assert::assertArraySubset([
+ ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
+ ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
+ ], $producer->getTraces());
+ }
+
+ public function testShouldAllowGetInfoSentToDifferentTopics()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+ $producer->sendEvent('aBarTopic', 'aBarBody');
+
+ Assert::assertArraySubset([
+ ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
+ ['topic' => 'aBarTopic', 'body' => 'aBarBody'],
+ ], $producer->getTraces());
+ }
+
+ public function testShouldAllowGetInfoSentToSpecialTopic()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+ $producer->sendEvent('aBarTopic', 'aBarBody');
+
+ Assert::assertArraySubset([
+ ['topic' => 'aFooTopic', 'body' => 'aFooBody'],
+ ], $producer->getTopicTraces('aFooTopic'));
+
+ Assert::assertArraySubset([
+ ['topic' => 'aBarTopic', 'body' => 'aBarBody'],
+ ], $producer->getTopicTraces('aBarTopic'));
+ }
+
+ public function testShouldAllowGetInfoSentToSameCommand()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+
+ Assert::assertArraySubset([
+ ['command' => 'aFooCommand', 'body' => 'aFooBody'],
+ ['command' => 'aFooCommand', 'body' => 'aFooBody'],
+ ], $producer->getTraces());
+ }
+
+ public function testShouldAllowGetInfoSentToDifferentCommands()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+ $producer->sendCommand('aBarCommand', 'aBarBody');
+
+ Assert::assertArraySubset([
+ ['command' => 'aFooCommand', 'body' => 'aFooBody'],
+ ['command' => 'aBarCommand', 'body' => 'aBarBody'],
+ ], $producer->getTraces());
+ }
+
+ public function testShouldAllowGetInfoSentToSpecialCommand()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendCommand('aFooCommand', 'aFooBody');
+ $producer->sendCommand('aBarCommand', 'aBarBody');
+
+ Assert::assertArraySubset([
+ ['command' => 'aFooCommand', 'body' => 'aFooBody'],
+ ], $producer->getCommandTraces('aFooCommand'));
+
+ Assert::assertArraySubset([
+ ['command' => 'aBarCommand', 'body' => 'aBarBody'],
+ ], $producer->getCommandTraces('aBarCommand'));
+ }
+
+ public function testShouldAllowClearStoredTraces()
+ {
+ $producer = new TraceableProducer($this->createProducerMock());
+
+ $producer->sendEvent('aFooTopic', 'aFooBody');
+
+ // guard
+ $this->assertNotEmpty($producer->getTraces());
+
+ $producer->clearTraces();
+ $this->assertSame([], $producer->getTraces());
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|ProducerInterface
+ */
+ protected function createProducerMock()
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/ConnectionFactoryFactoryTest.php b/pkg/enqueue/Tests/ConnectionFactoryFactoryTest.php
new file mode 100644
index 000000000..b6b5b4d67
--- /dev/null
+++ b/pkg/enqueue/Tests/ConnectionFactoryFactoryTest.php
@@ -0,0 +1,183 @@
+assertTrue($rc->implementsInterface(ConnectionFactoryFactoryInterface::class));
+ }
+
+ public function testShouldBeFinal()
+ {
+ $rc = new \ReflectionClass(ConnectionFactoryFactory::class);
+
+ $this->assertTrue($rc->isFinal());
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function testShouldAcceptStringDSN()
+ {
+ $factory = new ConnectionFactoryFactory();
+
+ $factory->create('null:');
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function testShouldAcceptArrayWithDsnKey()
+ {
+ $factory = new ConnectionFactoryFactory();
+
+ $factory->create(['dsn' => 'null:']);
+ }
+
+ public function testThrowIfInvalidConfigGiven()
+ {
+ $factory = new ConnectionFactoryFactory();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The config must be either array or DSN string.');
+ $factory->create(new \stdClass());
+ }
+
+ public function testThrowIfArrayConfigMissDsnKeyInvalidConfigGiven()
+ {
+ $factory = new ConnectionFactoryFactory();
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The config must be either array or DSN string.');
+ $factory->create(new \stdClass());
+ }
+
+ public function testThrowIfPackageThatSupportSchemeNotInstalled()
+ {
+ $scheme = 'scheme5b7aa7d7cd213';
+ $class = 'ConnectionClass5b7aa7d7cd213';
+
+ Resources::addConnection($class, [$scheme], [], 'thePackage');
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('To use given scheme "scheme5b7aa7d7cd213" a package has to be installed. Run "composer req thePackage" to add it.');
+ (new ConnectionFactoryFactory())->create($scheme.'://foo');
+ }
+
+ public function testThrowIfSchemeIsNotKnown()
+ {
+ $scheme = 'scheme5b7aa862e70a5';
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('A given scheme "scheme5b7aa862e70a5" is not supported. Maybe it is a custom connection, make sure you registered it with "Enqueue\Resources::addConnection".');
+ (new ConnectionFactoryFactory())->create($scheme.'://foo');
+ }
+
+ public function testThrowIfDsnInvalid()
+ {
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The DSN is invalid. It does not have scheme separator ":".');
+
+ (new ConnectionFactoryFactory())->create('invalid-scheme');
+ }
+
+ /**
+ * @dataProvider provideDSN
+ */
+ public function testReturnsExpectedFactories(string $dsn, string $expectedFactoryClass)
+ {
+ $connectionFactory = (new ConnectionFactoryFactory())->create($dsn);
+
+ $this->assertInstanceOf($expectedFactoryClass, $connectionFactory);
+ }
+
+ public static function provideDSN()
+ {
+ yield ['null:', NullConnectionFactory::class];
+
+ yield ['amqp:', AmqpBunnyConnectionFactory::class];
+
+ yield ['amqp+bunny:', AmqpBunnyConnectionFactory::class];
+
+ yield ['amqp+lib:', AmqpLibConnectionFactory::class];
+
+ yield ['amqp+ext:', AmqpExtConnectionFactory::class];
+
+ yield ['amqp+rabbitmq:', AmqpBunnyConnectionFactory::class];
+
+ yield ['amqp+rabbitmq+bunny:', AmqpBunnyConnectionFactory::class];
+
+ yield ['amqp+foo+bar+lib:', AmqpLibConnectionFactory::class];
+
+ yield ['amqp+rabbitmq+ext:', AmqpExtConnectionFactory::class];
+
+ yield ['amqp+rabbitmq+lib:', AmqpLibConnectionFactory::class];
+
+ // bunny does not support amqps, so it is skipped
+ yield ['amqps:', AmqpExtConnectionFactory::class];
+
+ // bunny does not support amqps, so it is skipped
+ yield ['amqps+ext:', AmqpExtConnectionFactory::class];
+
+ // bunny does not support amqps, so it is skipped
+ yield ['amqps+rabbitmq:', AmqpExtConnectionFactory::class];
+
+ yield ['amqps+ext+rabbitmq:', AmqpExtConnectionFactory::class];
+
+ yield ['amqps+lib+rabbitmq:', AmqpLibConnectionFactory::class];
+
+ yield ['mssql:', DbalConnectionFactory::class];
+
+ yield ['mysql:', DbalConnectionFactory::class];
+
+ yield ['pgsql:', DbalConnectionFactory::class];
+
+ yield ['file:', FsConnectionFactory::class];
+
+ // https://github.com/php-enqueue/enqueue-dev/issues/511
+ // yield ['gearman:', GearmanConnectionFactory::class];
+
+ yield ['gps:', GpsConnectionFactory::class];
+
+ yield ['mongodb:', MongodbConnectionFactory::class];
+
+ yield ['beanstalk:', PheanstalkConnectionFactory::class];
+
+ yield ['kafka:', RdKafkaConnectionFactory::class];
+
+ yield ['redis:', RedisConnectionFactory::class];
+
+ yield ['redis+predis:', RedisConnectionFactory::class];
+
+ yield ['redis+foo+bar+phpredis:', RedisConnectionFactory::class];
+
+ yield ['redis+phpredis:', RedisConnectionFactory::class];
+
+ yield ['sqs:', SqsConnectionFactory::class];
+
+ yield ['stomp:', StompConnectionFactory::class];
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/CallbackProcessorTest.php b/pkg/enqueue/Tests/Consumption/CallbackProcessorTest.php
index ac3e4992b..86adbd3a9 100644
--- a/pkg/enqueue/Tests/Consumption/CallbackProcessorTest.php
+++ b/pkg/enqueue/Tests/Consumption/CallbackProcessorTest.php
@@ -3,12 +3,13 @@
namespace Enqueue\Tests\Consumption;
use Enqueue\Consumption\CallbackProcessor;
-use Enqueue\Psr\Processor;
+use Enqueue\Null\NullContext;
+use Enqueue\Null\NullMessage;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullContext;
-use Enqueue\Transport\Null\NullMessage;
+use Interop\Queue\Processor;
+use PHPUnit\Framework\TestCase;
-class CallbackProcessorTest extends \PHPUnit_Framework_TestCase
+class CallbackProcessorTest extends TestCase
{
use ClassExtensionTrait;
@@ -17,12 +18,6 @@ public function testShouldImplementProcessorInterface()
$this->assertClassImplements(Processor::class, CallbackProcessor::class);
}
- public function testCouldBeConstructedWithCallableAsArgument()
- {
- new CallbackProcessor(function () {
- });
- }
-
public function testShouldCallCallbackAndProxyItsReturnedValue()
{
$expectedMessage = new NullMessage();
diff --git a/pkg/enqueue/Tests/Consumption/ChainExtensionTest.php b/pkg/enqueue/Tests/Consumption/ChainExtensionTest.php
new file mode 100644
index 000000000..198d00012
--- /dev/null
+++ b/pkg/enqueue/Tests/Consumption/ChainExtensionTest.php
@@ -0,0 +1,320 @@
+assertClassImplements(ExtensionInterface::class, ChainExtension::class);
+ }
+
+ public function testShouldProxyOnInitLoggerToAllInternalExtensions()
+ {
+ $context = new InitLogger(new NullLogger());
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onInitLogger')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onInitLogger')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onInitLogger($context);
+ }
+
+ public function testShouldProxyOnStartToAllInternalExtensions()
+ {
+ $context = new Start($this->createInteropContextMock(), $this->createLoggerMock(), [], 0, 0);
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onStart')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onStart')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onStart($context);
+ }
+
+ public function testShouldProxyOnPreSubscribeToAllInternalExtensions()
+ {
+ $context = new PreSubscribe(
+ $this->createInteropContextMock(),
+ $this->createInteropProcessorMock(),
+ $this->createInteropConsumerMock(),
+ $this->createLoggerMock()
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPreSubscribe')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPreSubscribe')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPreSubscribe($context);
+ }
+
+ public function testShouldProxyOnPreConsumeToAllInternalExtensions()
+ {
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ new NullLogger(),
+ 1,
+ 2,
+ 3
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPreConsume')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPreConsume')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+ $extensions->onPreConsume($context);
+ }
+
+ public function testShouldProxyOnPreReceiveToAllInternalExtensions()
+ {
+ $context = new MessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ $this->createMock(Processor::class),
+ 1,
+ new NullLogger()
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onMessageReceived')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onMessageReceived')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onMessageReceived($context);
+ }
+
+ public function testShouldProxyOnResultToAllInternalExtensions()
+ {
+ $context = new MessageResult(
+ $this->createInteropContextMock(),
+ $this->createInteropConsumerMock(),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onResult')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onResult')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onResult($context);
+ }
+
+ public function testShouldProxyOnPostReceiveToAllInternalExtensions()
+ {
+ $context = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPostMessageReceived')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPostMessageReceived')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPostMessageReceived($context);
+ }
+
+ public function testShouldProxyOnPostConsumeToAllInternalExtensions()
+ {
+ $postConsume = new PostConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ 1,
+ 1,
+ 1,
+ new NullLogger()
+ );
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onPostConsume')
+ ->with($this->identicalTo($postConsume))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onPostConsume')
+ ->with($this->identicalTo($postConsume))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onPostConsume($postConsume);
+ }
+
+ public function testShouldProxyOnEndToAllInternalExtensions()
+ {
+ $context = new End($this->createInteropContextMock(), 1, 2, new NullLogger());
+
+ $fooExtension = $this->createExtension();
+ $fooExtension
+ ->expects($this->once())
+ ->method('onEnd')
+ ->with($this->identicalTo($context))
+ ;
+ $barExtension = $this->createExtension();
+ $barExtension
+ ->expects($this->once())
+ ->method('onEnd')
+ ->with($this->identicalTo($context))
+ ;
+
+ $extensions = new ChainExtension([$fooExtension, $barExtension]);
+
+ $extensions->onEnd($context);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createLoggerMock(): LoggerInterface
+ {
+ return $this->createMock(LoggerInterface::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createInteropContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createInteropConsumerMock(): Consumer
+ {
+ return $this->createMock(Consumer::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createInteropProcessorMock(): Processor
+ {
+ return $this->createMock(Processor::class);
+ }
+
+ /**
+ * @return MockObject|ExtensionInterface
+ */
+ protected function createExtension()
+ {
+ return $this->createMock(ExtensionInterface::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createSubscriptionConsumerMock(): SubscriptionConsumer
+ {
+ return $this->createMock(SubscriptionConsumer::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/ContextTest.php b/pkg/enqueue/Tests/Consumption/ContextTest.php
deleted file mode 100644
index 3d891e232..000000000
--- a/pkg/enqueue/Tests/Consumption/ContextTest.php
+++ /dev/null
@@ -1,257 +0,0 @@
-createPsrContext());
- }
-
- public function testShouldAllowGetSessionSetInConstructor()
- {
- $psrContext = $this->createPsrContext();
-
- $context = new Context($psrContext);
-
- $this->assertSame($psrContext, $context->getPsrContext());
- }
-
- public function testShouldAllowGetMessageConsumerPreviouslySet()
- {
- $messageConsumer = $this->createPsrConsumer();
-
- $context = new Context($this->createPsrContext());
- $context->setPsrConsumer($messageConsumer);
-
- $this->assertSame($messageConsumer, $context->getPsrConsumer());
- }
-
- public function testThrowOnTryToChangeMessageConsumerIfAlreadySet()
- {
- $messageConsumer = $this->createPsrConsumer();
- $anotherMessageConsumer = $this->createPsrConsumer();
-
- $context = new Context($this->createPsrContext());
-
- $context->setPsrConsumer($messageConsumer);
-
- $this->expectException(IllegalContextModificationException::class);
-
- $context->setPsrConsumer($anotherMessageConsumer);
- }
-
- public function testShouldAllowGetMessageProducerPreviouslySet()
- {
- $processorMock = $this->createProcessorMock();
-
- $context = new Context($this->createPsrContext());
- $context->setPsrProcessor($processorMock);
-
- $this->assertSame($processorMock, $context->getPsrProcessor());
- }
-
- public function testThrowOnTryToChangeProcessorIfAlreadySet()
- {
- $processor = $this->createProcessorMock();
- $anotherProcessor = $this->createProcessorMock();
-
- $context = new Context($this->createPsrContext());
-
- $context->setPsrProcessor($processor);
-
- $this->expectException(IllegalContextModificationException::class);
-
- $context->setPsrProcessor($anotherProcessor);
- }
-
- public function testShouldAllowGetLoggerPreviouslySet()
- {
- $logger = new NullLogger();
-
- $context = new Context($this->createPsrContext());
- $context->setLogger($logger);
-
- $this->assertSame($logger, $context->getLogger());
- }
-
- public function testShouldSetExecutionInterruptedToFalseInConstructor()
- {
- $context = new Context($this->createPsrContext());
-
- $this->assertFalse($context->isExecutionInterrupted());
- }
-
- public function testShouldAllowGetPreviouslySetMessage()
- {
- /** @var Message $message */
- $message = $this->createMock(Message::class);
-
- $context = new Context($this->createPsrContext());
-
- $context->setPsrMessage($message);
-
- $this->assertSame($message, $context->getPsrMessage());
- }
-
- public function testThrowOnTryToChangeMessageIfAlreadySet()
- {
- /** @var Message $message */
- $message = $this->createMock(Message::class);
-
- $context = new Context($this->createPsrContext());
-
- $this->expectException(IllegalContextModificationException::class);
- $this->expectExceptionMessage('The message could be set once');
-
- $context->setPsrMessage($message);
- $context->setPsrMessage($message);
- }
-
- public function testShouldAllowGetPreviouslySetException()
- {
- $exception = new \Exception();
-
- $context = new Context($this->createPsrContext());
-
- $context->setException($exception);
-
- $this->assertSame($exception, $context->getException());
- }
-
- public function testShouldAllowGetPreviouslySetResult()
- {
- $result = 'aResult';
-
- $context = new Context($this->createPsrContext());
-
- $context->setResult($result);
-
- $this->assertSame($result, $context->getResult());
- }
-
- public function testThrowOnTryToChangeResultIfAlreadySet()
- {
- $result = 'aResult';
-
- $context = new Context($this->createPsrContext());
-
- $this->expectException(IllegalContextModificationException::class);
- $this->expectExceptionMessage('The result modification is not allowed');
-
- $context->setResult($result);
- $context->setResult($result);
- }
-
- public function testShouldAllowGetPreviouslySetExecutionInterrupted()
- {
- $context = new Context($this->createPsrContext());
-
- // guard
- $this->assertFalse($context->isExecutionInterrupted());
-
- $context->setExecutionInterrupted(true);
-
- $this->assertTrue($context->isExecutionInterrupted());
- }
-
- public function testThrowOnTryToRollbackExecutionInterruptedIfAlreadySetToTrue()
- {
- $context = new Context($this->createPsrContext());
-
- $this->expectException(IllegalContextModificationException::class);
- $this->expectExceptionMessage('The execution once interrupted could not be roll backed');
-
- $context->setExecutionInterrupted(true);
- $context->setExecutionInterrupted(false);
- }
-
- public function testNotThrowOnSettingExecutionInterruptedToTrueIfAlreadySetToTrue()
- {
- $context = new Context($this->createPsrContext());
-
- $context->setExecutionInterrupted(true);
- $context->setExecutionInterrupted(true);
- }
-
- public function testShouldAllowGetPreviouslySetLogger()
- {
- $expectedLogger = new NullLogger();
-
- $context = new Context($this->createPsrContext());
-
- $context->setLogger($expectedLogger);
-
- $this->assertSame($expectedLogger, $context->getLogger());
- }
-
- public function testThrowOnSettingLoggerIfAlreadySet()
- {
- $context = new Context($this->createPsrContext());
-
- $context->setLogger(new NullLogger());
-
- $this->expectException(IllegalContextModificationException::class);
- $this->expectExceptionMessage('The logger modification is not allowed');
-
- $context->setLogger(new NullLogger());
- }
-
- public function testShouldAllowGetPreviouslySetQueue()
- {
- $context = new Context($this->createPsrContext());
-
- $context->setPsrQueue($queue = new NullQueue(''));
-
- $this->assertSame($queue, $context->getPsrQueue());
- }
-
- public function testThrowOnSettingQueueNameIfAlreadySet()
- {
- $context = new Context($this->createPsrContext());
-
- $context->setPsrQueue(new NullQueue(''));
-
- $this->expectException(IllegalContextModificationException::class);
- $this->expectExceptionMessage('The queue modification is not allowed');
-
- $context->setPsrQueue(new NullQueue(''));
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|PsrContext
- */
- protected function createPsrContext()
- {
- return $this->createMock(PsrContext::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Consumer
- */
- protected function createPsrConsumer()
- {
- return $this->createMock(Consumer::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
- */
- protected function createProcessorMock()
- {
- return $this->createMock(Processor::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Consumption/Exception/ConsumptionInterruptedExceptionTest.php b/pkg/enqueue/Tests/Consumption/Exception/ConsumptionInterruptedExceptionTest.php
deleted file mode 100644
index f4fa0678d..000000000
--- a/pkg/enqueue/Tests/Consumption/Exception/ConsumptionInterruptedExceptionTest.php
+++ /dev/null
@@ -1,27 +0,0 @@
-assertClassImplements(ExceptionInterface::class, ConsumptionInterruptedException::class);
- }
-
- public function testShouldExtendLogicException()
- {
- $this->assertClassExtends(\LogicException::class, ConsumptionInterruptedException::class);
- }
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new ConsumptionInterruptedException();
- }
-}
diff --git a/pkg/enqueue/Tests/Consumption/Exception/IllegalContextModificationExceptionTest.php b/pkg/enqueue/Tests/Consumption/Exception/IllegalContextModificationExceptionTest.php
index 436e594d7..241f4adf9 100644
--- a/pkg/enqueue/Tests/Consumption/Exception/IllegalContextModificationExceptionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Exception/IllegalContextModificationExceptionTest.php
@@ -1,12 +1,13 @@
assertClassExtends(\LogicException::class, IllegalContextModificationException::class);
}
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new IllegalContextModificationException();
- }
}
diff --git a/pkg/enqueue/Tests/Consumption/Exception/InvalidArgumentExceptionTest.php b/pkg/enqueue/Tests/Consumption/Exception/InvalidArgumentExceptionTest.php
index 16c3b3c6f..c1c5db362 100644
--- a/pkg/enqueue/Tests/Consumption/Exception/InvalidArgumentExceptionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Exception/InvalidArgumentExceptionTest.php
@@ -1,12 +1,13 @@
assertClassExtends(\LogicException::class, InvalidArgumentException::class);
}
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new InvalidArgumentException();
- }
-
public function testThrowIfAssertInstanceOfNotSameAsExpected()
{
$this->expectException(InvalidArgumentException::class);
@@ -35,6 +31,9 @@ public function testThrowIfAssertInstanceOfNotSameAsExpected()
InvalidArgumentException::assertInstanceOf(new \SplStack(), \SplQueue::class);
}
+ /**
+ * @doesNotPerformAssertions
+ */
public function testShouldDoNothingIfAssertDestinationInstanceOfSameAsExpected()
{
InvalidArgumentException::assertInstanceOf(new \SplQueue(), \SplQueue::class);
diff --git a/pkg/enqueue/Tests/Consumption/Exception/LogicExceptionTest.php b/pkg/enqueue/Tests/Consumption/Exception/LogicExceptionTest.php
index d81975d3c..2655609ae 100644
--- a/pkg/enqueue/Tests/Consumption/Exception/LogicExceptionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Exception/LogicExceptionTest.php
@@ -1,12 +1,13 @@
assertClassExtends(\LogicException::class, LogicException::class);
}
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new LogicException();
- }
}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumedMessagesExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumedMessagesExtensionTest.php
index e9b783737..137e30ba4 100644
--- a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumedMessagesExtensionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumedMessagesExtensionTest.php
@@ -2,40 +2,84 @@
namespace Enqueue\Tests\Consumption\Extension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
use Enqueue\Consumption\Extension\LimitConsumedMessagesExtension;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context;
+use Interop\Queue\Message;
+use Interop\Queue\SubscriptionConsumer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
-class LimitConsumedMessagesExtensionTest extends \PHPUnit_Framework_TestCase
+class LimitConsumedMessagesExtensionTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
+ public function testOnPreConsumeShouldInterruptWhenLimitIsReached()
{
- new LimitConsumedMessagesExtension(12345);
- }
+ $logger = $this->createLoggerMock();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('[LimitConsumedMessagesExtension] Message consumption is interrupted since'.
+ ' the message limit reached. limit: "3"')
+ ;
- public function testShouldThrowExceptionIfMessageLimitIsNotInt()
- {
- $this->setExpectedException(
- \InvalidArgumentException::class,
- 'Expected message limit is int but got: "double"'
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ $logger,
+ 1,
+ 2,
+ 3
+ );
+
+ // guard
+ $this->assertFalse($context->isExecutionInterrupted());
+
+ // test
+ $extension = new LimitConsumedMessagesExtension(3);
+
+ $extension->onPreConsume($context);
+ $this->assertFalse($context->isExecutionInterrupted());
+
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
);
- new LimitConsumedMessagesExtension(0.0);
+ $extension->onPostMessageReceived($postReceivedMessage);
+ $extension->onPostMessageReceived($postReceivedMessage);
+ $extension->onPostMessageReceived($postReceivedMessage);
+
+ $extension->onPreConsume($context);
+ $this->assertTrue($context->isExecutionInterrupted());
}
- public function testOnBeforeReceiveShouldInterruptExecutionIfLimitIsZero()
+ public function testOnPreConsumeShouldInterruptExecutionIfLimitIsZero()
{
- $context = $this->createContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with('[LimitConsumedMessagesExtension] Message consumption is interrupted since'.
' the message limit reached. limit: "0"')
;
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ $logger,
+ 1,
+ 2,
+ 3
+ );
+
// guard
$this->assertFalse($context->isExecutionInterrupted());
@@ -43,20 +87,29 @@ public function testOnBeforeReceiveShouldInterruptExecutionIfLimitIsZero()
$extension = new LimitConsumedMessagesExtension(0);
// consume 1
- $extension->onBeforeReceive($context);
+ $extension->onPreConsume($context);
$this->assertTrue($context->isExecutionInterrupted());
}
- public function testOnBeforeReceiveShouldInterruptExecutionIfLimitIsLessThatZero()
+ public function testOnPreConsumeShouldInterruptExecutionIfLimitIsLessThatZero()
{
- $context = $this->createContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with('[LimitConsumedMessagesExtension] Message consumption is interrupted since'.
' the message limit reached. limit: "-1"')
;
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ $logger,
+ 1,
+ 2,
+ 3
+ );
+
// guard
$this->assertFalse($context->isExecutionInterrupted());
@@ -64,45 +117,65 @@ public function testOnBeforeReceiveShouldInterruptExecutionIfLimitIsLessThatZero
$extension = new LimitConsumedMessagesExtension(-1);
// consume 1
- $extension->onBeforeReceive($context);
+ $extension->onPreConsume($context);
$this->assertTrue($context->isExecutionInterrupted());
}
public function testOnPostReceivedShouldInterruptExecutionIfMessageLimitExceeded()
{
- $context = $this->createContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with('[LimitConsumedMessagesExtension] Message consumption is interrupted since'.
' the message limit reached. limit: "2"')
;
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ $logger
+ );
+
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// test
$extension = new LimitConsumedMessagesExtension(2);
// consume 1
- $extension->onPostReceived($context);
- $this->assertFalse($context->isExecutionInterrupted());
+ $extension->onPostMessageReceived($postReceivedMessage);
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// consume 2 and exit
- $extension->onPostReceived($context);
- $this->assertTrue($context->isExecutionInterrupted());
+ $extension->onPostMessageReceived($postReceivedMessage);
+ $this->assertTrue($postReceivedMessage->isExecutionInterrupted());
}
/**
- * @return Context
+ * @return MockObject
*/
- protected function createContext()
+ protected function createInteropContextMock(): Context
{
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($this->createMock(LoggerInterface::class));
- $context->setPsrConsumer($this->createMock(Consumer::class));
- $context->setPsrProcessor($this->createMock(Processor::class));
+ return $this->createMock(Context::class);
+ }
- return $context;
+ /**
+ * @return MockObject
+ */
+ private function createSubscriptionConsumerMock(): SubscriptionConsumer
+ {
+ return $this->createMock(SubscriptionConsumer::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createLoggerMock(): LoggerInterface
+ {
+ return $this->createMock(LoggerInterface::class);
}
}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumerMemoryExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumerMemoryExtensionTest.php
index c7f42a207..25ac85895 100644
--- a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumerMemoryExtensionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumerMemoryExtensionTest.php
@@ -2,136 +2,197 @@
namespace Enqueue\Tests\Consumption\Extension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
use Enqueue\Consumption\Extension\LimitConsumerMemoryExtension;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context;
+use Interop\Queue\Message;
+use Interop\Queue\SubscriptionConsumer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
-class LimitConsumerMemoryExtensionTest extends \PHPUnit_Framework_TestCase
+class LimitConsumerMemoryExtensionTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
- {
- new LimitConsumerMemoryExtension(12345);
- }
-
public function testShouldThrowExceptionIfMemoryLimitIsNotInt()
{
- $this->setExpectedException(\InvalidArgumentException::class, 'Expected memory limit is int but got: "double"');
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Expected memory limit is int but got: "double"');
new LimitConsumerMemoryExtension(0.0);
}
- public function testOnIdleShouldInterruptExecutionIfMemoryLimitReached()
+ public function testOnPostConsumeShouldInterruptExecutionIfMemoryLimitReached()
{
- $context = $this->createPsrContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with($this->stringContains('[LimitConsumerMemoryExtension] Interrupt execution as memory limit reached.'))
;
+ $postConsume = new PostConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ 1,
+ 1,
+ 1,
+ $logger
+ );
+
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
// test
$extension = new LimitConsumerMemoryExtension(1);
- $extension->onIdle($context);
+ $extension->onPostConsume($postConsume);
- $this->assertTrue($context->isExecutionInterrupted());
+ $this->assertTrue($postConsume->isExecutionInterrupted());
}
public function testOnPostReceivedShouldInterruptExecutionIfMemoryLimitReached()
{
- $context = $this->createPsrContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with($this->stringContains('[LimitConsumerMemoryExtension] Interrupt execution as memory limit reached.'))
;
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ $logger
+ );
+
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// test
$extension = new LimitConsumerMemoryExtension(1);
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
- $this->assertTrue($context->isExecutionInterrupted());
+ $this->assertTrue($postReceivedMessage->isExecutionInterrupted());
}
- public function testOnBeforeReceivedShouldInterruptExecutionIfMemoryLimitReached()
+ public function testOnPreConsumeShouldInterruptExecutionIfMemoryLimitReached()
{
- $context = $this->createPsrContext();
- $context->getLogger()
+ $logger = $this->createLoggerMock();
+ $logger
->expects($this->once())
->method('debug')
->with($this->stringContains('[LimitConsumerMemoryExtension] Interrupt execution as memory limit reached.'))
;
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ $logger,
+ 1,
+ 2,
+ 3
+ );
+
// guard
$this->assertFalse($context->isExecutionInterrupted());
// test
$extension = new LimitConsumerMemoryExtension(1);
- $extension->onBeforeReceive($context);
+ $extension->onPreConsume($context);
$this->assertTrue($context->isExecutionInterrupted());
}
- public function testOnBeforeReceiveShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
+ public function testOnPreConsumeShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
{
- $context = $this->createPsrContext();
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ new NullLogger(),
+ 1,
+ 2,
+ 3
+ );
// guard
$this->assertFalse($context->isExecutionInterrupted());
// test
- $extension = new LimitConsumerMemoryExtension(PHP_INT_MAX);
- $extension->onBeforeReceive($context);
+ $extension = new LimitConsumerMemoryExtension(\PHP_INT_MAX);
+ $extension->onPreConsume($context);
$this->assertFalse($context->isExecutionInterrupted());
}
- public function testOnIdleShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
+ public function testOnPostConsumeShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
{
- $context = $this->createPsrContext();
+ $postConsume = new PostConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ 1,
+ 1,
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
// test
- $extension = new LimitConsumerMemoryExtension(PHP_INT_MAX);
- $extension->onIdle($context);
+ $extension = new LimitConsumerMemoryExtension(\PHP_INT_MAX);
+ $extension->onPostConsume($postConsume);
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
}
- public function testOnPostReceivedShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
+ public function testOnPostMessageReceivedShouldNotInterruptExecutionIfMemoryLimitIsNotReached()
{
- $context = $this->createPsrContext();
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// test
- $extension = new LimitConsumerMemoryExtension(PHP_INT_MAX);
- $extension->onPostReceived($context);
+ $extension = new LimitConsumerMemoryExtension(\PHP_INT_MAX);
+ $extension->onPostMessageReceived($postReceivedMessage);
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
}
/**
- * @return Context
+ * @return MockObject
*/
- protected function createPsrContext()
+ protected function createInteropContextMock(): Context
{
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($this->createMock(LoggerInterface::class));
- $context->setPsrConsumer($this->createMock(Consumer::class));
- $context->setPsrProcessor($this->createMock(Processor::class));
+ return $this->createMock(Context::class);
+ }
- return $context;
+ /**
+ * @return MockObject
+ */
+ private function createSubscriptionConsumerMock(): SubscriptionConsumer
+ {
+ return $this->createMock(SubscriptionConsumer::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createLoggerMock(): LoggerInterface
+ {
+ return $this->createMock(LoggerInterface::class);
}
}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumptionTimeExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumptionTimeExtensionTest.php
index 2abb75ecd..fa6cb76a1 100644
--- a/pkg/enqueue/Tests/Consumption/Extension/LimitConsumptionTimeExtensionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Extension/LimitConsumptionTimeExtensionTest.php
@@ -2,23 +2,31 @@
namespace Enqueue\Tests\Consumption\Extension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
use Enqueue\Consumption\Extension\LimitConsumptionTimeExtension;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Processor;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context;
+use Interop\Queue\Message;
+use Interop\Queue\SubscriptionConsumer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
-class LimitConsumptionTimeExtensionTest extends \PHPUnit_Framework_TestCase
+class LimitConsumptionTimeExtensionTest extends TestCase
{
- public function testCouldBeConstructedWithRequiredArguments()
+ public function testOnPreConsumeShouldInterruptExecutionIfConsumptionTimeExceeded()
{
- new LimitConsumptionTimeExtension(new \DateTime('+1 day'));
- }
-
- public function testOnBeforeReceiveShouldInterruptExecutionIfConsumptionTimeExceeded()
- {
- $context = $this->createContext();
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ new NullLogger(),
+ 1,
+ 2,
+ 3
+ );
// guard
$this->assertFalse($context->isExecutionInterrupted());
@@ -26,44 +34,65 @@ public function testOnBeforeReceiveShouldInterruptExecutionIfConsumptionTimeExce
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('-2 second'));
- $extension->onBeforeReceive($context);
+ $extension->onPreConsume($context);
$this->assertTrue($context->isExecutionInterrupted());
}
- public function testOnIdleShouldInterruptExecutionIfConsumptionTimeExceeded()
+ public function testOnPostConsumeShouldInterruptExecutionIfConsumptionTimeExceeded()
{
- $context = $this->createContext();
+ $postConsume = new PostConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ 1,
+ 1,
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('-2 second'));
- $extension->onIdle($context);
+ $extension->onPostConsume($postConsume);
- $this->assertTrue($context->isExecutionInterrupted());
+ $this->assertTrue($postConsume->isExecutionInterrupted());
}
public function testOnPostReceivedShouldInterruptExecutionIfConsumptionTimeExceeded()
{
- $context = $this->createContext();
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('-2 second'));
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
- $this->assertTrue($context->isExecutionInterrupted());
+ $this->assertTrue($postReceivedMessage->isExecutionInterrupted());
}
- public function testOnBeforeReceiveShouldNotInterruptExecutionIfConsumptionTimeIsNotExceeded()
+ public function testOnPreConsumeShouldNotInterruptExecutionIfConsumptionTimeIsNotExceeded()
{
- $context = $this->createContext();
+ $context = new PreConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ new NullLogger(),
+ 1,
+ 2,
+ 3
+ );
// guard
$this->assertFalse($context->isExecutionInterrupted());
@@ -71,51 +100,76 @@ public function testOnBeforeReceiveShouldNotInterruptExecutionIfConsumptionTimeI
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('+2 second'));
- $extension->onBeforeReceive($context);
+ $extension->onPreConsume($context);
$this->assertFalse($context->isExecutionInterrupted());
}
- public function testOnIdleShouldNotInterruptExecutionIfConsumptionTimeIsNotExceeded()
+ public function testOnPostConsumeShouldNotInterruptExecutionIfConsumptionTimeIsNotExceeded()
{
- $context = $this->createContext();
+ $postConsume = new PostConsume(
+ $this->createInteropContextMock(),
+ $this->createSubscriptionConsumerMock(),
+ 1,
+ 1,
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('+2 second'));
- $extension->onIdle($context);
+ $extension->onPostConsume($postConsume);
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postConsume->isExecutionInterrupted());
}
public function testOnPostReceivedShouldNotInterruptExecutionIfConsumptionTimeIsNotExceeded()
{
- $context = $this->createContext();
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createInteropContextMock(),
+ $this->createMock(Consumer::class),
+ $this->createMock(Message::class),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
// guard
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
// test
$extension = new LimitConsumptionTimeExtension(new \DateTime('+2 second'));
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
- $this->assertFalse($context->isExecutionInterrupted());
+ $this->assertFalse($postReceivedMessage->isExecutionInterrupted());
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createInteropContextMock(): Context
+ {
+ return $this->createMock(Context::class);
}
/**
- * @return Context
+ * @return MockObject
*/
- protected function createContext()
+ private function createSubscriptionConsumerMock(): SubscriptionConsumer
{
- $context = new Context($this->createMock(PsrContext::class));
- $context->setLogger($this->createMock(LoggerInterface::class));
- $context->setPsrConsumer($this->createMock(Consumer::class));
- $context->setPsrProcessor($this->createMock(Processor::class));
+ return $this->createMock(SubscriptionConsumer::class);
+ }
- return $context;
+ /**
+ * @return MockObject
+ */
+ private function createLoggerMock(): LoggerInterface
+ {
+ return $this->createMock(LoggerInterface::class);
}
}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/LogExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/LogExtensionTest.php
new file mode 100644
index 000000000..006a2c549
--- /dev/null
+++ b/pkg/enqueue/Tests/Consumption/Extension/LogExtensionTest.php
@@ -0,0 +1,266 @@
+assertClassImplements(StartExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementEndExtensionInterface()
+ {
+ $this->assertClassImplements(EndExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementMessageReceivedExtensionInterface()
+ {
+ $this->assertClassImplements(MessageReceivedExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldImplementPostMessageReceivedExtensionInterface()
+ {
+ $this->assertClassImplements(PostMessageReceivedExtensionInterface::class, LogExtension::class);
+ }
+
+ public function testShouldLogStartOnStart()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Consumption has started')
+ ;
+
+ $context = new Start($this->createContextMock(), $logger, [], 1, 1);
+
+ $extension = new LogExtension();
+ $extension->onStart($context);
+ }
+
+ public function testShouldLogEndOnEnd()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Consumption has ended')
+ ;
+
+ $context = new End($this->createContextMock(), 1, 2, $logger);
+
+ $extension = new LogExtension();
+ $extension->onEnd($context);
+ }
+
+ public function testShouldLogMessageReceived()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('debug')
+ ->with('Received from {queueName} {body}', [
+ 'queueName' => 'aQueue',
+ 'redelivered' => false,
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ ])
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new MessageReceived($this->createContextMock(), $consumerMock, $message, $this->createProcessorMock(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithStringResult()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'aResult',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, 'aResult', 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogRejectedMessageAsError()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::ERROR,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'reject',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Processor::REJECT, 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => '',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack(), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ public function testShouldLogMessageProcessedWithReasonResultObject()
+ {
+ $logger = $this->createLogger();
+ $logger
+ ->expects($this->once())
+ ->method('log')
+ ->with(LogLevel::INFO,
+ 'Processed from {queueName} {body} {result} {reason}',
+ [
+ 'queueName' => 'aQueue',
+ 'body' => Stringify::that('aBody'),
+ 'properties' => Stringify::that(['aProp' => 'aPropVal']),
+ 'headers' => Stringify::that(['aHeader' => 'aHeaderVal']),
+ 'result' => 'ack',
+ 'reason' => 'aReason',
+ ]
+ )
+ ;
+
+ $consumerMock = $this->createConsumerStub(new NullQueue('aQueue'));
+ $message = new NullMessage('aBody');
+ $message->setProperty('aProp', 'aPropVal');
+ $message->setHeader('aHeader', 'aHeaderVal');
+
+ $context = new PostMessageReceived($this->createContextMock(), $consumerMock, $message, Result::ack('aReason'), 1, $logger);
+
+ $extension = new LogExtension();
+ $extension->onPostMessageReceived($context);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createConsumerStub(Queue $queue): Consumer
+ {
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue)
+ ;
+
+ return $consumerMock;
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createProcessorMock(): Processor
+ {
+ return $this->createMock(Processor::class);
+ }
+
+ /**
+ * @return MockObject|LoggerInterface
+ */
+ private function createLogger()
+ {
+ return $this->createMock(LoggerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/LoggerExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/LoggerExtensionTest.php
index 62998e82d..666892e0e 100644
--- a/pkg/enqueue/Tests/Consumption/Extension/LoggerExtensionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Extension/LoggerExtensionTest.php
@@ -2,175 +2,78 @@
namespace Enqueue\Tests\Consumption\Extension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\InitLogger;
use Enqueue\Consumption\Extension\LoggerExtension;
-use Enqueue\Consumption\ExtensionInterface;
-use Enqueue\Consumption\Result;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
+use Enqueue\Consumption\InitLoggerExtensionInterface;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullMessage;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
use Psr\Log\LoggerInterface;
+use Psr\Log\NullLogger;
-class LoggerExtensionTest extends \PHPUnit_Framework_TestCase
+class LoggerExtensionTest extends TestCase
{
use ClassExtensionTrait;
- public function testShouldImplementExtensionInterface()
+ public function testShouldImplementInitLoggerExtensionInterface()
{
- $this->assertClassImplements(ExtensionInterface::class, LoggerExtension::class);
+ $this->assertClassImplements(InitLoggerExtensionInterface::class, LoggerExtension::class);
}
- public function testCouldBeConstructedWithLoggerAsFirstArgument()
- {
- new LoggerExtension($this->createLogger());
- }
-
- public function testShouldSetLoggerToContextOnStart()
+ public function testShouldSetLoggerToContextOnInitLogger()
{
$logger = $this->createLogger();
$extension = new LoggerExtension($logger);
- $context = new Context($this->createPsrContextMock());
+ $previousLogger = new NullLogger();
+ $context = new InitLogger($previousLogger);
- $extension->onStart($context);
+ $extension->onInitLogger($context);
$this->assertSame($logger, $context->getLogger());
}
public function testShouldAddInfoMessageOnStart()
{
- $logger = $this->createLogger();
- $logger
- ->expects($this->once())
- ->method('debug')
- ->with($this->stringStartsWith('Set context\'s logger'))
- ;
-
- $extension = new LoggerExtension($logger);
-
- $context = new Context($this->createPsrContextMock());
-
- $extension->onStart($context);
- }
-
- public function testShouldLogRejectMessageStatus()
- {
- $logger = $this->createLogger();
- $logger
- ->expects($this->once())
- ->method('error')
- ->with('reason', ['body' => 'message body', 'headers' => [], 'properties' => []])
- ;
-
- $extension = new LoggerExtension($logger);
-
- $message = new NullMessage();
- $message->setBody('message body');
-
- $context = new Context($this->createPsrContextMock());
- $context->setResult(Result::reject('reason'));
- $context->setPsrMessage($message);
-
- $extension->onPostReceived($context);
- }
-
- public function testShouldLogRequeueMessageStatus()
- {
- $logger = $this->createLogger();
- $logger
- ->expects($this->once())
- ->method('error')
- ->with('reason', ['body' => 'message body', 'headers' => [], 'properties' => []])
- ;
-
- $extension = new LoggerExtension($logger);
-
- $message = new NullMessage();
- $message->setBody('message body');
-
- $context = new Context($this->createPsrContextMock());
- $context->setResult(Result::requeue('reason'));
- $context->setPsrMessage($message);
-
- $extension->onPostReceived($context);
- }
-
- public function testShouldNotLogRequeueMessageStatusIfReasonIsEmpty()
- {
- $logger = $this->createLogger();
- $logger
- ->expects($this->never())
- ->method('error')
- ;
+ $previousLogger = $this->createLogger();
- $extension = new LoggerExtension($logger);
-
- $context = new Context($this->createPsrContextMock());
- $context->setResult(Result::requeue());
-
- $extension->onPostReceived($context);
- }
-
- public function testShouldLogAckMessageStatus()
- {
$logger = $this->createLogger();
$logger
->expects($this->once())
- ->method('info')
- ->with('reason', ['body' => 'message body', 'headers' => [], 'properties' => []])
+ ->method('debug')
+ ->with(sprintf('Change logger from "%s" to "%s"', $logger::class, $previousLogger::class))
;
$extension = new LoggerExtension($logger);
- $message = new NullMessage();
- $message->setBody('message body');
-
- $context = new Context($this->createPsrContextMock());
- $context->setResult(Result::ack('reason'));
- $context->setPsrMessage($message);
+ $context = new InitLogger($previousLogger);
- $extension->onPostReceived($context);
+ $extension->onInitLogger($context);
}
- public function testShouldNotLogAckMessageStatusIfReasonIsEmpty()
+ public function testShouldDoNothingIfSameLoggerInstanceAlreadySet()
{
$logger = $this->createLogger();
$logger
->expects($this->never())
- ->method('info')
+ ->method('debug')
;
$extension = new LoggerExtension($logger);
- $context = new Context($this->createPsrContextMock());
- $context->setResult(Result::ack());
+ $context = new InitLogger($logger);
- $extension->onPostReceived($context);
- }
+ $extension->onInitLogger($context);
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|PsrContext
- */
- protected function createPsrContextMock()
- {
- return $this->createMock(PsrContext::class);
+ $this->assertSame($logger, $context->getLogger());
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|LoggerInterface
+ * @return MockObject|LoggerInterface
*/
protected function createLogger()
{
return $this->createMock(LoggerInterface::class);
}
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Consumer
- */
- protected function createConsumerMock()
- {
- return $this->createMock(Consumer::class);
- }
}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/NicenessExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/NicenessExtensionTest.php
new file mode 100644
index 000000000..734bc8417
--- /dev/null
+++ b/pkg/enqueue/Tests/Consumption/Extension/NicenessExtensionTest.php
@@ -0,0 +1,38 @@
+expectException(\InvalidArgumentException::class);
+ new NicenessExtension('1');
+ }
+
+ public function testShouldThrowWarningOnInvalidArgument()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('proc_nice(): Only a super user may attempt to increase the priority of a process');
+
+ $context = new Start($this->createContextMock(), new NullLogger(), [], 0, 0);
+
+ $extension = new NicenessExtension(-1);
+ $extension->onStart($context);
+ }
+
+ /**
+ * @return MockObject|InteropContext
+ */
+ protected function createContextMock(): InteropContext
+ {
+ return $this->createMock(InteropContext::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/Extension/ReplyExtensionTest.php b/pkg/enqueue/Tests/Consumption/Extension/ReplyExtensionTest.php
index 9e23b426d..cb65816ce 100644
--- a/pkg/enqueue/Tests/Consumption/Extension/ReplyExtensionTest.php
+++ b/pkg/enqueue/Tests/Consumption/Extension/ReplyExtensionTest.php
@@ -2,99 +2,81 @@
namespace Enqueue\Tests\Consumption\Extension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\PostMessageReceived;
use Enqueue\Consumption\Extension\ReplyExtension;
-use Enqueue\Consumption\ExtensionInterface;
+use Enqueue\Consumption\PostMessageReceivedExtensionInterface;
use Enqueue\Consumption\Result;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Producer;
+use Enqueue\Null\NullMessage;
+use Enqueue\Null\NullQueue;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullContext;
-use Enqueue\Transport\Null\NullMessage;
-use Enqueue\Transport\Null\NullQueue;
-
-class ReplyExtensionTest extends \PHPUnit_Framework_TestCase
+use Interop\Queue\Consumer;
+use Interop\Queue\Context;
+use Interop\Queue\Producer as InteropProducer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\NullLogger;
+
+class ReplyExtensionTest extends TestCase
{
use ClassExtensionTrait;
- public function testShouldImplementExtensionInterface()
- {
- $this->assertClassImplements(ExtensionInterface::class, ReplyExtension::class);
- }
-
- public function testCouldBeConstructedWithoutAnyArguments()
- {
- new ReplyExtension();
- }
-
- public function testShouldDoNothingOnPreReceived()
- {
- $extension = new ReplyExtension();
-
- $extension->onPreReceived(new Context(new NullContext()));
- }
-
- public function testShouldDoNothingOnStart()
- {
- $extension = new ReplyExtension();
-
- $extension->onStart(new Context(new NullContext()));
- }
-
- public function testShouldDoNothingOnBeforeReceive()
+ public function testShouldImplementPostMessageReceivedExtensionInterface()
{
- $extension = new ReplyExtension();
-
- $extension->onBeforeReceive(new Context(new NullContext()));
- }
-
- public function testShouldDoNothingOnInterrupted()
- {
- $extension = new ReplyExtension();
-
- $extension->onInterrupted(new Context(new NullContext()));
+ $this->assertClassImplements(PostMessageReceivedExtensionInterface::class, ReplyExtension::class);
}
public function testShouldDoNothingIfReceivedMessageNotHaveReplyToSet()
{
$extension = new ReplyExtension();
- $context = new Context(new NullContext());
- $context->setPsrMessage(new NullMessage());
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createNeverUsedContextMock(),
+ $this->createMock(Consumer::class),
+ new NullMessage(),
+ 'aResult',
+ 1,
+ new NullLogger()
+ );
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
}
- public function testThrowIfResultNotInstanceOfResult()
+ public function testShouldDoNothingIfContextResultIsNotInstanceOfResult()
{
$extension = new ReplyExtension();
$message = new NullMessage();
$message->setReplyTo('aReplyToQueue');
- $context = new Context(new NullContext());
- $context->setPsrMessage($message);
- $context->setResult('notInstanceOfResult');
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createNeverUsedContextMock(),
+ $this->createMock(Consumer::class),
+ $message,
+ 'notInstanceOfResult',
+ 1,
+ new NullLogger()
+ );
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('To send a reply an instance of Result class has to returned from a Processor.');
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
}
- public function testThrowIfResultInstanceOfResultButReplyMessageNotSet()
+ public function testShouldDoNothingIfResultInstanceOfResultButReplyMessageNotSet()
{
$extension = new ReplyExtension();
$message = new NullMessage();
$message->setReplyTo('aReplyToQueue');
- $context = new Context(new NullContext());
- $context->setPsrMessage($message);
- $context->setResult(Result::ack());
+ $postReceivedMessage = new PostMessageReceived(
+ $this->createNeverUsedContextMock(),
+ $this->createMock(Consumer::class),
+ $message,
+ Result::ack(),
+ 1,
+ new NullLogger()
+ );
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('To send a reply the Result must contain a reply message.');
- $extension->onPostReceived($context);
+ $extension->onPostMessageReceived($postReceivedMessage);
}
public function testShouldSendReplyMessageToReplyQueueOnPostReceived()
@@ -110,14 +92,15 @@ public function testShouldSendReplyMessageToReplyQueueOnPostReceived()
$replyQueue = new NullQueue('aReplyName');
- $producerMock = $this->createMock(Producer::class);
+ $producerMock = $this->createMock(InteropProducer::class);
$producerMock
->expects($this->once())
->method('send')
->with($replyQueue, $replyMessage)
;
- $contextMock = $this->createMock(PsrContext::class);
+ /** @var MockObject|Context $contextMock */
+ $contextMock = $this->createMock(Context::class);
$contextMock
->expects($this->once())
->method('createQueue')
@@ -129,10 +112,37 @@ public function testShouldSendReplyMessageToReplyQueueOnPostReceived()
->willReturn($producerMock)
;
- $context = new Context($contextMock);
- $context->setPsrMessage($message);
- $context->setResult(Result::reply($replyMessage));
+ $postReceivedMessage = new PostMessageReceived(
+ $contextMock,
+ $this->createMock(Consumer::class),
+ $message,
+ Result::reply($replyMessage),
+ 1,
+ new NullLogger()
+ );
+
+ $extension->onPostMessageReceived($postReceivedMessage);
+ }
+
+ /**
+ * @return MockObject
+ */
+ protected function createInteropContextMock(): Context
+ {
+ return $this->createMock(Context::class);
+ }
+
+ /**
+ * @return MockObject
+ */
+ private function createNeverUsedContextMock(): Context
+ {
+ $contextMock = $this->createMock(Context::class);
+ $contextMock
+ ->expects($this->never())
+ ->method('createProducer')
+ ;
- $extension->onPostReceived($context);
+ return $contextMock;
}
}
diff --git a/pkg/enqueue/Tests/Consumption/ExtensionsTest.php b/pkg/enqueue/Tests/Consumption/ExtensionsTest.php
deleted file mode 100644
index 2f35bc76f..000000000
--- a/pkg/enqueue/Tests/Consumption/ExtensionsTest.php
+++ /dev/null
@@ -1,171 +0,0 @@
-assertClassImplements(ExtensionInterface::class, ChainExtension::class);
- }
-
- public function testCouldBeConstructedWithExtensionsArray()
- {
- new ChainExtension([$this->createExtension(), $this->createExtension()]);
- }
-
- public function testShouldProxyOnStartToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onStart')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onStart')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onStart($context);
- }
-
- public function testShouldProxyOnBeforeReceiveToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onBeforeReceive')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onBeforeReceive')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onBeforeReceive($context);
- }
-
- public function testShouldProxyOnPreReceiveToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onPreReceived')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onPreReceived')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onPreReceived($context);
- }
-
- public function testShouldProxyOnPostReceiveToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onPostReceived')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onPostReceived')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onPostReceived($context);
- }
-
- public function testShouldProxyOnIdleToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onIdle')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onIdle')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onIdle($context);
- }
-
- public function testShouldProxyOnInterruptedToAllInternalExtensions()
- {
- $context = $this->createContextMock();
-
- $fooExtension = $this->createExtension();
- $fooExtension
- ->expects($this->once())
- ->method('onInterrupted')
- ->with($this->identicalTo($context))
- ;
- $barExtension = $this->createExtension();
- $barExtension
- ->expects($this->once())
- ->method('onInterrupted')
- ->with($this->identicalTo($context))
- ;
-
- $extensions = new ChainExtension([$fooExtension, $barExtension]);
-
- $extensions->onInterrupted($context);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Context
- */
- protected function createContextMock()
- {
- return $this->createMock(Context::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|ExtensionInterface
- */
- protected function createExtension()
- {
- return $this->createMock(ExtensionInterface::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Consumption/FallbackSubscriptionConsumerTest.php b/pkg/enqueue/Tests/Consumption/FallbackSubscriptionConsumerTest.php
new file mode 100644
index 000000000..73fba7bfd
--- /dev/null
+++ b/pkg/enqueue/Tests/Consumption/FallbackSubscriptionConsumerTest.php
@@ -0,0 +1,253 @@
+assertTrue($rc->implementsInterface(SubscriptionConsumer::class));
+ }
+
+ public function testShouldInitSubscribersPropertyWithEmptyArray()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $this->assertAttributeSame([], 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testShouldAddConsumerAndCallbackToSubscribersPropertyOnSubscribe()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $fooCallback = function () {};
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+
+ $barCallback = function () {};
+ $barConsumer = $this->createConsumerStub('bar_queue');
+
+ $subscriptionConsumer->subscribe($fooConsumer, $fooCallback);
+ $subscriptionConsumer->subscribe($barConsumer, $barCallback);
+
+ $this->assertAttributeSame([
+ 'foo_queue' => [$fooConsumer, $fooCallback],
+ 'bar_queue' => [$barConsumer, $barCallback],
+ ], 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testThrowsIfTrySubscribeAnotherConsumerToAlreadySubscribedQueue()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $fooCallback = function () {};
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+
+ $barCallback = function () {};
+ $barConsumer = $this->createConsumerStub('foo_queue');
+
+ $subscriptionConsumer->subscribe($fooConsumer, $fooCallback);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('There is a consumer subscribed to queue: "foo_queue"');
+ $subscriptionConsumer->subscribe($barConsumer, $barCallback);
+ }
+
+ /**
+ * @doesNotPerformAssertions
+ */
+ public function testShouldAllowSubscribeSameConsumerAndCallbackSecondTime()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $fooCallback = function () {};
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+
+ $subscriptionConsumer->subscribe($fooConsumer, $fooCallback);
+ $subscriptionConsumer->subscribe($fooConsumer, $fooCallback);
+ }
+
+ public function testShouldRemoveSubscribedConsumerOnUnsubscribeCall()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+ $barConsumer = $this->createConsumerStub('bar_queue');
+
+ $subscriptionConsumer->subscribe($fooConsumer, function () {});
+ $subscriptionConsumer->subscribe($barConsumer, function () {});
+
+ // guard
+ $this->assertAttributeCount(2, 'subscribers', $subscriptionConsumer);
+
+ $subscriptionConsumer->unsubscribe($fooConsumer);
+
+ $this->assertAttributeCount(1, 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testShouldDoNothingIfTryUnsubscribeNotSubscribedQueueName()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $subscriptionConsumer->subscribe($this->createConsumerStub('foo_queue'), function () {});
+
+ // guard
+ $this->assertAttributeCount(1, 'subscribers', $subscriptionConsumer);
+
+ $subscriptionConsumer->unsubscribe($this->createConsumerStub('bar_queue'));
+
+ $this->assertAttributeCount(1, 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testShouldDoNothingIfTryUnsubscribeNotSubscribedConsumer()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $subscriptionConsumer->subscribe($this->createConsumerStub('foo_queue'), function () {});
+
+ // guard
+ $this->assertAttributeCount(1, 'subscribers', $subscriptionConsumer);
+
+ $subscriptionConsumer->unsubscribe($this->createConsumerStub('foo_queue'));
+
+ $this->assertAttributeCount(1, 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testShouldRemoveAllSubscriberOnUnsubscribeAllCall()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $subscriptionConsumer->subscribe($this->createConsumerStub('foo_queue'), function () {});
+ $subscriptionConsumer->subscribe($this->createConsumerStub('bar_queue'), function () {});
+
+ // guard
+ $this->assertAttributeCount(2, 'subscribers', $subscriptionConsumer);
+
+ $subscriptionConsumer->unsubscribeAll();
+
+ $this->assertAttributeCount(0, 'subscribers', $subscriptionConsumer);
+ }
+
+ public function testShouldConsumeMessagesFromTwoQueuesInExpectedOrder()
+ {
+ $firstMessage = $this->createMessageStub('first');
+ $secondMessage = $this->createMessageStub('second');
+ $thirdMessage = $this->createMessageStub('third');
+ $fourthMessage = $this->createMessageStub('fourth');
+ $fifthMessage = $this->createMessageStub('fifth');
+
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+ $fooConsumer
+ ->expects($this->any())
+ ->method('receiveNoWait')
+ ->willReturnOnConsecutiveCalls(null, $firstMessage, null, $secondMessage, $thirdMessage)
+ ;
+
+ $barConsumer = $this->createConsumerStub('bar_queue');
+ $barConsumer
+ ->expects($this->any())
+ ->method('receiveNoWait')
+ ->willReturnOnConsecutiveCalls($fourthMessage, null, null, $fifthMessage)
+ ;
+
+ $actualOrder = [];
+ $callback = function (InteropMessage $message, Consumer $consumer) use (&$actualOrder) {
+ $actualOrder[] = [$message->getBody(), $consumer->getQueue()->getQueueName()];
+ };
+
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $subscriptionConsumer->subscribe($fooConsumer, $callback);
+ $subscriptionConsumer->subscribe($barConsumer, $callback);
+
+ $subscriptionConsumer->consume(100);
+
+ $this->assertEquals([
+ ['fourth', 'bar_queue'],
+ ['first', 'foo_queue'],
+ ['second', 'foo_queue'],
+ ['fifth', 'bar_queue'],
+ ['third', 'foo_queue'],
+ ], $actualOrder);
+ }
+
+ public function testThrowsIfTryConsumeWithoutSubscribers()
+ {
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('No subscribers');
+ $subscriptionConsumer->consume();
+ }
+
+ public function testShouldConsumeTillTimeoutIsReached()
+ {
+ $fooConsumer = $this->createConsumerStub('foo_queue');
+ $fooConsumer
+ ->expects($this->any())
+ ->method('receiveNoWait')
+ ->willReturn(null)
+ ;
+
+ $subscriptionConsumer = new FallbackSubscriptionConsumer();
+
+ $subscriptionConsumer->subscribe($fooConsumer, function () {});
+
+ $startAt = microtime(true);
+ $subscriptionConsumer->consume(500);
+ $endAt = microtime(true);
+
+ $this->assertGreaterThan(0.49, $endAt - $startAt);
+ }
+
+ /**
+ * @param mixed|null $body
+ *
+ * @return InteropMessage|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createMessageStub($body = null)
+ {
+ $messageMock = $this->createMock(InteropMessage::class);
+ $messageMock
+ ->expects($this->any())
+ ->method('getBody')
+ ->willReturn($body)
+ ;
+
+ return $messageMock;
+ }
+
+ /**
+ * @param mixed|null $queueName
+ *
+ * @return Consumer|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createConsumerStub($queueName = null)
+ {
+ $queueMock = $this->createMock(InteropQueue::class);
+ $queueMock
+ ->expects($this->any())
+ ->method('getQueueName')
+ ->willReturn($queueName);
+
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queueMock)
+ ;
+
+ return $consumerMock;
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/Mock/BreakCycleExtension.php b/pkg/enqueue/Tests/Consumption/Mock/BreakCycleExtension.php
index b61d8dd8d..cbc2f8b1e 100644
--- a/pkg/enqueue/Tests/Consumption/Mock/BreakCycleExtension.php
+++ b/pkg/enqueue/Tests/Consumption/Mock/BreakCycleExtension.php
@@ -2,14 +2,20 @@
namespace Enqueue\Tests\Consumption\Mock;
-use Enqueue\Consumption\Context;
-use Enqueue\Consumption\EmptyExtensionTrait;
+use Enqueue\Consumption\Context\End;
+use Enqueue\Consumption\Context\InitLogger;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\Context\MessageResult;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\Context\PreSubscribe;
+use Enqueue\Consumption\Context\ProcessorException;
+use Enqueue\Consumption\Context\Start;
use Enqueue\Consumption\ExtensionInterface;
class BreakCycleExtension implements ExtensionInterface
{
- use EmptyExtensionTrait;
-
protected $cycles = 1;
private $limit;
@@ -19,15 +25,51 @@ public function __construct($limit)
$this->limit = $limit;
}
- public function onPostReceived(Context $context)
+ public function onInitLogger(InitLogger $context): void
+ {
+ }
+
+ public function onPostMessageReceived(PostMessageReceived $context): void
+ {
+ if ($this->cycles >= $this->limit) {
+ $context->interruptExecution();
+ } else {
+ ++$this->cycles;
+ }
+ }
+
+ public function onEnd(End $context): void
+ {
+ }
+
+ public function onMessageReceived(MessageReceived $context): void
+ {
+ }
+
+ public function onResult(MessageResult $context): void
+ {
+ }
+
+ public function onPreConsume(PreConsume $context): void
+ {
+ }
+
+ public function onPreSubscribe(PreSubscribe $context): void
+ {
+ }
+
+ public function onProcessorException(ProcessorException $context): void
+ {
+ }
+
+ public function onStart(Start $context): void
{
- $this->onIdle($context);
}
- public function onIdle(Context $context)
+ public function onPostConsume(PostConsume $context): void
{
if ($this->cycles >= $this->limit) {
- $context->setExecutionInterrupted(true);
+ $context->interruptExecution();
} else {
++$this->cycles;
}
diff --git a/pkg/enqueue/Tests/Consumption/Mock/DummySubscriptionConsumer.php b/pkg/enqueue/Tests/Consumption/Mock/DummySubscriptionConsumer.php
new file mode 100644
index 000000000..40351484d
--- /dev/null
+++ b/pkg/enqueue/Tests/Consumption/Mock/DummySubscriptionConsumer.php
@@ -0,0 +1,48 @@
+messages as list($message, $queueName)) {
+ /** @var InteropMessage $message */
+ /** @var string $queueName */
+ if (false == call_user_func($this->subscriptions[$queueName][1], $message, $this->subscriptions[$queueName][0])) {
+ return;
+ }
+ }
+ }
+
+ public function subscribe(Consumer $consumer, callable $callback): void
+ {
+ $this->subscriptions[$consumer->getQueue()->getQueueName()] = [$consumer, $callback];
+ }
+
+ public function unsubscribe(Consumer $consumer): void
+ {
+ unset($this->subscriptions[$consumer->getQueue()->getQueueName()]);
+ }
+
+ public function unsubscribeAll(): void
+ {
+ $this->subscriptions = [];
+ }
+
+ public function addMessage(InteropMessage $message, string $queueName): void
+ {
+ $this->messages[] = [$message, $queueName];
+ }
+}
diff --git a/pkg/enqueue/Tests/Consumption/QueueConsumerTest.php b/pkg/enqueue/Tests/Consumption/QueueConsumerTest.php
index 06988b3cc..2bcc253e7 100644
--- a/pkg/enqueue/Tests/Consumption/QueueConsumerTest.php
+++ b/pkg/enqueue/Tests/Consumption/QueueConsumerTest.php
@@ -2,60 +2,92 @@
namespace Enqueue\Tests\Consumption;
+use Enqueue\Consumption\BoundProcessor;
use Enqueue\Consumption\CallbackProcessor;
use Enqueue\Consumption\ChainExtension;
-use Enqueue\Consumption\Context;
+use Enqueue\Consumption\Context\End;
+use Enqueue\Consumption\Context\InitLogger;
+use Enqueue\Consumption\Context\MessageReceived;
+use Enqueue\Consumption\Context\MessageResult;
+use Enqueue\Consumption\Context\PostConsume;
+use Enqueue\Consumption\Context\PostMessageReceived;
+use Enqueue\Consumption\Context\PreConsume;
+use Enqueue\Consumption\Context\PreSubscribe;
+use Enqueue\Consumption\Context\ProcessorException;
+use Enqueue\Consumption\Context\Start;
use Enqueue\Consumption\Exception\InvalidArgumentException;
+use Enqueue\Consumption\Extension\ExitStatusExtension;
use Enqueue\Consumption\ExtensionInterface;
use Enqueue\Consumption\QueueConsumer;
use Enqueue\Consumption\Result;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context as PsrContext;
-use Enqueue\Psr\Message;
-use Enqueue\Psr\Processor;
-use Enqueue\Psr\Queue;
+use Enqueue\Null\NullQueue;
+use Enqueue\Test\ReadAttributeTrait;
use Enqueue\Tests\Consumption\Mock\BreakCycleExtension;
-use Enqueue\Transport\Null\NullQueue;
+use Enqueue\Tests\Consumption\Mock\DummySubscriptionConsumer;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context as InteropContext;
+use Interop\Queue\Exception\SubscriptionConsumerNotSupportedException;
+use Interop\Queue\Message;
+use Interop\Queue\Processor;
+use Interop\Queue\Queue;
+use Interop\Queue\SubscriptionConsumer;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
-class QueueConsumerTest extends \PHPUnit_Framework_TestCase
+class QueueConsumerTest extends TestCase
{
- public function testCouldBeConstructedWithConnectionAndExtensionsAsArguments()
+ use ReadAttributeTrait;
+
+ public function testShouldSetEmptyArrayToBoundProcessorsPropertyInConstructor()
{
- new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub(), null, [], null, 0);
+
+ $this->assertAttributeSame([], 'boundProcessors', $consumer);
}
- public function testCouldBeConstructedWithConnectionOnly()
+ public function testShouldSetProvidedBoundProcessorsToThePropertyInConstructor()
{
- new QueueConsumer($this->createPsrContextStub());
+ $boundProcessors = [
+ new BoundProcessor(new NullQueue('foo'), $this->createProcessorMock()),
+ new BoundProcessor(new NullQueue('bar'), $this->createProcessorMock()),
+ ];
+
+ $consumer = new QueueConsumer($this->createContextStub(), null, $boundProcessors, null, 0);
+
+ $this->assertAttributeSame($boundProcessors, 'boundProcessors', $consumer);
}
- public function testCouldBeConstructedWithConnectionAndSingleExtension()
+ public function testShouldSetNullLoggerIfNoneProvidedInConstructor()
{
- new QueueConsumer($this->createPsrContextStub(), $this->createExtension());
+ $consumer = new QueueConsumer($this->createContextStub(), null, [], null, 0);
+
+ $this->assertAttributeInstanceOf(NullLogger::class, 'logger', $consumer);
}
- public function testShouldSetEmptyArrayToBoundProcessorsPropertyInConstructor()
+ public function testShouldSetProvidedLoggerToThePropertyInConstructor()
{
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $expectedLogger = $this->createMock(LoggerInterface::class);
- $this->assertAttributeSame([], 'boundProcessors', $consumer);
+ $consumer = new QueueConsumer($this->createContextStub(), null, [], $expectedLogger, 0);
+
+ $this->assertAttributeSame($expectedLogger, 'logger', $consumer);
}
- public function testShouldAllowGetConnectionSetInConstructor()
+ public function testShouldAllowGetContextSetInConstructor()
{
- $expectedConnection = $this->createPsrContextStub();
+ $expectedContext = $this->createContextStub();
- $consumer = new QueueConsumer($expectedConnection, null, 0);
+ $consumer = new QueueConsumer($expectedContext, null, [], null, 0);
- $this->assertSame($expectedConnection, $consumer->getPsrContext());
+ $this->assertSame($expectedContext, $consumer->getContext());
}
public function testThrowIfQueueNameEmptyOnBind()
{
$processorMock = $this->createProcessorMock();
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('The queue name must be not empty.');
@@ -66,7 +98,7 @@ public function testThrowIfQueueAlreadyBoundToProcessorOnBind()
{
$processorMock = $this->createProcessorMock();
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
$consumer->bind(new NullQueue('theQueueName'), $processorMock);
@@ -80,31 +112,35 @@ public function testShouldAllowBindProcessorToQueue()
$queue = new NullQueue('theQueueName');
$processorMock = $this->createProcessorMock();
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
$consumer->bind($queue, $processorMock);
- $this->assertAttributeSame(['theQueueName' => [$queue, $processorMock]], 'boundProcessors', $consumer);
+ $this->assertAttributeEquals(
+ ['theQueueName' => new BoundProcessor($queue, $processorMock)],
+ 'boundProcessors',
+ $consumer
+ );
}
public function testThrowIfQueueNeitherInstanceOfQueueNorString()
{
$processorMock = $this->createProcessorMock();
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
$this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('The argument must be an instance of Enqueue\Psr\Queue but got stdClass.');
+ $this->expectExceptionMessage('The argument must be an instance of Interop\Queue\Queue but got stdClass.');
$consumer->bind(new \stdClass(), $processorMock);
}
- public function testThrowIfProcessorNeitherInstanceOfProcessorNorCallable()
+ public function testCouldSetGetReceiveTimeout()
{
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
- $this->expectException(InvalidArgumentException::class);
- $this->expectExceptionMessage('The argument must be an instance of Enqueue\Psr\Processor but got stdClass.');
- $consumer->bind(new NullQueue(''), new \stdClass());
+ $consumer->setReceiveTimeout(123456);
+
+ $this->assertSame(123456, $consumer->getReceiveTimeout());
}
public function testShouldAllowBindCallbackToQueueName()
@@ -115,7 +151,7 @@ public function testShouldAllowBindCallbackToQueueName()
$queueName = 'theQueueName';
$queue = new NullQueue($queueName);
- $context = $this->createMock(PsrContext::class);
+ $context = $this->createContextWithoutSubscriptionConsumerMock();
$context
->expects($this->once())
->method('createQueue')
@@ -123,48 +159,159 @@ public function testShouldAllowBindCallbackToQueueName()
->willReturn($queue)
;
- $consumer = new QueueConsumer($context, null, 0);
+ $consumer = new QueueConsumer($context);
- $consumer->bind($queueName, $callback);
+ $consumer->bindCallback($queueName, $callback);
$boundProcessors = $this->readAttribute($consumer, 'boundProcessors');
- $this->assertInternalType('array', $boundProcessors);
+ self::assertIsArray($boundProcessors);
$this->assertCount(1, $boundProcessors);
$this->assertArrayHasKey($queueName, $boundProcessors);
- $this->assertInternalType('array', $boundProcessors[$queueName]);
- $this->assertCount(2, $boundProcessors[$queueName]);
- $this->assertSame($queue, $boundProcessors[$queueName][0]);
- $this->assertInstanceOf(CallbackProcessor::class, $boundProcessors[$queueName][1]);
+ $this->assertInstanceOf(BoundProcessor::class, $boundProcessors[$queueName]);
+ $this->assertSame($queue, $boundProcessors[$queueName]->getQueue());
+ $this->assertInstanceOf(CallbackProcessor::class, $boundProcessors[$queueName]->getProcessor());
}
public function testShouldReturnSelfOnBind()
{
$processorMock = $this->createProcessorMock();
- $consumer = new QueueConsumer($this->createPsrContextStub(), null, 0);
+ $consumer = new QueueConsumer($this->createContextStub());
- $this->assertSame($consumer, $consumer->bind(new NullQueue('aQueueName'), $processorMock));
+ $this->assertSame($consumer, $consumer->bind(new NullQueue('foo_queue'), $processorMock));
}
- public function testShouldSubscribeToGivenQueueAndQuitAfterFifthIdleCycle()
+ public function testShouldUseContextSubscriptionConsumerIfSupport()
{
$expectedQueue = new NullQueue('theQueueName');
- $messageConsumerMock = $this->createMock(Consumer::class);
- $messageConsumerMock
+ $contextSubscriptionConsumer = $this->createSubscriptionConsumerMock();
+ $contextSubscriptionConsumer
+ ->expects($this->once())
+ ->method('consume')
+ ;
+
+ $fallbackSubscriptionConsumer = $this->createSubscriptionConsumerMock();
+ $fallbackSubscriptionConsumer
+ ->expects($this->never())
+ ->method('consume')
+ ;
+
+ $contextMock = $this->createMock(InteropContext::class);
+ $contextMock
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($expectedQueue))
+ ->willReturn($this->createConsumerStub())
+ ;
+ $contextMock
+ ->expects($this->once())
+ ->method('createSubscriptionConsumer')
+ ->willReturn($contextSubscriptionConsumer)
+ ;
+
+ $processorMock = $this->createProcessorMock();
+ $processorMock
+ ->expects($this->never())
+ ->method('process')
+ ;
+
+ $queueConsumer = new QueueConsumer($contextMock, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($fallbackSubscriptionConsumer);
+ $queueConsumer->bind($expectedQueue, $processorMock);
+ $queueConsumer->consume();
+ }
+
+ public function testShouldUseFallbackSubscriptionConsumerIfNotSupported()
+ {
+ $expectedQueue = new NullQueue('theQueueName');
+
+ $contextSubscriptionConsumer = $this->createSubscriptionConsumerMock();
+ $contextSubscriptionConsumer
+ ->expects($this->never())
+ ->method('consume')
+ ;
+
+ $fallbackSubscriptionConsumer = $this->createSubscriptionConsumerMock();
+ $fallbackSubscriptionConsumer
+ ->expects($this->once())
+ ->method('consume')
+ ;
+
+ $contextMock = $this->createContextWithoutSubscriptionConsumerMock();
+ $contextMock
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($expectedQueue))
+ ->willReturn($this->createConsumerStub())
+ ;
+ $contextMock
+ ->expects($this->once())
+ ->method('createSubscriptionConsumer')
+ ->willThrowException(SubscriptionConsumerNotSupportedException::providerDoestNotSupportIt())
+ ;
+
+ $processorMock = $this->createProcessorMock();
+ $processorMock
+ ->expects($this->never())
+ ->method('process')
+ ;
+
+ $queueConsumer = new QueueConsumer($contextMock, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($fallbackSubscriptionConsumer);
+ $queueConsumer->bind($expectedQueue, $processorMock);
+ $queueConsumer->consume();
+ }
+
+ public function testShouldSubscribeToGivenQueueWithExpectedTimeout()
+ {
+ $expectedQueue = new NullQueue('theQueueName');
+
+ $subscriptionConsumerMock = $this->createSubscriptionConsumerMock();
+ $subscriptionConsumerMock
+ ->expects($this->once())
+ ->method('consume')
+ ->with(12345)
+ ;
+
+ $contextMock = $this->createContextWithoutSubscriptionConsumerMock();
+ $contextMock
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($expectedQueue))
+ ->willReturn($this->createConsumerStub())
+ ;
+
+ $processorMock = $this->createProcessorMock();
+ $processorMock
+ ->expects($this->never())
+ ->method('process')
+ ;
+
+ $queueConsumer = new QueueConsumer($contextMock, new BreakCycleExtension(1), [], null, 12345);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind($expectedQueue, $processorMock);
+ $queueConsumer->consume();
+ }
+
+ public function testShouldSubscribeToGivenQueueAndQuitAfterFifthConsumeCycle()
+ {
+ $expectedQueue = new NullQueue('theQueueName');
+
+ $subscriptionConsumerMock = $this->createSubscriptionConsumerMock();
+ $subscriptionConsumerMock
->expects($this->exactly(5))
- ->method('receive')
- ->willReturn(null)
+ ->method('consume')
;
- $contextMock = $this->createMock(PsrContext::class);
+ $contextMock = $this->createContextWithoutSubscriptionConsumerMock();
$contextMock
->expects($this->once())
->method('createConsumer')
->with($this->identicalTo($expectedQueue))
- ->willReturn($messageConsumerMock)
+ ->willReturn($this->createConsumerStub())
;
$processorMock = $this->createProcessorMock();
@@ -173,17 +320,30 @@ public function testShouldSubscribeToGivenQueueAndQuitAfterFifthIdleCycle()
->method('process')
;
- $queueConsumer = new QueueConsumer($contextMock, new BreakCycleExtension(5), 0);
+ $queueConsumer = new QueueConsumer($contextMock, new BreakCycleExtension(5));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
$queueConsumer->bind($expectedQueue, $processorMock);
$queueConsumer->consume();
}
public function testShouldProcessFiveMessagesAndQuit()
{
- $messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
+ $fooQueue = new NullQueue('foo_queue');
+
+ $firstMessageMock = $this->createMessageMock();
+ $secondMessageMock = $this->createMessageMock();
+ $thirdMessageMock = $this->createMessageMock();
+ $fourthMessageMock = $this->createMessageMock();
+ $fifthMessageMock = $this->createMessageMock();
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($firstMessageMock, 'foo_queue');
+ $subscriptionConsumerMock->addMessage($secondMessageMock, 'foo_queue');
+ $subscriptionConsumerMock->addMessage($thirdMessageMock, 'foo_queue');
+ $subscriptionConsumerMock->addMessage($fourthMessageMock, 'foo_queue');
+ $subscriptionConsumerMock->addMessage($fifthMessageMock, 'foo_queue');
+
+ $contextStub = $this->createContextStub();
$processorMock = $this->createProcessorMock();
$processorMock
@@ -192,8 +352,9 @@ public function testShouldProcessFiveMessagesAndQuit()
->willReturn(Result::ACK)
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(5), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(5));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind($fooQueue, $processorMock);
$queueConsumer->consume();
}
@@ -201,14 +362,18 @@ public function testShouldProcessFiveMessagesAndQuit()
public function testShouldAckMessageIfProcessorReturnSuchStatus()
{
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $messageConsumerStub
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+ $consumerStub
->expects($this->once())
->method('acknowledge')
->with($this->identicalTo($messageMock))
;
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -218,8 +383,9 @@ public function testShouldAckMessageIfProcessorReturnSuchStatus()
->willReturn(Result::ACK)
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
@@ -227,9 +393,13 @@ public function testShouldAckMessageIfProcessorReturnSuchStatus()
public function testThrowIfProcessorReturnNull()
{
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -239,8 +409,9 @@ public function testThrowIfProcessorReturnNull()
->willReturn(null)
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Status is not supported');
@@ -250,14 +421,18 @@ public function testThrowIfProcessorReturnNull()
public function testShouldRejectMessageIfProcessorReturnSuchStatus()
{
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $messageConsumerStub
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+ $consumerStub
->expects($this->once())
->method('reject')
->with($this->identicalTo($messageMock), false)
;
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -267,8 +442,43 @@ public function testShouldRejectMessageIfProcessorReturnSuchStatus()
->willReturn(Result::REJECT)
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldDoNothingIfProcessorReturnsAlreadyAcknowledged()
+ {
+ $messageMock = $this->createMessageMock();
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+ $consumerStub
+ ->expects($this->never())
+ ->method('reject')
+ ;
+ $consumerStub
+ ->expects($this->never())
+ ->method('acknowledge')
+ ;
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorMock();
+ $processorMock
+ ->expects($this->once())
+ ->method('process')
+ ->with($this->identicalTo($messageMock))
+ ->willReturn(Result::ALREADY_ACKNOWLEDGED)
+ ;
+
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
@@ -276,14 +486,18 @@ public function testShouldRejectMessageIfProcessorReturnSuchStatus()
public function testShouldRequeueMessageIfProcessorReturnSuchStatus()
{
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $messageConsumerStub
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+ $consumerStub
->expects($this->once())
->method('reject')
->with($this->identicalTo($messageMock), true)
;
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -293,8 +507,9 @@ public function testShouldRequeueMessageIfProcessorReturnSuchStatus()
->willReturn(Result::REQUEUE)
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
@@ -302,9 +517,13 @@ public function testShouldRequeueMessageIfProcessorReturnSuchStatus()
public function testThrowIfProcessorReturnInvalidStatus()
{
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -314,8 +533,9 @@ public function testThrowIfProcessorReturnInvalidStatus()
->willReturn('invalidStatus')
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$this->expectException(\LogicException::class);
$this->expectExceptionMessage('Status is not supported: invalidStatus');
@@ -327,17 +547,21 @@ public function testShouldNotPassMessageToProcessorIfItWasProcessedByExtension()
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onPreReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) {
- $context->setResult(Result::ACK);
+ ->method('onMessageReceived')
+ ->with($this->isInstanceOf(MessageReceived::class))
+ ->willReturnCallback(function (MessageReceived $context) {
+ $context->setResult(Result::ack());
})
;
$messageMock = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($messageMock);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($messageMock, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -346,17 +570,46 @@ public function testShouldNotPassMessageToProcessorIfItWasProcessedByExtension()
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallOnInitLoggerExtensionMethod()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorMock();
+
+ $logger = $this->createMock(LoggerInterface::class);
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->once())
+ ->method('onInitLogger')
+ ->with($this->isInstanceOf(InitLogger::class))
+ ->willReturnCallback(function (InitLogger $context) use ($logger) {
+ $this->assertSame($logger, $context->getLogger());
+ })
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, [], $logger);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
public function testShouldCallOnStartExtensionMethod()
{
- $messageConsumerStub = $this->createMessageConsumerStub($message = null);
+ $consumerStub = $this->createConsumerStub('foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
@@ -364,282 +617,488 @@ public function testShouldCallOnStartExtensionMethod()
$extension
->expects($this->once())
->method('onStart')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertNull($context->getPsrConsumer());
- $this->assertNull($context->getPsrProcessor());
- $this->assertNull($context->getLogger());
- $this->assertNull($context->getPsrMessage());
- $this->assertNull($context->getException());
- $this->assertNull($context->getResult());
- $this->assertNull($context->getPsrQueue());
- $this->assertFalse($context->isExecutionInterrupted());
+ ->with($this->isInstanceOf(Start::class))
+ ->willReturnCallback(function (Start $context) use ($contextStub) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertInstanceOf(NullLogger::class, $context->getLogger());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldCallOnIdleExtensionMethod()
+ public function testShouldCallOnStartWithLoggerProvidedInConstructor()
{
- $messageConsumerStub = $this->createMessageConsumerStub($message = null);
+ $consumerStub = $this->createConsumerStub('foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
+ $expectedLogger = $this->createMock(LoggerInterface::class);
+
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onIdle')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
+ ->method('onStart')
+ ->with($this->isInstanceOf(Start::class))
+ ->willReturnCallback(function (Start $context) use ($expectedLogger) {
+ $this->assertSame($expectedLogger, $context->getLogger());
+ })
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, [], $expectedLogger);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldInterruptExecutionOnStart()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorMock();
+ $processorMock
+ ->expects($this->never())
+ ->method('process')
+ ;
+
+ $expectedLogger = $this->createMock(LoggerInterface::class);
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->once())
+ ->method('onStart')
+ ->willReturnCallback(function (Start $context) {
+ $context->interruptExecution();
+ })
+ ;
+ $extension
+ ->expects($this->once())
+ ->method('onEnd')
+ ;
+ $extension
+ ->expects($this->never())
+ ->method('onPreConsume')
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, [], $expectedLogger);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallPreSubscribeExtensionMethod()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorMock();
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->once())
+ ->method('onPreSubscribe')
+ ->with($this->isInstanceOf(PreSubscribe::class))
+ ->willReturnCallback(function (PreSubscribe $context) use ($contextStub, $consumerStub, $processorMock) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($consumerStub, $context->getConsumer());
+ $this->assertSame($processorMock, $context->getProcessor());
+ $this->assertInstanceOf(NullLogger::class, $context->getLogger());
+ })
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallPreSubscribeForEachBoundProcessor()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorMock();
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->exactly(3))
+ ->method('onPreSubscribe')
+ ->with($this->isInstanceOf(PreSubscribe::class))
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+ $queueConsumer->bind(new NullQueue('bar_queue'), $processorMock);
+ $queueConsumer->bind(new NullQueue('baz_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallOnPostConsumeExtensionMethod()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $subscriptionConsumer = new DummySubscriptionConsumer();
+
+ $processorMock = $this->createProcessorMock();
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->once())
+ ->method('onPostConsume')
+ ->with($this->isInstanceOf(PostConsume::class))
+ ->willReturnCallback(function (PostConsume $context) use ($contextStub, $subscriptionConsumer) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($subscriptionConsumer, $context->getSubscriptionConsumer());
+ $this->assertSame(1, $context->getCycle());
+ $this->assertSame(0, $context->getReceivedMessagesCount());
+ $this->assertGreaterThan(1, $context->getStartTime());
$this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getPsrMessage());
- $this->assertNull($context->getException());
- $this->assertNull($context->getResult());
$this->assertFalse($context->isExecutionInterrupted());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumer);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldCallOnBeforeReceiveExtensionMethod()
+ public function testShouldCallOnPreConsumeExtensionMethod()
{
- $expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
+ $consumerStub = $this->createConsumerStub('foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorStub();
- $queue = new NullQueue('aQueueName');
+ $queue = new NullQueue('foo_queue');
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onBeforeReceive')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock,
- $expectedMessage,
- $queue
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
+ ->method('onPreConsume')
+ ->with($this->isInstanceOf(PreConsume::class))
+ ->willReturnCallback(function (PreConsume $context) use ($contextStub) {
+ $this->assertSame($contextStub, $context->getContext());
$this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getPsrMessage());
- $this->assertNull($context->getException());
- $this->assertNull($context->getResult());
+ $this->assertInstanceOf(SubscriptionConsumer::class, $context->getSubscriptionConsumer());
+ $this->assertSame(10000, $context->getReceiveTimeout());
+ $this->assertSame(1, $context->getCycle());
+ $this->assertGreaterThan(0, $context->getStartTime());
$this->assertFalse($context->isExecutionInterrupted());
- $this->assertSame($queue, $context->getPsrQueue());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind($queue, $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallOnPreConsumeExpectedAmountOfTimes()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorStub();
+
+ $queue = new NullQueue('foo_queue');
+
+ $extension = $this->createExtension();
+ $extension
+ ->expects($this->exactly(3))
+ ->method('onPreConsume')
+ ;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(3)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
$queueConsumer->bind($queue, $processorMock);
$queueConsumer->consume();
}
- public function testShouldCallOnPreReceivedAndPostReceivedExtensionMethods()
+ public function testShouldCallOnPreReceivedExtensionMethodWithExpectedContext()
{
$expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorStub();
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onPreReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
+ ->method('onMessageReceived')
+ ->with($this->isInstanceOf(MessageReceived::class))
+ ->willReturnCallback(function (MessageReceived $context) use (
$contextStub,
- $messageConsumerStub,
+ $consumerStub,
$processorMock,
$expectedMessage
) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($expectedMessage, $context->getPsrMessage());
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($consumerStub, $context->getConsumer());
+ $this->assertSame($processorMock, $context->getProcessor());
+ $this->assertSame($expectedMessage, $context->getMessage());
$this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getException());
$this->assertNull($context->getResult());
- $this->assertFalse($context->isExecutionInterrupted());
})
;
+
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldCallOnResultExtensionMethodWithExpectedContext()
+ {
+ $expectedMessage = $this->createMessageMock();
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorStub();
+
+ $extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onPostReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock,
- $expectedMessage
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($expectedMessage, $context->getPsrMessage());
+ ->method('onResult')
+ ->with($this->isInstanceOf(MessageResult::class))
+ ->willReturnCallback(function (MessageResult $context) use ($contextStub, $expectedMessage) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($expectedMessage, $context->getMessage());
$this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getException());
$this->assertSame(Result::ACK, $context->getResult());
- $this->assertFalse($context->isExecutionInterrupted());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldAllowInterruptConsumingOnIdle()
+ public function testShouldCallOnProcessorExceptionExtensionMethodWithExpectedContext()
{
- $messageConsumerStub = $this->createMessageConsumerStub($message = null);
+ $exception = new \LogicException('Exception exception');
+
+ $expectedMessage = $this->createMessageMock();
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
- $processorMock = $this->createProcessorMock();
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorStub();
+ $processorMock
+ ->expects($this->once())
+ ->method('process')
+ ->willThrowException($exception)
+ ;
$extension = $this->createExtension();
$extension
- ->expects($this->once())
- ->method('onIdle')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) {
- $context->setExecutionInterrupted(true);
- })
+ ->expects($this->never())
+ ->method('onResult')
;
$extension
->expects($this->once())
- ->method('onInterrupted')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
+ ->method('onProcessorException')
+ ->with($this->isInstanceOf(ProcessorException::class))
+ ->willReturnCallback(function (ProcessorException $context) use ($contextStub, $expectedMessage, $exception) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($expectedMessage, $context->getMessage());
+ $this->assertSame($exception, $context->getException());
+ $this->assertGreaterThan(1, $context->getReceivedAt());
$this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getPsrMessage());
- $this->assertNull($context->getException());
$this->assertNull($context->getResult());
- $this->assertTrue($context->isExecutionInterrupted());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Exception exception');
$queueConsumer->consume();
}
- public function testShouldCloseSessionWhenConsumptionInterrupted()
+ public function testShouldContinueConsumptionIfResultSetOnProcessorExceptionExtension()
{
- $messageConsumerStub = $this->createMessageConsumerStub($message = null);
+ $result = Result::ack();
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
- $contextStub
+ $expectedMessage = $this->createMessageMock();
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorStub();
+ $processorMock
->expects($this->once())
- ->method('close')
+ ->method('process')
+ ->willThrowException(new \LogicException())
;
- $processorMock = $this->createProcessorMock();
-
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onIdle')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) {
- $context->setExecutionInterrupted(true);
+ ->method('onProcessorException')
+ ->willReturnCallback(function (ProcessorException $context) use ($result) {
+ $context->setResult($result);
+ })
+ ;
+ $extension
+ ->expects($this->once())
+ ->method('onResult')
+ ->willReturnCallback(function (MessageResult $context) use ($result) {
+ $this->assertSame($result, $context->getResult());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldCloseSessionWhenConsumptionInterruptedByException()
+ public function testShouldCallOnPostMessageReceivedExtensionMethodWithExpectedContext()
{
- $expectedException = new \Exception();
+ $expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($message = $this->createMessageMock());
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
- $contextStub
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
+ $processorMock = $this->createProcessorStub();
+
+ $extension = $this->createExtension();
+ $extension
->expects($this->once())
- ->method('close')
+ ->method('onPostMessageReceived')
+ ->with($this->isInstanceOf(PostMessageReceived::class))
+ ->willReturnCallback(function (PostMessageReceived $context) use (
+ $contextStub,
+ $expectedMessage
+ ) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertSame($expectedMessage, $context->getMessage());
+ $this->assertInstanceOf(NullLogger::class, $context->getLogger());
+ $this->assertSame(Result::ACK, $context->getResult());
+ $this->assertFalse($context->isExecutionInterrupted());
+ })
;
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
+
+ $queueConsumer->consume();
+ }
+
+ public function testShouldAllowInterruptConsumingOnPostConsume()
+ {
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
+
$processorMock = $this->createProcessorMock();
- $processorMock
+
+ $extension = $this->createExtension();
+ $extension
->expects($this->once())
- ->method('process')
- ->willThrowException($expectedException)
+ ->method('onPostConsume')
+ ->with($this->isInstanceOf(PostConsume::class))
+ ->willReturnCallback(function (PostConsume $context) {
+ $context->interruptExecution();
+ })
+ ;
+ $extension
+ ->expects($this->once())
+ ->method('onEnd')
+ ->with($this->isInstanceOf(End::class))
+ ->willReturnCallback(function (End $context) use ($contextStub) {
+ $this->assertSame($contextStub, $context->getContext());
+ $this->assertInstanceOf(NullLogger::class, $context->getLogger());
+ $this->assertGreaterThan(1, $context->getStartTime());
+ $this->assertGreaterThan(1, $context->getEndTime());
+ })
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
-
- try {
- $queueConsumer->consume();
- } catch (\Exception $e) {
- $this->assertSame($expectedException, $e);
- $this->assertNull($e->getPrevious());
-
- return;
- }
+ $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer(new DummySubscriptionConsumer());
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
- $this->fail('Exception throw is expected.');
+ $queueConsumer->consume();
}
- public function testShouldSetMainExceptionAsPreviousToExceptionThrownOnInterrupt()
+ public function testShouldSetMainExceptionAsPreviousToExceptionThrownOnProcessorException()
{
$mainException = new \Exception();
$expectedException = new \Exception();
- $messageConsumerStub = $this->createMessageConsumerStub($message = $this->createMessageMock());
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($this->createMessageMock(), 'foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -651,13 +1110,14 @@ public function testShouldSetMainExceptionAsPreviousToExceptionThrownOnInterrupt
$extension = $this->createExtension();
$extension
->expects($this->atLeastOnce())
- ->method('onInterrupted')
+ ->method('onProcessorException')
->willThrowException($expectedException)
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
try {
$queueConsumer->consume();
@@ -671,63 +1131,16 @@ public function testShouldSetMainExceptionAsPreviousToExceptionThrownOnInterrupt
$this->fail('Exception throw is expected.');
}
- public function testShouldAllowInterruptConsumingOnPreReceiveButProcessCurrentMessage()
+ public function testShouldAllowInterruptConsumingOnPostMessageReceived()
{
$expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
-
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
-
- $processorMock = $this->createProcessorMock();
- $processorMock
- ->expects($this->once())
- ->method('process')
- ->willReturn(Result::ACK)
- ;
- $extension = $this->createExtension();
- $extension
- ->expects($this->once())
- ->method('onPreReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) {
- $context->setExecutionInterrupted(true);
- })
- ;
- $extension
- ->expects($this->atLeastOnce())
- ->method('onInterrupted')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock,
- $expectedMessage
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($expectedMessage, $context->getPsrMessage());
- $this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getException());
- $this->assertSame(Result::ACK, $context->getResult());
- $this->assertTrue($context->isExecutionInterrupted());
- })
- ;
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
- $chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $consumerStub = $this->createConsumerStub('foo_queue');
- $queueConsumer->consume();
- }
-
- public function testShouldAllowInterruptConsumingOnPostReceive()
- {
- $expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
-
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -739,47 +1152,37 @@ public function testShouldAllowInterruptConsumingOnPostReceive()
$extension = $this->createExtension();
$extension
->expects($this->once())
- ->method('onPostReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) {
- $context->setExecutionInterrupted(true);
+ ->method('onPostMessageReceived')
+ ->with($this->isInstanceOf(PostMessageReceived::class))
+ ->willReturnCallback(function (PostMessageReceived $context) {
+ $context->interruptExecution();
})
;
$extension
->expects($this->atLeastOnce())
- ->method('onInterrupted')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock,
- $expectedMessage
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($expectedMessage, $context->getPsrMessage());
- $this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getException());
- $this->assertSame(Result::ACK, $context->getResult());
- $this->assertTrue($context->isExecutionInterrupted());
- })
+ ->method('onEnd')
+ ->with($this->isInstanceOf(End::class))
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldCallOnInterruptedIfExceptionThrow()
+ public function testShouldNotCallOnEndIfExceptionThrow()
{
$expectedException = new \Exception('Process failed');
$expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -790,30 +1193,14 @@ public function testShouldCallOnInterruptedIfExceptionThrow()
$extension = $this->createExtension();
$extension
- ->expects($this->atLeastOnce())
- ->method('onInterrupted')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use (
- $contextStub,
- $messageConsumerStub,
- $processorMock,
- $expectedMessage,
- $expectedException
- ) {
- $this->assertSame($contextStub, $context->getPsrContext());
- $this->assertSame($messageConsumerStub, $context->getPsrConsumer());
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($expectedMessage, $context->getPsrMessage());
- $this->assertSame($expectedException, $context->getException());
- $this->assertInstanceOf(NullLogger::class, $context->getLogger());
- $this->assertNull($context->getResult());
- $this->assertTrue($context->isExecutionInterrupted());
- })
+ ->expects($this->never())
+ ->method('onEnd')
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$this->expectException(\Exception::class);
$this->expectExceptionMessage('Process failed');
@@ -823,9 +1210,13 @@ public function testShouldCallOnInterruptedIfExceptionThrow()
public function testShouldCallExtensionPassedOnRuntime()
{
$expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -835,39 +1226,59 @@ public function testShouldCallExtensionPassedOnRuntime()
;
$runtimeExtension = $this->createExtension();
+ $runtimeExtension
+ ->expects($this->once())
+ ->method('onInitLogger')
+ ->with($this->isInstanceOf(InitLogger::class))
+ ;
$runtimeExtension
->expects($this->once())
->method('onStart')
- ->with($this->isInstanceOf(Context::class))
+ ->with($this->isInstanceOf(Start::class))
+ ;
+ $runtimeExtension
+ ->expects($this->once())
+ ->method('onPreSubscribe')
+ ->with($this->isInstanceOf(PreSubscribe::class))
+ ;
+ $runtimeExtension
+ ->expects($this->once())
+ ->method('onPreConsume')
+ ->with($this->isInstanceOf(PreConsume::class))
;
$runtimeExtension
->expects($this->once())
- ->method('onBeforeReceive')
- ->with($this->isInstanceOf(Context::class))
+ ->method('onMessageReceived')
+ ->with($this->isInstanceOf(MessageReceived::class))
;
$runtimeExtension
->expects($this->once())
- ->method('onPreReceived')
- ->with($this->isInstanceOf(Context::class))
+ ->method('onResult')
+ ->with($this->isInstanceOf(MessageResult::class))
;
$runtimeExtension
->expects($this->once())
- ->method('onPostReceived')
- ->with($this->isInstanceOf(Context::class))
+ ->method('onPostMessageReceived')
+ ->with($this->isInstanceOf(PostMessageReceived::class))
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1), 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(1));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume(new ChainExtension([$runtimeExtension]));
}
- public function testShouldChangeLoggerOnStart()
+ public function testShouldChangeLoggerOnInitLogger()
{
$expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($expectedMessage, 'foo_queue');
+
+ $consumerStub = $this->createConsumerStub('foo_queue');
+
+ $contextStub = $this->createContextStub($consumerStub);
$processorMock = $this->createProcessorMock();
$processorMock
@@ -879,134 +1290,204 @@ public function testShouldChangeLoggerOnStart()
$expectedLogger = new NullLogger();
$extension = $this->createExtension();
+ $extension
+ ->expects($this->atLeastOnce())
+ ->method('onInitLogger')
+ ->with($this->isInstanceOf(InitLogger::class))
+ ->willReturnCallback(function (InitLogger $context) use ($expectedLogger) {
+ $context->changeLogger($expectedLogger);
+ })
+ ;
$extension
->expects($this->atLeastOnce())
->method('onStart')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use ($expectedLogger) {
- $context->setLogger($expectedLogger);
+ ->with($this->isInstanceOf(Start::class))
+ ->willReturnCallback(function (Start $context) use ($expectedLogger) {
+ $this->assertSame($expectedLogger, $context->getLogger());
})
;
$extension
->expects($this->atLeastOnce())
- ->method('onBeforeReceive')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use ($expectedLogger) {
+ ->method('onPreSubscribe')
+ ->with($this->isInstanceOf(PreSubscribe::class))
+ ->willReturnCallback(function (PreSubscribe $context) use ($expectedLogger) {
$this->assertSame($expectedLogger, $context->getLogger());
})
;
$extension
->expects($this->atLeastOnce())
- ->method('onPreReceived')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use ($expectedLogger) {
+ ->method('onPreConsume')
+ ->with($this->isInstanceOf(PreConsume::class))
+ ->willReturnCallback(function (PreConsume $context) use ($expectedLogger) {
+ $this->assertSame($expectedLogger, $context->getLogger());
+ })
+ ;
+ $extension
+ ->expects($this->atLeastOnce())
+ ->method('onMessageReceived')
+ ->with($this->isInstanceOf(MessageReceived::class))
+ ->willReturnCallback(function (MessageReceived $context) use ($expectedLogger) {
$this->assertSame($expectedLogger, $context->getLogger());
})
;
$chainExtensions = new ChainExtension([$extension, new BreakCycleExtension(1)]);
- $queueConsumer = new QueueConsumer($contextStub, $chainExtensions, 0);
- $queueConsumer->bind(new NullQueue('aQueueName'), $processorMock);
+ $queueConsumer = new QueueConsumer($contextStub, $chainExtensions);
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
+ $queueConsumer->bind(new NullQueue('foo_queue'), $processorMock);
$queueConsumer->consume();
}
- public function testShouldCallEachQueueOneByOne()
+ public function testShouldCallProcessorAsMessageComeAlong()
{
- $expectedMessage = $this->createMessageMock();
- $messageConsumerStub = $this->createMessageConsumerStub($expectedMessage);
+ $queue1 = new NullQueue('foo_queue');
+ $queue2 = new NullQueue('bar_queue');
+
+ $firstMessage = $this->createMessageMock();
+ $secondMessage = $this->createMessageMock();
+ $thirdMessage = $this->createMessageMock();
+
+ $subscriptionConsumerMock = new DummySubscriptionConsumer();
+ $subscriptionConsumerMock->addMessage($firstMessage, 'foo_queue');
+ $subscriptionConsumerMock->addMessage($secondMessage, 'bar_queue');
+ $subscriptionConsumerMock->addMessage($thirdMessage, 'foo_queue');
- $contextStub = $this->createPsrContextStub($messageConsumerStub);
+ $fooConsumerStub = $this->createConsumerStub($queue1);
+ $barConsumerStub = $this->createConsumerStub($queue2);
+
+ $consumers = [
+ 'foo_queue' => $fooConsumerStub,
+ 'bar_queue' => $barConsumerStub,
+ ];
+
+ $contextStub = $this->createContextWithoutSubscriptionConsumerMock();
+ $contextStub
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $queueName) {
+ return new NullQueue($queueName);
+ })
+ ;
+ $contextStub
+ ->expects($this->any())
+ ->method('createConsumer')
+ ->willReturnCallback(function (Queue $queue) use ($consumers) {
+ return $consumers[$queue->getQueueName()];
+ })
+ ;
$processorMock = $this->createProcessorStub();
$anotherProcessorMock = $this->createProcessorStub();
- $queue1 = new NullQueue('aQueueName');
- $queue2 = new NullQueue('aAnotherQueueName');
+ /** @var MessageReceived[] $actualContexts */
+ $actualContexts = [];
$extension = $this->createExtension();
$extension
- ->expects($this->at(1))
- ->method('onBeforeReceive')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use ($processorMock, $queue1) {
- $this->assertSame($processorMock, $context->getPsrProcessor());
- $this->assertSame($queue1, $context->getPsrQueue());
- })
- ;
- $extension
- ->expects($this->at(4))
- ->method('onBeforeReceive')
- ->with($this->isInstanceOf(Context::class))
- ->willReturnCallback(function (Context $context) use ($anotherProcessorMock, $queue2) {
- $this->assertSame($anotherProcessorMock, $context->getPsrProcessor());
- $this->assertSame($queue2, $context->getPsrQueue());
+ ->expects($this->any())
+ ->method('onMessageReceived')
+ ->with($this->isInstanceOf(MessageReceived::class))
+ ->willReturnCallback(function (MessageReceived $context) use (&$actualContexts) {
+ $actualContexts[] = clone $context;
})
;
- $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(2), 0);
+ $queueConsumer = new QueueConsumer($contextStub, new BreakCycleExtension(3));
+ $queueConsumer->setFallbackSubscriptionConsumer($subscriptionConsumerMock);
$queueConsumer
->bind($queue1, $processorMock)
->bind($queue2, $anotherProcessorMock)
;
$queueConsumer->consume(new ChainExtension([$extension]));
+
+ $this->assertCount(3, $actualContexts);
+
+ $this->assertSame($firstMessage, $actualContexts[0]->getMessage());
+ $this->assertSame($secondMessage, $actualContexts[1]->getMessage());
+ $this->assertSame($thirdMessage, $actualContexts[2]->getMessage());
+
+ $this->assertSame($fooConsumerStub, $actualContexts[0]->getConsumer());
+ $this->assertSame($barConsumerStub, $actualContexts[1]->getConsumer());
+ $this->assertSame($fooConsumerStub, $actualContexts[2]->getConsumer());
+ }
+
+ public function testCaptureExitStatus()
+ {
+ $testExitCode = 5;
+
+ $stubExtension = $this->createExtension();
+
+ $stubExtension
+ ->expects($this->once())
+ ->method('onStart')
+ ->with($this->isInstanceOf(Start::class))
+ ->willReturnCallback(function (Start $context) use ($testExitCode) {
+ $context->interruptExecution($testExitCode);
+ })
+ ;
+
+ $exitExtension = new ExitStatusExtension();
+
+ $consumer = new QueueConsumer($this->createContextStub(), $stubExtension);
+ $consumer->consume(new ChainExtension([$exitExtension]));
+
+ $this->assertEquals($testExitCode, $exitExtension->getExitStatus());
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Consumer
- * @param null|mixed $message
+ * @return \PHPUnit\Framework\MockObject\MockObject
*/
- protected function createMessageConsumerStub($message = null)
+ private function createContextWithoutSubscriptionConsumerMock(): InteropContext
{
- $messageConsumerMock = $this->createMock(Consumer::class);
- $messageConsumerMock
+ $contextMock = $this->createMock(InteropContext::class);
+ $contextMock
->expects($this->any())
- ->method('receive')
- ->willReturn($message)
+ ->method('createSubscriptionConsumer')
+ ->willThrowException(SubscriptionConsumerNotSupportedException::providerDoestNotSupportIt())
;
- return $messageConsumerMock;
+ return $contextMock;
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|PsrContext
- * @param null|mixed $messageConsumer
+ * @return \PHPUnit\Framework\MockObject\MockObject|InteropContext
*/
- protected function createPsrContextStub($messageConsumer = null)
+ private function createContextStub(?Consumer $consumer = null): InteropContext
{
- $context = $this->createMock(PsrContext::class);
- $context
- ->expects($this->any())
- ->method('createConsumer')
- ->willReturn($messageConsumer)
- ;
+ $context = $this->createContextWithoutSubscriptionConsumerMock();
$context
->expects($this->any())
->method('createQueue')
- ->willReturn($this->createMock(Queue::class))
+ ->willReturnCallback(function (string $queueName) {
+ return new NullQueue($queueName);
+ })
;
$context
->expects($this->any())
- ->method('close')
+ ->method('createConsumer')
+ ->willReturnCallback(function (Queue $queue) use ($consumer) {
+ return $consumer ?: $this->createConsumerStub($queue);
+ })
;
return $context;
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
+ * @return \PHPUnit\Framework\MockObject\MockObject|Processor
*/
- protected function createProcessorMock()
+ private function createProcessorMock()
{
return $this->createMock(Processor::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
+ * @return \PHPUnit\Framework\MockObject\MockObject|Processor
*/
- protected function createProcessorStub()
+ private function createProcessorStub()
{
$processorMock = $this->createProcessorMock();
$processorMock
@@ -1019,18 +1500,50 @@ protected function createProcessorStub()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Message
+ * @return \PHPUnit\Framework\MockObject\MockObject|Message
*/
- protected function createMessageMock()
+ private function createMessageMock(): Message
{
return $this->createMock(Message::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|ExtensionInterface
+ * @return \PHPUnit\Framework\MockObject\MockObject|ExtensionInterface
*/
- protected function createExtension()
+ private function createExtension()
{
return $this->createMock(ExtensionInterface::class);
}
+
+ /**
+ * @param mixed|null $queue
+ *
+ * @return \PHPUnit\Framework\MockObject\MockObject|Consumer
+ */
+ private function createConsumerStub($queue = null): Consumer
+ {
+ if (null === $queue) {
+ $queue = 'queue';
+ }
+ if (is_string($queue)) {
+ $queue = new NullQueue($queue);
+ }
+
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue)
+ ;
+
+ return $consumerMock;
+ }
+
+ /**
+ * @return SubscriptionConsumer|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createSubscriptionConsumerMock(): SubscriptionConsumer
+ {
+ return $this->createMock(SubscriptionConsumer::class);
+ }
}
diff --git a/pkg/enqueue/Tests/Consumption/ResultTest.php b/pkg/enqueue/Tests/Consumption/ResultTest.php
index 20b675593..26b8c7812 100644
--- a/pkg/enqueue/Tests/Consumption/ResultTest.php
+++ b/pkg/enqueue/Tests/Consumption/ResultTest.php
@@ -3,9 +3,10 @@
namespace Enqueue\Tests\Consumption;
use Enqueue\Consumption\Result;
-use Enqueue\Transport\Null\NullMessage;
+use Enqueue\Null\NullMessage;
+use PHPUnit\Framework\TestCase;
-class ResultTest extends \PHPUnit_Framework_TestCase
+class ResultTest extends TestCase
{
public function testCouldBeConstructedWithExpectedArguments()
{
@@ -13,13 +14,13 @@ public function testCouldBeConstructedWithExpectedArguments()
$this->assertSame('theStatus', $result->getStatus());
$this->assertSame('', $result->getReason());
- $this->assertSame(null, $result->getReply());
+ $this->assertNull($result->getReply());
$result = new Result('theStatus', 'theReason');
$this->assertSame('theStatus', $result->getStatus());
$this->assertSame('theReason', $result->getReason());
- $this->assertSame(null, $result->getReply());
+ $this->assertNull($result->getReply());
}
public function testCouldConstructedWithAckFactoryMethod()
@@ -29,7 +30,7 @@ public function testCouldConstructedWithAckFactoryMethod()
$this->assertInstanceOf(Result::class, $result);
$this->assertSame(Result::ACK, $result->getStatus());
$this->assertSame('theReason', $result->getReason());
- $this->assertSame(null, $result->getReply());
+ $this->assertNull($result->getReply());
}
public function testCouldConstructedWithRejectFactoryMethod()
@@ -39,7 +40,7 @@ public function testCouldConstructedWithRejectFactoryMethod()
$this->assertInstanceOf(Result::class, $result);
$this->assertSame(Result::REJECT, $result->getStatus());
$this->assertSame('theReason', $result->getReason());
- $this->assertSame(null, $result->getReply());
+ $this->assertNull($result->getReply());
}
public function testCouldConstructedWithRequeueFactoryMethod()
@@ -49,14 +50,38 @@ public function testCouldConstructedWithRequeueFactoryMethod()
$this->assertInstanceOf(Result::class, $result);
$this->assertSame(Result::REQUEUE, $result->getStatus());
$this->assertSame('theReason', $result->getReason());
- $this->assertSame(null, $result->getReply());
+ $this->assertNull($result->getReply());
}
- public function testCouldConstructedWithReplyFactoryMethod()
+ public function testCouldConstructedWithReplyFactoryMethodAndAckStatusByDefault()
{
$reply = new NullMessage();
- $result = Result::reply($reply, 'theReason');
+ $result = Result::reply($reply);
+
+ $this->assertInstanceOf(Result::class, $result);
+ $this->assertSame(Result::ACK, $result->getStatus());
+ $this->assertSame('', $result->getReason());
+ $this->assertSame($reply, $result->getReply());
+ }
+
+ public function testCouldConstructedWithReplyFactoryMethodAndRejectStatusExplicitly()
+ {
+ $reply = new NullMessage();
+
+ $result = Result::reply($reply, Result::REJECT);
+
+ $this->assertInstanceOf(Result::class, $result);
+ $this->assertSame(Result::REJECT, $result->getStatus());
+ $this->assertSame('', $result->getReason());
+ $this->assertSame($reply, $result->getReply());
+ }
+
+ public function testCouldConstructedWithReplyFactoryMethodAndReasonSet()
+ {
+ $reply = new NullMessage();
+
+ $result = Result::reply($reply, null, 'theReason');
$this->assertInstanceOf(Result::class, $result);
$this->assertSame(Result::ACK, $result->getStatus());
diff --git a/pkg/enqueue/Tests/DoctrineConnectionFactoryFactoryTest.php b/pkg/enqueue/Tests/DoctrineConnectionFactoryFactoryTest.php
new file mode 100644
index 000000000..14f7b1006
--- /dev/null
+++ b/pkg/enqueue/Tests/DoctrineConnectionFactoryFactoryTest.php
@@ -0,0 +1,71 @@
+registry = $this->prophesize(ManagerRegistry::class);
+ $this->fallbackFactory = $this->prophesize(ConnectionFactoryFactoryInterface::class);
+
+ $this->factory = new DoctrineConnectionFactoryFactory($this->registry->reveal(), $this->fallbackFactory->reveal());
+ }
+
+ public function testCreateWithoutArray()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The config must be either array or DSN string.');
+
+ $this->factory->create(true);
+ }
+
+ public function testCreateWithoutDsn()
+ {
+ $this->expectExceptionMessage(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The config must have dsn key set.');
+
+ $this->factory->create(['foo' => 'bar']);
+ }
+
+ public function testCreateWithDoctrineSchema()
+ {
+ $this->assertInstanceOf(
+ ManagerRegistryConnectionFactory::class,
+ $this->factory->create('doctrine://localhost:3306')
+ );
+ }
+
+ public function testCreateFallback()
+ {
+ $this->fallbackFactory
+ ->create(['dsn' => 'fallback://'])
+ ->shouldBeCalled();
+
+ $this->factory->create(['dsn' => 'fallback://']);
+ }
+}
diff --git a/pkg/enqueue/Tests/Functional/Client/SimpleClientTest.php b/pkg/enqueue/Tests/Functional/Client/SimpleClientTest.php
deleted file mode 100644
index eb84cf2bc..000000000
--- a/pkg/enqueue/Tests/Functional/Client/SimpleClientTest.php
+++ /dev/null
@@ -1,79 +0,0 @@
-context = $this->buildAmqpContext();
-
- $this->removeQueue('default');
- }
-
- public function testProduceAndConsumeOneMessage()
- {
- $actualMessage = null;
-
- $client = new SimpleClient($this->context);
- $client->bind('foo_topic', function (Message $message) use (&$actualMessage) {
- $actualMessage = $message;
-
- return Result::ACK;
- });
-
- $client->send('foo_topic', 'Hello there!');
-
- $client->consume(new ChainExtension([
- new LimitConsumptionTimeExtension(new \DateTime('+5sec')),
- new LimitConsumedMessagesExtension(2),
- ]));
-
- $this->assertInstanceOf(Message::class, $actualMessage);
- $this->assertSame('Hello there!', $actualMessage->getBody());
- }
-
- public function testProduceAndRouteToTwoConsumes()
- {
- $received = 0;
-
- $client = new SimpleClient($this->context);
- $client->bind('foo_topic', function () use (&$received) {
- ++$received;
-
- return Result::ACK;
- });
- $client->bind('foo_topic', function () use (&$received) {
- ++$received;
-
- return Result::ACK;
- });
-
- $client->send('foo_topic', 'Hello there!');
-
- $client->consume(new ChainExtension([
- new LimitConsumptionTimeExtension(new \DateTime('+5sec')),
- new LimitConsumedMessagesExtension(3),
- ]));
-
- $this->assertSame(2, $received);
- }
-}
diff --git a/pkg/enqueue/Tests/Mocks/CustomPrepareBodyClientExtension.php b/pkg/enqueue/Tests/Mocks/CustomPrepareBodyClientExtension.php
new file mode 100644
index 000000000..dd0e1a69e
--- /dev/null
+++ b/pkg/enqueue/Tests/Mocks/CustomPrepareBodyClientExtension.php
@@ -0,0 +1,20 @@
+getMessage()->setBody('theCommandBodySerializedByCustomExtension');
+ }
+
+ public function onPreSendEvent(PreSend $context): void
+ {
+ $context->getMessage()->setBody('theEventBodySerializedByCustomExtension');
+ }
+}
diff --git a/pkg/enqueue/Tests/Mocks/JsonSerializableObject.php b/pkg/enqueue/Tests/Mocks/JsonSerializableObject.php
new file mode 100644
index 000000000..84885c316
--- /dev/null
+++ b/pkg/enqueue/Tests/Mocks/JsonSerializableObject.php
@@ -0,0 +1,12 @@
+ 'fooVal'];
+ }
+}
diff --git a/pkg/enqueue/Tests/ResourcesTest.php b/pkg/enqueue/Tests/ResourcesTest.php
new file mode 100644
index 000000000..ec713fd03
--- /dev/null
+++ b/pkg/enqueue/Tests/ResourcesTest.php
@@ -0,0 +1,149 @@
+assertTrue($rc->isFinal());
+ }
+
+ public function testShouldConstructorBePrivate()
+ {
+ $rc = new \ReflectionClass(Resources::class);
+
+ $this->assertTrue($rc->getConstructor()->isPrivate());
+ }
+
+ public function testShouldGetAvailableConnectionsInExpectedFormat()
+ {
+ $availableConnections = Resources::getAvailableConnections();
+
+ self::assertIsArray($availableConnections);
+ $this->assertArrayHasKey(RedisConnectionFactory::class, $availableConnections);
+
+ $connectionInfo = $availableConnections[RedisConnectionFactory::class];
+ $this->assertArrayHasKey('schemes', $connectionInfo);
+ $this->assertSame(['redis', 'rediss'], $connectionInfo['schemes']);
+
+ $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo);
+ $this->assertSame(['predis', 'phpredis'], $connectionInfo['supportedSchemeExtensions']);
+
+ $this->assertArrayHasKey('package', $connectionInfo);
+ $this->assertSame('enqueue/redis', $connectionInfo['package']);
+ }
+
+ public function testShouldGetKnownConnectionsInExpectedFormat()
+ {
+ $availableConnections = Resources::getKnownConnections();
+
+ self::assertIsArray($availableConnections);
+ $this->assertArrayHasKey(RedisConnectionFactory::class, $availableConnections);
+
+ $connectionInfo = $availableConnections[RedisConnectionFactory::class];
+ $this->assertArrayHasKey('schemes', $connectionInfo);
+ $this->assertSame(['redis', 'rediss'], $connectionInfo['schemes']);
+
+ $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo);
+ $this->assertSame(['predis', 'phpredis'], $connectionInfo['supportedSchemeExtensions']);
+
+ $this->assertArrayHasKey('package', $connectionInfo);
+ $this->assertSame('enqueue/redis', $connectionInfo['package']);
+ }
+
+ public function testThrowsIfConnectionClassNotImplementConnectionFactoryInterfaceOnAddConnection()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The connection factory class "stdClass" must implement "Interop\Queue\ConnectionFactory" interface.');
+
+ Resources::addConnection(\stdClass::class, [], [], 'foo');
+ }
+
+ public function testThrowsIfNoSchemesProvidedOnAddConnection()
+ {
+ $connectionClass = $this->getMockClass(ConnectionFactory::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Schemes could not be empty.');
+
+ Resources::addConnection($connectionClass, [], [], 'foo');
+ }
+
+ public function testThrowsIfNoPackageProvidedOnAddConnection()
+ {
+ $connectionClass = $this->getMockClass(ConnectionFactory::class);
+
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('Package name could not be empty.');
+
+ Resources::addConnection($connectionClass, ['foo'], [], '');
+ }
+
+ public function testShouldAllowRegisterConnectionThatIsNotInstalled()
+ {
+ Resources::addConnection('theConnectionClass', ['foo'], [], 'foo');
+
+ $knownConnections = Resources::getKnownConnections();
+ self::assertIsArray($knownConnections);
+ $this->assertArrayHasKey('theConnectionClass', $knownConnections);
+
+ $availableConnections = Resources::getAvailableConnections();
+
+ self::assertIsArray($availableConnections);
+ $this->assertArrayNotHasKey('theConnectionClass', $availableConnections);
+ }
+
+ public function testShouldAllowGetPreviouslyRegisteredConnection()
+ {
+ $connectionClass = $this->getMockClass(ConnectionFactory::class);
+
+ Resources::addConnection(
+ $connectionClass,
+ ['fooscheme', 'barscheme'],
+ ['fooextension', 'barextension'],
+ 'foo/bar'
+ );
+
+ $availableConnections = Resources::getAvailableConnections();
+
+ self::assertIsArray($availableConnections);
+ $this->assertArrayHasKey($connectionClass, $availableConnections);
+
+ $connectionInfo = $availableConnections[$connectionClass];
+ $this->assertArrayHasKey('schemes', $connectionInfo);
+ $this->assertSame(['fooscheme', 'barscheme'], $connectionInfo['schemes']);
+
+ $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo);
+ $this->assertSame(['fooextension', 'barextension'], $connectionInfo['supportedSchemeExtensions']);
+
+ $this->assertArrayHasKey('package', $connectionInfo);
+ $this->assertSame('foo/bar', $connectionInfo['package']);
+ }
+
+ public function testShouldHaveRegisteredWampConfiguration()
+ {
+ $availableConnections = Resources::getKnownConnections();
+
+ self::assertIsArray($availableConnections);
+ $this->assertArrayHasKey(WampConnectionFactory::class, $availableConnections);
+
+ $connectionInfo = $availableConnections[WampConnectionFactory::class];
+ $this->assertArrayHasKey('schemes', $connectionInfo);
+ $this->assertSame(['wamp', 'ws'], $connectionInfo['schemes']);
+
+ $this->assertArrayHasKey('supportedSchemeExtensions', $connectionInfo);
+ $this->assertSame([], $connectionInfo['supportedSchemeExtensions']);
+
+ $this->assertArrayHasKey('package', $connectionInfo);
+ $this->assertSame('enqueue/wamp', $connectionInfo['package']);
+ }
+}
diff --git a/pkg/enqueue/Tests/Router/RecipientTest.php b/pkg/enqueue/Tests/Router/RecipientTest.php
index db893fee2..57cc83eed 100644
--- a/pkg/enqueue/Tests/Router/RecipientTest.php
+++ b/pkg/enqueue/Tests/Router/RecipientTest.php
@@ -2,15 +2,16 @@
namespace Enqueue\Tests\Router;
-use Enqueue\Psr\Destination;
-use Enqueue\Psr\Message;
use Enqueue\Router\Recipient;
+use Interop\Queue\Destination;
+use Interop\Queue\Message as InteropMessage;
+use PHPUnit\Framework\TestCase;
-class RecipientTest extends \PHPUnit_Framework_TestCase
+class RecipientTest extends TestCase
{
public function testShouldAllowGetMessageSetInConstructor()
{
- $message = $this->createMock(Message::class);
+ $message = $this->createMock(InteropMessage::class);
$recipient = new Recipient($this->createMock(Destination::class), $message);
@@ -21,7 +22,7 @@ public function testShouldAllowGetDestinationSetInConstructor()
{
$destination = $this->createMock(Destination::class);
- $recipient = new Recipient($destination, $this->createMock(Message::class));
+ $recipient = new Recipient($destination, $this->createMock(InteropMessage::class));
$this->assertSame($destination, $recipient->getDestination());
}
diff --git a/pkg/enqueue/Tests/Router/RouteRecipientListProcessorTest.php b/pkg/enqueue/Tests/Router/RouteRecipientListProcessorTest.php
index 780470cad..ae878fc53 100644
--- a/pkg/enqueue/Tests/Router/RouteRecipientListProcessorTest.php
+++ b/pkg/enqueue/Tests/Router/RouteRecipientListProcessorTest.php
@@ -3,17 +3,18 @@
namespace Enqueue\Tests\Router;
use Enqueue\Consumption\Result;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Processor;
-use Enqueue\Psr\Producer;
+use Enqueue\Null\NullMessage;
+use Enqueue\Null\NullQueue;
use Enqueue\Router\Recipient;
use Enqueue\Router\RecipientListRouterInterface;
use Enqueue\Router\RouteRecipientListProcessor;
use Enqueue\Test\ClassExtensionTrait;
-use Enqueue\Transport\Null\NullMessage;
-use Enqueue\Transport\Null\NullQueue;
+use Interop\Queue\Context;
+use Interop\Queue\Processor;
+use Interop\Queue\Producer as InteropProducer;
+use PHPUnit\Framework\TestCase;
-class RouteRecipientListProcessorTest extends \PHPUnit_Framework_TestCase
+class RouteRecipientListProcessorTest extends TestCase
{
use ClassExtensionTrait;
@@ -22,11 +23,6 @@ public function testShouldImplementProcessorInterface()
$this->assertClassImplements(Processor::class, RouteRecipientListProcessor::class);
}
- public function testCouldBeConstructedWithRouterAsFirstArgument()
- {
- new RouteRecipientListProcessor($this->createRecipientListRouterMock());
- }
-
public function testShouldProduceRecipientsMessagesAndAckOriginalMessage()
{
$fooRecipient = new Recipient(new NullQueue('aName'), new NullMessage());
@@ -54,7 +50,7 @@ public function testShouldProduceRecipientsMessagesAndAckOriginalMessage()
->with($this->identicalTo($barRecipient->getDestination()), $this->identicalTo($barRecipient->getMessage()))
;
- $sessionMock = $this->createPsrContextMock();
+ $sessionMock = $this->createContextMock();
$sessionMock
->expects($this->once())
->method('createProducer')
@@ -69,23 +65,23 @@ public function testShouldProduceRecipientsMessagesAndAckOriginalMessage()
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Producer
+ * @return \PHPUnit\Framework\MockObject\MockObject|InteropProducer
*/
protected function createProducerMock()
{
- return $this->createMock(Producer::class);
+ return $this->createMock(InteropProducer::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Context
+ * @return \PHPUnit\Framework\MockObject\MockObject|Context
*/
- protected function createPsrContextMock()
+ protected function createContextMock()
{
return $this->createMock(Context::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|RecipientListRouterInterface
+ * @return \PHPUnit\Framework\MockObject\MockObject|RecipientListRouterInterface
*/
protected function createRecipientListRouterMock()
{
diff --git a/pkg/enqueue/Tests/Rpc/PromiseTest.php b/pkg/enqueue/Tests/Rpc/PromiseTest.php
index 1e7057b1a..6762149ef 100644
--- a/pkg/enqueue/Tests/Rpc/PromiseTest.php
+++ b/pkg/enqueue/Tests/Rpc/PromiseTest.php
@@ -2,139 +2,236 @@
namespace Enqueue\Tests\Rpc;
-use Enqueue\Psr\Consumer;
+use Enqueue\Null\NullMessage;
use Enqueue\Rpc\Promise;
-use Enqueue\Transport\Null\NullMessage;
+use PHPUnit\Framework\TestCase;
-class PromiseTest extends \PHPUnit_Framework_TestCase
+class PromiseTest extends TestCase
{
- public function testCouldBeConstructedWithExpectedSetOfArguments()
+ public function testIsDeleteReplyQueueShouldReturnTrueByDefault()
{
- new Promise($this->createPsrConsumerMock(), 'aCorrelationId', 2);
+ $promise = new Promise(function () {}, function () {}, function () {});
+
+ $this->assertTrue($promise->isDeleteReplyQueue());
+ }
+
+ public function testCouldSetGetDeleteReplyQueue()
+ {
+ $promise = new Promise(function () {}, function () {}, function () {});
+
+ $promise->setDeleteReplyQueue(false);
+ $this->assertFalse($promise->isDeleteReplyQueue());
+
+ $promise->setDeleteReplyQueue(true);
+ $this->assertTrue($promise->isDeleteReplyQueue());
+ }
+
+ public function testOnReceiveShouldCallReceiveCallBack()
+ {
+ $receiveInvoked = false;
+ $receivePromise = null;
+ $receiveTimeout = null;
+ $receivecb = function ($promise, $timeout) use (&$receiveInvoked, &$receivePromise, &$receiveTimeout) {
+ $receiveInvoked = true;
+ $receivePromise = $promise;
+ $receiveTimeout = $timeout;
+ };
+
+ $promise = new Promise($receivecb, function () {}, function () {});
+ $promise->receive();
+
+ $this->assertTrue($receiveInvoked);
+ $this->assertInstanceOf(Promise::class, $receivePromise);
+ $this->assertNull($receiveTimeout);
}
- public function testShouldTimeoutIfNoResponseMessage()
+ public function testOnReceiveShouldCallReceiveCallBackWithTimeout()
{
- $psrConsumerMock = $this->createPsrConsumerMock();
- $psrConsumerMock
- ->expects($this->atLeastOnce())
- ->method('receive')
- ->willReturn(null)
- ;
-
- $promise = new Promise($psrConsumerMock, 'aCorrelationId', 2);
-
- $this->expectException(\LogicException::class);
- $this->expectExceptionMessage('Time outed without receiving reply message. Timeout: 2, CorrelationId: aCorrelationId');
- $promise->getMessage();
+ $receiveInvoked = false;
+ $receivePromise = null;
+ $receiveTimeout = null;
+ $receivecb = function ($promise, $timeout) use (&$receiveInvoked, &$receivePromise, &$receiveTimeout) {
+ $receiveInvoked = true;
+ $receivePromise = $promise;
+ $receiveTimeout = $timeout;
+ };
+
+ $promise = new Promise($receivecb, function () {}, function () {});
+ $promise->receive(12345);
+
+ $this->assertTrue($receiveInvoked);
+ $this->assertInstanceOf(Promise::class, $receivePromise);
+ $this->assertSame(12345, $receiveTimeout);
}
- public function testShouldReturnReplyMessageIfCorrelationIdSame()
+ public function testOnReceiveNoWaitShouldCallReceiveNoWaitCallBack()
{
- $correlationId = 'theCorrelationId';
-
- $replyMessage = new NullMessage();
- $replyMessage->setCorrelationId($correlationId);
-
- $psrConsumerMock = $this->createPsrConsumerMock();
- $psrConsumerMock
- ->expects($this->once())
- ->method('receive')
- ->willReturn($replyMessage)
- ;
- $psrConsumerMock
- ->expects($this->once())
- ->method('acknowledge')
- ->with($this->identicalTo($replyMessage))
- ;
-
- $promise = new Promise($psrConsumerMock, $correlationId, 2);
-
- $actualReplyMessage = $promise->getMessage();
- $this->assertSame($replyMessage, $actualReplyMessage);
+ $receiveInvoked = false;
+ $receivecb = function () use (&$receiveInvoked) {
+ $receiveInvoked = true;
+ };
+
+ $promise = new Promise(function () {}, $receivecb, function () {});
+ $promise->receiveNoWait();
+
+ $this->assertTrue($receiveInvoked);
}
- public function testShouldReQueueIfCorrelationIdNotSame()
+ public function testOnReceiveShouldCallFinallyCallback()
{
- $correlationId = 'theCorrelationId';
-
- $anotherReplyMessage = new NullMessage();
- $anotherReplyMessage->setCorrelationId('theOtherCorrelationId');
-
- $replyMessage = new NullMessage();
- $replyMessage->setCorrelationId($correlationId);
-
- $psrConsumerMock = $this->createPsrConsumerMock();
- $psrConsumerMock
- ->expects($this->at(0))
- ->method('receive')
- ->willReturn($anotherReplyMessage)
- ;
- $psrConsumerMock
- ->expects($this->at(1))
- ->method('reject')
- ->with($this->identicalTo($anotherReplyMessage), true)
- ;
- $psrConsumerMock
- ->expects($this->at(2))
- ->method('receive')
- ->willReturn($replyMessage)
- ;
- $psrConsumerMock
- ->expects($this->at(3))
- ->method('acknowledge')
- ->with($this->identicalTo($replyMessage))
- ;
-
- $promise = new Promise($psrConsumerMock, $correlationId, 2);
-
- $actualReplyMessage = $promise->getMessage();
- $this->assertSame($replyMessage, $actualReplyMessage);
+ $invoked = false;
+ $cb = function () use (&$invoked) {
+ $invoked = true;
+ };
+
+ $promise = new Promise(function () {}, function () {}, $cb);
+ $promise->receive();
+
+ $this->assertTrue($invoked);
}
- public function testShouldTrySeveralTimesToReceiveReplyMessage()
+ public function testOnReceiveShouldCallFinallyCallbackEvenIfExceptionThrown()
{
- $correlationId = 'theCorrelationId';
-
- $anotherReplyMessage = new NullMessage();
- $anotherReplyMessage->setCorrelationId('theOtherCorrelationId');
-
- $replyMessage = new NullMessage();
- $replyMessage->setCorrelationId($correlationId);
-
- $psrConsumerMock = $this->createPsrConsumerMock();
- $psrConsumerMock
- ->expects($this->at(0))
- ->method('receive')
- ->willReturn(null)
- ;
- $psrConsumerMock
- ->expects($this->at(1))
- ->method('receive')
- ->willReturn(null)
- ;
- $psrConsumerMock
- ->expects($this->at(2))
- ->method('receive')
- ->willReturn($replyMessage)
- ;
- $psrConsumerMock
- ->expects($this->at(3))
- ->method('acknowledge')
- ->with($this->identicalTo($replyMessage))
- ;
-
- $promise = new Promise($psrConsumerMock, $correlationId, 2);
-
- $actualReplyMessage = $promise->getMessage();
- $this->assertSame($replyMessage, $actualReplyMessage);
+ $invokedFinally = false;
+ $finallycb = function () use (&$invokedFinally) {
+ $invokedFinally = true;
+ };
+
+ $invokedReceive = false;
+ $receivecb = function () use (&$invokedReceive) {
+ $invokedReceive = true;
+ throw new \Exception();
+ };
+
+ try {
+ $promise = new Promise($receivecb, function () {}, $finallycb);
+ $promise->receive();
+ } catch (\Exception $e) {
+ }
+
+ $this->assertTrue($invokedReceive);
+ $this->assertTrue($invokedFinally);
}
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Consumer
- */
- private function createPsrConsumerMock()
+ public function testOnReceiveShouldThrowExceptionIfCallbackReturnNotMessageInstance()
{
- return $this->createMock(Consumer::class);
+ $receivecb = function () {
+ return new \stdClass();
+ };
+
+ $promise = new Promise($receivecb, function () {}, function () {});
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Expected "Interop\Queue\Message" but got: "stdClass"');
+
+ $promise->receive();
+ }
+
+ public function testOnReceiveNoWaitShouldThrowExceptionIfCallbackReturnNotMessageInstance()
+ {
+ $receivecb = function () {
+ return new \stdClass();
+ };
+
+ $promise = new Promise(function () {}, $receivecb, function () {});
+
+ $this->expectException(\RuntimeException::class);
+ $this->expectExceptionMessage('Expected "Interop\Queue\Message" but got: "stdClass"');
+
+ $promise->receiveNoWait();
+ }
+
+ public function testOnReceiveNoWaitShouldCallFinallyCallbackOnlyIfMessageReceived()
+ {
+ $invokedReceive = false;
+ $receivecb = function () use (&$invokedReceive) {
+ $invokedReceive = true;
+ };
+
+ $invokedFinally = false;
+ $finallycb = function () use (&$invokedFinally) {
+ $invokedFinally = true;
+ };
+
+ $promise = new Promise(function () {}, $receivecb, $finallycb);
+ $promise->receiveNoWait();
+
+ $this->assertTrue($invokedReceive);
+ $this->assertFalse($invokedFinally);
+
+ // now should call finally too
+
+ $invokedReceive = false;
+ $receivecb = function () use (&$invokedReceive) {
+ $invokedReceive = true;
+
+ return new NullMessage();
+ };
+
+ $promise = new Promise(function () {}, $receivecb, $finallycb);
+ $promise->receiveNoWait();
+
+ $this->assertTrue($invokedReceive);
+ $this->assertTrue($invokedFinally);
+ }
+
+ public function testOnReceiveShouldNotCallCallbackIfMessageReceivedByReceiveNoWaitBefore()
+ {
+ $message = new NullMessage();
+
+ $invokedReceive = false;
+ $receivecb = function () use (&$invokedReceive) {
+ $invokedReceive = true;
+ };
+
+ $invokedReceiveNoWait = false;
+ $receiveNoWaitCb = function () use (&$invokedReceiveNoWait, $message) {
+ $invokedReceiveNoWait = true;
+
+ return $message;
+ };
+
+ $promise = new Promise($receivecb, $receiveNoWaitCb, function () {});
+
+ $this->assertSame($message, $promise->receiveNoWait());
+ $this->assertTrue($invokedReceiveNoWait);
+ $this->assertFalse($invokedReceive);
+
+ // receive should return message but not call callback
+ $invokedReceiveNoWait = false;
+
+ $this->assertSame($message, $promise->receive());
+ $this->assertFalse($invokedReceiveNoWait);
+ $this->assertFalse($invokedReceive);
+ }
+
+ public function testOnReceiveNoWaitShouldNotCallCallbackIfMessageReceivedByReceiveBefore()
+ {
+ $message = new NullMessage();
+
+ $invokedReceive = false;
+ $receivecb = function () use (&$invokedReceive, $message) {
+ $invokedReceive = true;
+
+ return $message;
+ };
+
+ $invokedReceiveNoWait = false;
+ $receiveNoWaitCb = function () use (&$invokedReceiveNoWait) {
+ $invokedReceiveNoWait = true;
+ };
+
+ $promise = new Promise($receivecb, $receiveNoWaitCb, function () {});
+
+ $this->assertSame($message, $promise->receive());
+ $this->assertTrue($invokedReceive);
+ $this->assertFalse($invokedReceiveNoWait);
+
+ // receiveNoWait should return message but not call callback
+ $invokedReceive = false;
+
+ $this->assertSame($message, $promise->receiveNoWait());
+ $this->assertFalse($invokedReceiveNoWait);
+ $this->assertFalse($invokedReceive);
}
}
diff --git a/pkg/enqueue/Tests/Rpc/RpcClientTest.php b/pkg/enqueue/Tests/Rpc/RpcClientTest.php
index 4e35c32ba..db4813c88 100644
--- a/pkg/enqueue/Tests/Rpc/RpcClientTest.php
+++ b/pkg/enqueue/Tests/Rpc/RpcClientTest.php
@@ -2,22 +2,19 @@
namespace Enqueue\Tests\Rpc;
-use Enqueue\Psr\Consumer;
-use Enqueue\Psr\Context;
-use Enqueue\Psr\Producer;
+use Enqueue\Null\NullContext;
+use Enqueue\Null\NullMessage;
+use Enqueue\Null\NullQueue;
use Enqueue\Rpc\Promise;
use Enqueue\Rpc\RpcClient;
-use Enqueue\Transport\Null\NullContext;
-use Enqueue\Transport\Null\NullMessage;
-use Enqueue\Transport\Null\NullQueue;
+use Interop\Queue\Consumer;
+use Interop\Queue\Context;
+use Interop\Queue\Producer as InteropProducer;
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
-class RpcClientTest extends \PHPUnit_Framework_TestCase
+class RpcClientTest extends TestCase
{
- public function testCouldBeConstructedWithPsrContextAsFirstArgument()
- {
- new RpcClient($this->createPsrContextMock());
- }
-
public function testShouldSetReplyToIfNotSet()
{
$context = new NullContext();
@@ -72,28 +69,190 @@ public function testShouldNotSetCorrelationIdIfSet()
$this->assertEquals('theCorrelationId', $message->getCorrelationId());
}
- public function testShouldPopulatePromiseWithExpectedArguments()
+ public function testShouldProduceMessageToQueue()
{
- $context = new NullContext();
+ $queue = new NullQueue('aQueue');
+ $message = new NullMessage();
+ $message->setCorrelationId('theCorrelationId');
+ $message->setReplyTo('theReplyTo');
- $queue = $context->createQueue('rpc.call');
- $message = $context->createMessage();
+ $producer = $this->createInteropProducerMock();
+ $producer
+ ->expects($this->once())
+ ->method('send')
+ ->with($this->identicalTo($queue), $this->identicalTo($message))
+ ;
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($producer)
+ ;
+
+ $rpc = new RpcClient($context);
+
+ $rpc->callAsync($queue, $message, 2);
+ }
+
+ public function testShouldReceiveMessageAndAckMessageIfCorrelationEquals()
+ {
+ $queue = new NullQueue('aQueue');
+ $replyQueue = new NullQueue('theReplyTo');
+ $message = new NullMessage();
$message->setCorrelationId('theCorrelationId');
$message->setReplyTo('theReplyTo');
- $timeout = 123;
+ $receivedMessage = new NullMessage();
+ $receivedMessage->setCorrelationId('theCorrelationId');
+
+ $consumer = $this->createConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('receive')
+ ->with(12345)
+ ->willReturn($receivedMessage)
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('acknowledge')
+ ->with($this->identicalTo($receivedMessage))
+ ;
+ $consumer
+ ->expects($this->never())
+ ->method('reject')
+ ;
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($this->createInteropProducerMock())
+ ;
+ $context
+ ->expects($this->atLeastOnce())
+ ->method('createQueue')
+ ->with('theReplyTo')
+ ->willReturn($replyQueue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($replyQueue))
+ ->willReturn($consumer)
+ ;
+
+ $rpc = new RpcClient($context);
+
+ $rpc->callAsync($queue, $message, 2)->receive(12345);
+ }
+
+ public function testShouldReceiveNoWaitMessageAndAckMessageIfCorrelationEquals()
+ {
+ $queue = new NullQueue('aQueue');
+ $replyQueue = new NullQueue('theReplyTo');
+ $message = new NullMessage();
+ $message->setCorrelationId('theCorrelationId');
+ $message->setReplyTo('theReplyTo');
+
+ $receivedMessage = new NullMessage();
+ $receivedMessage->setCorrelationId('theCorrelationId');
+
+ $consumer = $this->createConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('receiveNoWait')
+ ->willReturn($receivedMessage)
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('acknowledge')
+ ->with($this->identicalTo($receivedMessage))
+ ;
+ $consumer
+ ->expects($this->never())
+ ->method('reject')
+ ;
+
+ $context = $this->createContextMock();
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($this->createInteropProducerMock())
+ ;
+ $context
+ ->expects($this->atLeastOnce())
+ ->method('createQueue')
+ ->with('theReplyTo')
+ ->willReturn($replyQueue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($replyQueue))
+ ->willReturn($consumer)
+ ;
$rpc = new RpcClient($context);
- $promise = $rpc->callAsync($queue, $message, $timeout);
+ $rpc->callAsync($queue, $message, 2)->receiveNoWait();
+ }
+
+ public function testShouldDeleteQueueAfterReceiveIfDeleteReplyQueueIsTrue()
+ {
+ $queue = new NullQueue('aQueue');
+ $replyQueue = new NullQueue('theReplyTo');
+ $message = new NullMessage();
+ $message->setCorrelationId('theCorrelationId');
+ $message->setReplyTo('theReplyTo');
+
+ $receivedMessage = new NullMessage();
+ $receivedMessage->setCorrelationId('theCorrelationId');
+
+ $consumer = $this->createConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('receive')
+ ->willReturn($receivedMessage)
+ ;
+
+ $context = $this->getMockBuilder(Context::class)
+ ->disableOriginalConstructor()
+ ->setMethods(['deleteQueue'])
+ ->getMockForAbstractClass()
+ ;
+
+ $context
+ ->expects($this->once())
+ ->method('createProducer')
+ ->willReturn($this->createInteropProducerMock())
+ ;
+ $context
+ ->expects($this->atLeastOnce())
+ ->method('createQueue')
+ ->with('theReplyTo')
+ ->willReturn($replyQueue)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('createConsumer')
+ ->with($this->identicalTo($replyQueue))
+ ->willReturn($consumer)
+ ;
+ $context
+ ->expects($this->once())
+ ->method('deleteQueue')
+ ->with($this->identicalTo($replyQueue))
+ ;
+
+ $rpc = new RpcClient($context);
- $this->assertInstanceOf(Promise::class, $promise);
- $this->assertAttributeEquals('theCorrelationId', 'correlationId', $promise);
- $this->assertAttributeEquals(123, 'timeout', $promise);
- $this->assertAttributeInstanceOf(Consumer::class, 'consumer', $promise);
+ $promise = $rpc->callAsync($queue, $message, 2);
+ $promise->setDeleteReplyQueue(true);
+ $promise->receive();
}
- public function testShouldProduceMessageToQueueAndCreateConsumerForReplyQueue()
+ public function testShouldNotCallDeleteQueueIfDeleteReplyQueueIsTrueButContextHasNoDeleteQueueMethod()
{
$queue = new NullQueue('aQueue');
$replyQueue = new NullQueue('theReplyTo');
@@ -101,18 +260,21 @@ public function testShouldProduceMessageToQueueAndCreateConsumerForReplyQueue()
$message->setCorrelationId('theCorrelationId');
$message->setReplyTo('theReplyTo');
- $producer = $this->createPsrProducerMock();
- $producer
+ $receivedMessage = new NullMessage();
+ $receivedMessage->setCorrelationId('theCorrelationId');
+
+ $consumer = $this->createConsumerMock();
+ $consumer
->expects($this->once())
- ->method('send')
- ->with($this->identicalTo($queue), $this->identicalTo($message))
+ ->method('receive')
+ ->willReturn($receivedMessage)
;
- $context = $this->createPsrContextMock();
+ $context = $this->createContextMock();
$context
->expects($this->once())
->method('createProducer')
- ->willReturn($producer)
+ ->willReturn($this->createInteropProducerMock())
;
$context
->expects($this->once())
@@ -124,12 +286,15 @@ public function testShouldProduceMessageToQueueAndCreateConsumerForReplyQueue()
->expects($this->once())
->method('createConsumer')
->with($this->identicalTo($replyQueue))
- ->willReturn($this->createPsrConsumerMock())
+ ->willReturn($consumer)
;
$rpc = new RpcClient($context);
- $rpc->callAsync($queue, $message, 2);
+ $promise = $rpc->callAsync($queue, $message, 2);
+ $promise->setDeleteReplyQueue(true);
+
+ $promise->receive();
}
public function testShouldDoSyncCall()
@@ -142,7 +307,7 @@ public function testShouldDoSyncCall()
$promiseMock = $this->createMock(Promise::class);
$promiseMock
->expects($this->once())
- ->method('getMessage')
+ ->method('receive')
->willReturn($replyMessage)
;
@@ -160,25 +325,25 @@ public function testShouldDoSyncCall()
}
/**
- * @return Context|\PHPUnit_Framework_MockObject_MockObject|Producer
+ * @return Context|MockObject|InteropProducer
*/
- private function createPsrProducerMock()
+ private function createInteropProducerMock()
{
- return $this->createMock(Producer::class);
+ return $this->createMock(InteropProducer::class);
}
/**
- * @return \Enqueue\Psr\Context|\PHPUnit_Framework_MockObject_MockObject|Consumer
+ * @return MockObject|Consumer
*/
- private function createPsrConsumerMock()
+ private function createConsumerMock()
{
return $this->createMock(Consumer::class);
}
/**
- * @return \PHPUnit_Framework_MockObject_MockObject|Context
+ * @return MockObject|Context
*/
- private function createPsrContextMock()
+ private function createContextMock()
{
return $this->createMock(Context::class);
}
diff --git a/pkg/enqueue/Tests/Rpc/TimeoutExceptionTest.php b/pkg/enqueue/Tests/Rpc/TimeoutExceptionTest.php
new file mode 100644
index 000000000..ef43a5e75
--- /dev/null
+++ b/pkg/enqueue/Tests/Rpc/TimeoutExceptionTest.php
@@ -0,0 +1,24 @@
+assertTrue($rc->isSubclassOf(\LogicException::class));
+ }
+
+ public function testShouldCreateSelfInstanceWithPreSetMessage()
+ {
+ $exception = TimeoutException::create('theTimeout', 'theCorrelationId');
+
+ $this->assertInstanceOf(TimeoutException::class, $exception);
+ $this->assertEquals('Rpc call timeout is reached without receiving a reply message. Timeout: theTimeout, CorrelationId: theCorrelationId', $exception->getMessage());
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/ConsumeCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/ConsumeCommandTest.php
new file mode 100644
index 000000000..3758ca96a
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/ConsumeCommandTest.php
@@ -0,0 +1,703 @@
+assertClassExtends(Command::class, ConsumeCommand::class);
+ }
+
+ public function testShouldNotBeFinal()
+ {
+ $this->assertClassNotFinal(ConsumeCommand::class);
+ }
+
+ public function testShouldHaveAsCommandAttributeWithCommandName()
+ {
+ $commandClass = ConsumeCommand::class;
+
+ $reflectionClass = new \ReflectionClass($commandClass);
+
+ $attributes = $reflectionClass->getAttributes(AsCommand::class);
+
+ $this->assertNotEmpty($attributes, 'The command does not have the AsCommand attribute.');
+
+ // Get the first attribute instance (assuming there is only one AsCommand attribute)
+ $asCommandAttribute = $attributes[0];
+
+ // Verify the 'name' parameter value
+ $attributeInstance = $asCommandAttribute->newInstance();
+ $this->assertEquals('enqueue:consume', $attributeInstance->name, 'The command name is not set correctly in the AsCommand attribute.');
+ }
+
+ public function testShouldHaveExpectedOptions()
+ {
+ $command = new ConsumeCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $options = $command->getDefinition()->getOptions();
+
+ $this->assertCount(9, $options);
+ $this->assertArrayHasKey('memory-limit', $options);
+ $this->assertArrayHasKey('message-limit', $options);
+ $this->assertArrayHasKey('time-limit', $options);
+ $this->assertArrayHasKey('receive-timeout', $options);
+ $this->assertArrayHasKey('niceness', $options);
+ $this->assertArrayHasKey('client', $options);
+ $this->assertArrayHasKey('logger', $options);
+ $this->assertArrayHasKey('skip', $options);
+ $this->assertArrayHasKey('setup-broker', $options);
+ }
+
+ public function testShouldHaveExpectedAttributes()
+ {
+ $command = new ConsumeCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $arguments = $command->getDefinition()->getArguments();
+
+ $this->assertCount(1, $arguments);
+ $this->assertArrayHasKey('client-queue-names', $arguments);
+ }
+
+ public function testShouldBindDefaultQueueOnly()
+ {
+ $queue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('bind')
+ ->with($this->identicalTo($queue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($queue)
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+ }
+
+ public function testShouldUseRequestedClient()
+ {
+ $defaultProcessor = $this->createDelegateProcessorMock();
+
+ $defaultConsumer = $this->createQueueConsumerMock();
+ $defaultConsumer
+ ->expects($this->never())
+ ->method('bind')
+ ;
+ $defaultConsumer
+ ->expects($this->never())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $defaultDriver = $this->createDriverStub(new RouteCollection([]));
+ $defaultDriver
+ ->expects($this->never())
+ ->method('createQueue')
+ ;
+
+ $queue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([]);
+
+ $fooProcessor = $this->createDelegateProcessorMock();
+
+ $fooConsumer = $this->createQueueConsumerMock();
+ $fooConsumer
+ ->expects($this->once())
+ ->method('bind')
+ ->with($this->identicalTo($queue), $this->identicalTo($fooProcessor))
+ ;
+ $fooConsumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $fooDriver = $this->createDriverStub($routeCollection);
+ $fooDriver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($queue)
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $defaultConsumer,
+ 'enqueue.client.default.driver' => $defaultDriver,
+ 'enqueue.client.default.delegate_processor' => $defaultProcessor,
+ 'enqueue.client.foo.queue_consumer' => $fooConsumer,
+ 'enqueue.client.foo.driver' => $fooDriver,
+ 'enqueue.client.foo.delegate_processor' => $fooProcessor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--client' => 'foo',
+ ]);
+ }
+
+ public function testThrowIfNotDefinedClientRequested()
+ {
+ $routeCollection = new RouteCollection([]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->never())
+ ->method('bind')
+ ;
+ $consumer
+ ->expects($this->never())
+ ->method('consume')
+ ;
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->never())
+ ->method('createQueue')
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Client "not-defined" is not supported.');
+ $tester->execute([
+ '--client' => 'not-defined',
+ ]);
+ }
+
+ public function testShouldBindDefaultQueueIfRouteUseDifferentQueue()
+ {
+ $queue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor'),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('bind')
+ ->with($this->identicalTo($queue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($queue)
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+ }
+
+ public function testShouldBindCustomExecuteConsumptionAndUseCustomClientDestinationName()
+ {
+ $defaultQueue = new NullQueue('');
+ $customQueue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor', ['queue' => 'custom']),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->at(3))
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($defaultQueue)
+ ;
+ $driver
+ ->expects($this->at(4))
+ ->method('createQueue')
+ ->with('custom', true)
+ ->willReturn($customQueue)
+ ;
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->at(0))
+ ->method('bind')
+ ->with($this->identicalTo($defaultQueue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->at(1))
+ ->method('bind')
+ ->with($this->identicalTo($customQueue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->at(2))
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+ }
+
+ public function testShouldBindUserProvidedQueues()
+ {
+ $queue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor', ['queue' => 'custom']),
+ new Route('topic', Route::TOPIC, 'processor', ['queue' => 'non-default-queue']),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('non-default-queue', true)
+ ->willReturn($queue)
+ ;
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('bind')
+ ->with($this->identicalTo($queue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'client-queue-names' => ['non-default-queue'],
+ ]);
+ }
+
+ public function testShouldBindNotPrefixedQueue()
+ {
+ $queue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('topic', Route::TOPIC, 'processor', ['queue' => 'non-prefixed-queue', 'prefix_queue' => false]),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->once())
+ ->method('createQueue')
+ ->with('non-prefixed-queue', false)
+ ->willReturn($queue)
+ ;
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->once())
+ ->method('bind')
+ ->with($this->identicalTo($queue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'client-queue-names' => ['non-prefixed-queue'],
+ ]);
+ }
+
+ public function testShouldBindQueuesOnlyOnce()
+ {
+ $defaultQueue = new NullQueue('');
+ $customQueue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('fooTopic', Route::TOPIC, 'processor', ['queue' => 'custom']),
+ new Route('barTopic', Route::TOPIC, 'processor', ['queue' => 'custom']),
+ new Route('ololoTopic', Route::TOPIC, 'processor', []),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->at(3))
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($defaultQueue)
+ ;
+ $driver
+ ->expects($this->at(4))
+ ->method('createQueue', true)
+ ->with('custom')
+ ->willReturn($customQueue)
+ ;
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->at(0))
+ ->method('bind')
+ ->with($this->identicalTo($defaultQueue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->at(1))
+ ->method('bind')
+ ->with($this->identicalTo($customQueue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->at(2))
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+ }
+
+ public function testShouldNotBindExternalRoutes()
+ {
+ $defaultQueue = new NullQueue('');
+
+ $routeCollection = new RouteCollection([
+ new Route('barTopic', Route::TOPIC, 'processor', ['queue' => null]),
+ new Route('fooTopic', Route::TOPIC, 'processor', ['queue' => 'external_queue', 'external' => true]),
+ ]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->exactly(1))
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($defaultQueue)
+ ;
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->exactly(1))
+ ->method('bind')
+ ->with($this->identicalTo($defaultQueue), $this->identicalTo($processor))
+ ;
+ $consumer
+ ->expects($this->at(1))
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+ }
+
+ public function testShouldSkipQueueConsumptionAndUseCustomClientDestinationName()
+ {
+ $queue = new NullQueue('');
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $consumer = $this->createQueueConsumerMock();
+ $consumer
+ ->expects($this->exactly(3))
+ ->method('bind')
+ ;
+ $consumer
+ ->expects($this->once())
+ ->method('consume')
+ ->with($this->isInstanceOf(ChainExtension::class))
+ ;
+
+ $routeCollection = new RouteCollection([
+ new Route('fooTopic', Route::TOPIC, 'processor', ['queue' => 'fooQueue']),
+ new Route('barTopic', Route::TOPIC, 'processor', ['queue' => 'barQueue']),
+ new Route('ololoTopic', Route::TOPIC, 'processor', ['queue' => 'ololoQueue']),
+ ]);
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->at(3))
+ ->method('createQueue', true)
+ ->with('default')
+ ->willReturn($queue)
+ ;
+ $driver
+ ->expects($this->at(4))
+ ->method('createQueue', true)
+ ->with('fooQueue')
+ ->willReturn($queue)
+ ;
+ $driver
+ ->expects($this->at(5))
+ ->method('createQueue', true)
+ ->with('ololoQueue')
+ ->willReturn($queue)
+ ;
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--skip' => ['barQueue'],
+ ]);
+ }
+
+ public function testShouldReturnExitStatusIfSet()
+ {
+ $testExitCode = 678;
+
+ $stubExtension = $this->createExtension();
+
+ $stubExtension
+ ->expects($this->once())
+ ->method('onStart')
+ ->with($this->isInstanceOf(Start::class))
+ ->willReturnCallback(function (Start $context) use ($testExitCode) {
+ $context->interruptExecution($testExitCode);
+ })
+ ;
+
+ $defaultQueue = new NullQueue('default');
+
+ $routeCollection = new RouteCollection([]);
+
+ $processor = $this->createDelegateProcessorMock();
+
+ $driver = $this->createDriverStub($routeCollection);
+ $driver
+ ->expects($this->exactly(1))
+ ->method('createQueue')
+ ->with('default', true)
+ ->willReturn($defaultQueue)
+ ;
+
+ $consumer = new QueueConsumer($this->createContextStub(), $stubExtension);
+
+ $command = new ConsumeCommand(new Container([
+ 'enqueue.client.default.queue_consumer' => $consumer,
+ 'enqueue.client.default.driver' => $driver,
+ 'enqueue.client.default.delegate_processor' => $processor,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([]);
+
+ $this->assertEquals($testExitCode, $tester->getStatusCode());
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|DelegateProcessor
+ */
+ private function createDelegateProcessorMock()
+ {
+ return $this->createMock(DelegateProcessor::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|QueueConsumerInterface
+ */
+ private function createQueueConsumerMock()
+ {
+ return $this->createMock(QueueConsumerInterface::class);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|DriverInterface
+ */
+ private function createDriverStub(?RouteCollection $routeCollection = null): DriverInterface
+ {
+ $driverMock = $this->createMock(DriverInterface::class);
+ $driverMock
+ ->expects($this->any())
+ ->method('getRouteCollection')
+ ->willReturn($routeCollection ?? new RouteCollection([]))
+ ;
+
+ $driverMock
+ ->expects($this->any())
+ ->method('getConfig')
+ ->willReturn(Config::create('aPrefix', 'anApp'))
+ ;
+
+ return $driverMock;
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject
+ */
+ private function createContextWithoutSubscriptionConsumerMock(): InteropContext
+ {
+ $contextMock = $this->createMock(InteropContext::class);
+ $contextMock
+ ->expects($this->any())
+ ->method('createSubscriptionConsumer')
+ ->willThrowException(SubscriptionConsumerNotSupportedException::providerDoestNotSupportIt())
+ ;
+
+ return $contextMock;
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|InteropContext
+ */
+ private function createContextStub(?Consumer $consumer = null): InteropContext
+ {
+ $context = $this->createContextWithoutSubscriptionConsumerMock();
+ $context
+ ->expects($this->any())
+ ->method('createQueue')
+ ->willReturnCallback(function (string $queueName) {
+ return new NullQueue($queueName);
+ })
+ ;
+ $context
+ ->expects($this->any())
+ ->method('createConsumer')
+ ->willReturnCallback(function (Queue $queue) use ($consumer) {
+ return $consumer ?: $this->createConsumerStub($queue);
+ })
+ ;
+
+ return $context;
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|ExtensionInterface
+ */
+ private function createExtension()
+ {
+ return $this->createMock(ExtensionInterface::class);
+ }
+
+ /**
+ * @param mixed|null $queue
+ *
+ * @return \PHPUnit\Framework\MockObject\MockObject|Consumer
+ */
+ private function createConsumerStub($queue = null): Consumer
+ {
+ if (null === $queue) {
+ $queue = 'queue';
+ }
+ if (is_string($queue)) {
+ $queue = new NullQueue($queue);
+ }
+
+ $consumerMock = $this->createMock(Consumer::class);
+ $consumerMock
+ ->expects($this->any())
+ ->method('getQueue')
+ ->willReturn($queue)
+ ;
+
+ return $consumerMock;
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/ConsumeMessagesCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/ConsumeMessagesCommandTest.php
deleted file mode 100644
index 2247a6478..000000000
--- a/pkg/enqueue/Tests/Symfony/Client/ConsumeMessagesCommandTest.php
+++ /dev/null
@@ -1,231 +0,0 @@
-createQueueConsumerMock(),
- $this->createDelegateProcessorMock(),
- $this->createQueueMetaRegistry([]),
- $this->createDriverMock()
- );
- }
-
- public function testShouldHaveCommandName()
- {
- $command = new ConsumeMessagesCommand(
- $this->createQueueConsumerMock(),
- $this->createDelegateProcessorMock(),
- $this->createQueueMetaRegistry([]),
- $this->createDriverMock()
- );
-
- $this->assertEquals('enqueue:consume', $command->getName());
- }
-
- public function testShouldHaveCommandAliases()
- {
- $command = new ConsumeMessagesCommand(
- $this->createQueueConsumerMock(),
- $this->createDelegateProcessorMock(),
- $this->createQueueMetaRegistry([]),
- $this->createDriverMock()
- );
-
- $this->assertEquals(['enq:c'], $command->getAliases());
- }
-
- public function testShouldHaveExpectedOptions()
- {
- $command = new ConsumeMessagesCommand(
- $this->createQueueConsumerMock(),
- $this->createDelegateProcessorMock(),
- $this->createQueueMetaRegistry([]),
- $this->createDriverMock()
- );
-
- $options = $command->getDefinition()->getOptions();
-
- $this->assertCount(4, $options);
- $this->assertArrayHasKey('memory-limit', $options);
- $this->assertArrayHasKey('message-limit', $options);
- $this->assertArrayHasKey('time-limit', $options);
- $this->assertArrayHasKey('setup-broker', $options);
- }
-
- public function testShouldHaveExpectedAttributes()
- {
- $command = new ConsumeMessagesCommand(
- $this->createQueueConsumerMock(),
- $this->createDelegateProcessorMock(),
- $this->createQueueMetaRegistry([]),
- $this->createDriverMock()
- );
-
- $arguments = $command->getDefinition()->getArguments();
-
- $this->assertCount(1, $arguments);
- $this->assertArrayHasKey('client-queue-names', $arguments);
- }
-
- public function testShouldExecuteConsumptionAndUseDefaultQueueName()
- {
- $queue = new NullQueue('');
-
- $processor = $this->createDelegateProcessorMock();
-
- $context = $this->createPsrContextMock();
- $context
- ->expects($this->once())
- ->method('close')
- ;
-
- $consumer = $this->createQueueConsumerMock();
- $consumer
- ->expects($this->once())
- ->method('bind')
- ->with($this->identicalTo($queue), $this->identicalTo($processor))
- ;
- $consumer
- ->expects($this->once())
- ->method('consume')
- ->with($this->isInstanceOf(ChainExtension::class))
- ;
- $consumer
- ->expects($this->once())
- ->method('getPsrContext')
- ->will($this->returnValue($context))
- ;
-
- $queueMetaRegistry = $this->createQueueMetaRegistry([
- 'default' => [],
- ]);
-
- $driver = $this->createDriverMock();
- $driver
- ->expects($this->once())
- ->method('createQueue')
- ->with('default')
- ->willReturn($queue)
- ;
-
- $command = new ConsumeMessagesCommand($consumer, $processor, $queueMetaRegistry, $driver);
-
- $tester = new CommandTester($command);
- $tester->execute([]);
- }
-
- public function testShouldExecuteConsumptionAndUseCustomClientDestinationName()
- {
- $queue = new NullQueue('');
-
- $processor = $this->createDelegateProcessorMock();
-
- $context = $this->createPsrContextMock();
- $context
- ->expects($this->once())
- ->method('close')
- ;
-
- $consumer = $this->createQueueConsumerMock();
- $consumer
- ->expects($this->once())
- ->method('bind')
- ->with($this->identicalTo($queue), $this->identicalTo($processor))
- ;
- $consumer
- ->expects($this->once())
- ->method('consume')
- ->with($this->isInstanceOf(ChainExtension::class))
- ;
- $consumer
- ->expects($this->once())
- ->method('getPsrContext')
- ->will($this->returnValue($context))
- ;
-
- $queueMetaRegistry = $this->createQueueMetaRegistry([
- 'non-default-queue' => [],
- ]);
-
- $driver = $this->createDriverMock();
- $driver
- ->expects($this->once())
- ->method('createQueue')
- ->with('non-default-queue')
- ->willReturn($queue)
- ;
-
- $command = new ConsumeMessagesCommand($consumer, $processor, $queueMetaRegistry, $driver);
-
- $tester = new CommandTester($command);
- $tester->execute([
- 'client-queue-names' => ['non-default-queue'],
- ]);
- }
-
- /**
- * @param array $destinationNames
- *
- * @return QueueMetaRegistry
- */
- private function createQueueMetaRegistry(array $destinationNames)
- {
- $config = new Config(
- 'aPrefix',
- 'anApp',
- 'aRouterTopicName',
- 'aRouterQueueName',
- 'aDefaultQueueName',
- 'aRouterProcessorName'
- );
-
- return new QueueMetaRegistry($config, $destinationNames, 'default');
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Context
- */
- private function createPsrContextMock()
- {
- return $this->createMock(Context::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|DelegateProcessor
- */
- private function createDelegateProcessorMock()
- {
- return $this->createMock(DelegateProcessor::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|QueueConsumer
- */
- private function createQueueConsumerMock()
- {
- return $this->createMock(QueueConsumer::class);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|DriverInterface
- */
- private function createDriverMock()
- {
- return $this->createMock(DriverInterface::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Symfony/Client/ContainerAwareProcessorRegistryTest.php b/pkg/enqueue/Tests/Symfony/Client/ContainerAwareProcessorRegistryTest.php
deleted file mode 100644
index aa2f744b4..000000000
--- a/pkg/enqueue/Tests/Symfony/Client/ContainerAwareProcessorRegistryTest.php
+++ /dev/null
@@ -1,83 +0,0 @@
-assertClassImplements(ProcessorRegistryInterface::class, ContainerAwareProcessorRegistry::class);
- }
-
- public function testCouldBeConstructedWithoutAnyArgument()
- {
- new ContainerAwareProcessorRegistry();
- }
-
- public function testShouldThrowExceptionIfProcessorIsNotSet()
- {
- $this->setExpectedException(
- \LogicException::class,
- 'Processor was not found. processorName: "processor-name"'
- );
-
- $registry = new ContainerAwareProcessorRegistry();
- $registry->get('processor-name');
- }
-
- public function testShouldThrowExceptionIfContainerIsNotSet()
- {
- $this->setExpectedException(\LogicException::class, 'Container was not set');
-
- $registry = new ContainerAwareProcessorRegistry();
- $registry->set('processor-name', 'processor-id');
-
- $registry->get('processor-name');
- }
-
- public function testShouldThrowExceptionIfInstanceOfProcessorIsInvalid()
- {
- $this->setExpectedException(\LogicException::class, 'Container was not set');
-
- $processor = new \stdClass();
-
- $container = new Container();
- $container->set('processor-id', $processor);
-
- $registry = new ContainerAwareProcessorRegistry();
- $registry->set('processor-name', 'processor-id');
-
- $registry->get('processor-name');
- }
-
- public function testShouldReturnInstanceOfProcessor()
- {
- $this->setExpectedException(\LogicException::class, 'Container was not set');
-
- $processor = $this->createProcessorMock();
-
- $container = new Container();
- $container->set('processor-id', $processor);
-
- $registry = new ContainerAwareProcessorRegistry();
- $registry->set('processor-name', 'processor-id');
-
- $this->assertSame($processor, $registry->get('processor-name'));
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|Processor
- */
- protected function createProcessorMock()
- {
- return $this->createMock(Processor::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPassTest.php
new file mode 100644
index 000000000..568de6488
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/AnalyzeRouteCollectionPassTest.php
@@ -0,0 +1,167 @@
+assertClassImplements(CompilerPassInterface::class, AnalyzeRouteCollectionPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(AnalyzeRouteCollectionPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoRouteCollectionServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.route_collection" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowIfExclusiveCommandProcessorOnDefaultQueue()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aCommand',
+ Route::COMMAND,
+ 'aBarProcessor',
+ ['exclusive' => true]
+ ))->toArray(),
+ ]);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The command "aCommand" processor "aBarProcessor" is exclusive but queue is not specified. Exclusive processors could not be run on a default queue.');
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $pass->process($container);
+ }
+
+ public function testThrowIfTwoExclusiveCommandProcessorsWorkOnSamePrefixedQueue()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aFooCommand',
+ Route::COMMAND,
+ 'aFooProcessor',
+ ['exclusive' => true, 'queue' => 'aQueue', 'prefix_queue' => true]
+ ))->toArray(),
+
+ (new Route(
+ 'aBarCommand',
+ Route::COMMAND,
+ 'aBarProcessor',
+ ['exclusive' => true, 'queue' => 'aQueue', 'prefix_queue' => true]
+ ))->toArray(),
+ ]);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The command "aBarCommand" processor "aBarProcessor" is exclusive. The queue "aQueue" already has another exclusive command processor "aFooProcessor" bound to it.');
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $pass->process($container);
+ }
+
+ public function testThrowIfTwoExclusiveCommandProcessorsWorkOnSameQueue()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aFooCommand',
+ Route::COMMAND,
+ 'aFooProcessor',
+ ['exclusive' => true, 'queue' => 'aQueue', 'prefix_queue' => false]
+ ))->toArray(),
+
+ (new Route(
+ 'aBarCommand',
+ Route::COMMAND,
+ 'aBarProcessor',
+ ['exclusive' => true, 'queue' => 'aQueue', 'prefix_queue' => false]
+ ))->toArray(),
+ ]);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The command "aBarCommand" processor "aBarProcessor" is exclusive. The queue "aQueue" already has another exclusive command processor "aFooProcessor" bound to it.');
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $pass->process($container);
+ }
+
+ public function testThrowIfThereAreTwoQueuesWithSameNameAndOneNotPrefixed()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aFooCommand',
+ Route::COMMAND,
+ 'aFooProcessor',
+ ['queue' => 'foo', 'prefix_queue' => false]
+ ))->toArray(),
+
+ (new Route(
+ 'aBarCommand',
+ Route::COMMAND,
+ 'aBarProcessor',
+ ['queue' => 'foo', 'prefix_queue' => true]
+ ))->toArray(),
+ ]);
+
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('There are prefixed and not prefixed queue with the same name "foo". This is not allowed.');
+ $pass->process($container);
+ }
+
+ public function testThrowIfDefaultQueueNotPrefixed()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aFooCommand',
+ Route::COMMAND,
+ 'aFooProcessor',
+ ['queue' => null, 'prefix_queue' => false]
+ ))->toArray(),
+ ]);
+
+ $pass = new AnalyzeRouteCollectionPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The default queue must be prefixed.');
+ $pass->process($container);
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildClientExtensionsPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildClientExtensionsPassTest.php
new file mode 100644
index 000000000..753790369
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildClientExtensionsPassTest.php
@@ -0,0 +1,283 @@
+assertClassImplements(CompilerPassInterface::class, BuildClientExtensionsPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildClientExtensionsPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildClientExtensionsPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoClientExtensionsServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'foo');
+
+ $pass = new BuildClientExtensionsPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.client_extensions" not found');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterClientExtension()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.aName.client_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'aName'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'aName'])
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ new Reference('aBarExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldIgnoreOtherClientExtensions()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.aName.client_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'aName'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'anotherName'])
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldAddExtensionIfClientAll()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.aName.client_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'all'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'anotherName'])
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldTreatTagsWithoutClientAsDefaultClient()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.client_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension')
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension')
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ new Reference('aBarExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldOrderExtensionsByPriority()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+ $container->setDefinition('enqueue.client.foo.client_extensions', $extensions);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension', ['priority' => 6]);
+ $container->setDefinition('foo_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension', ['priority' => -5]);
+ $container->setDefinition('bar_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension', ['priority' => 2]);
+ $container->setDefinition('baz_extension', $extension);
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ $orderedExtensions = $extensions->getArgument(0);
+
+ $this->assertCount(3, $orderedExtensions);
+ $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[0]);
+ $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[1]);
+ $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[2]);
+ }
+
+ public function testShouldAssumePriorityZeroIfPriorityIsNotSet()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+ $container->setDefinition('enqueue.client.foo.client_extensions', $extensions);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension');
+ $container->setDefinition('foo_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension', ['priority' => 1]);
+ $container->setDefinition('bar_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.client_extension', ['priority' => -1]);
+ $container->setDefinition('baz_extension', $extension);
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ $orderedExtensions = $extensions->getArgument(0);
+
+ $this->assertCount(3, $orderedExtensions);
+ $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[0]);
+ $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[1]);
+ $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[2]);
+ }
+
+ public function testShouldMergeWithAddedPreviously()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([
+ 'aBarExtension' => 'aBarServiceIdAddedPreviously',
+ 'aOloloExtension' => 'aOloloServiceIdAddedPreviously',
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.client_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension')
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension')
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertCount(4, $extensions->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorWithMatchedNameToCorrespondingExtensions()
+ {
+ $fooExtensions = new Definition();
+ $fooExtensions->addArgument([]);
+
+ $barExtensions = new Definition();
+ $barExtensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.client_extensions', $fooExtensions);
+ $container->setDefinition('enqueue.client.bar.client_extensions', $barExtensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'foo'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.client_extension', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildClientExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($fooExtensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $fooExtensions->getArgument(0));
+
+ self::assertIsArray($barExtensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aBarExtension'),
+ ], $barExtensions->getArgument(0));
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPassTest.php
new file mode 100644
index 000000000..e1ed297c6
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildCommandSubscriberRoutesPassTest.php
@@ -0,0 +1,459 @@
+assertClassImplements(CompilerPassInterface::class, BuildCommandSubscriberRoutesPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildCommandSubscriberRoutesPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoRouteCollectionServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'baz');
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.route_collection" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowIfTaggedProcessorIsBuiltByFactory()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->register('enqueue.client.aName.route_collection', RouteCollection::class)
+ ->addArgument([])
+ ;
+ $container->register('aProcessor', Processor::class)
+ ->setFactory('foo')
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The command subscriber tag could not be applied to a service created by factory.');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterProcessorWithMatchedName()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber', ['client' => 'foo'])
+ ;
+ $container->register('aProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorWithoutNameToDefaultClient()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber')
+ ;
+ $container->register('aProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorIfClientNameEqualsAll()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber', ['client' => 'all'])
+ ;
+ $container->register('aProcessor', get_class($this->createCommandSubscriberProcessor()))
+ ->addTag('enqueue.command_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorIfCommandsIsString()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor('fooCommand');
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testThrowIfCommandSubscriberReturnsNothing()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor(null);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Command subscriber must return something.');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterProcessorIfCommandsAreStrings()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor(['fooCommand', 'barCommand']);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(2, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ [
+ 'source' => 'barCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegisterProcessorIfParamSingleCommandArray()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor([
+ 'command' => 'fooCommand',
+ 'processor' => 'aCustomFooProcessorName',
+ 'anOption' => 'aFooVal',
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aCustomFooProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aFooVal',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegisterProcessorIfCommandsAreParamArrays()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor([
+ ['command' => 'fooCommand', 'processor' => 'aCustomFooProcessorName', 'anOption' => 'aFooVal'],
+ ['command' => 'barCommand', 'processor' => 'aCustomBarProcessorName', 'anOption' => 'aBarVal'],
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(2, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aCustomFooProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aFooVal',
+ ],
+ [
+ 'source' => 'barCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aCustomBarProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aBarVal',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testThrowIfCommandSubscriberParamsInvalid()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor(['fooBar', true]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Command subscriber configuration is invalid');
+ $pass->process($container);
+ }
+
+ public function testShouldMergeExtractedRoutesWithAlreadySetInCollection()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([
+ (new Route('aCommand', Route::COMMAND, 'aProcessor'))->toArray(),
+ (new Route('aCommand', Route::COMMAND, 'aProcessor'))->toArray(),
+ ]);
+
+ $processor = $this->createCommandSubscriberProcessor(['fooCommand']);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(3, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegister08CommandProcessor()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createCommandSubscriberProcessor([
+ 'processorName' => 'fooCommand',
+ 'queueName' => 'a_client_queue_name',
+ 'queueNameHardcoded' => true,
+ 'exclusive' => true,
+ 'anOption' => 'aFooVal',
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['default']);
+ $container->setParameter('enqueue.default_client', 'default');
+ $container->setDefinition('enqueue.client.default.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.command_subscriber')
+ ;
+
+ $pass = new BuildCommandSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aFooVal',
+ 'queue' => 'a_client_queue_name',
+ 'prefix_queue' => false,
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ private function createCommandSubscriberProcessor($commandSubscriberReturns = ['aCommand'])
+ {
+ $processor = new class implements Processor, CommandSubscriberInterface {
+ public static $return;
+
+ public function process(InteropMessage $message, Context $context)
+ {
+ return self::ACK;
+ }
+
+ public static function getSubscribedCommand()
+ {
+ return static::$return;
+ }
+ };
+
+ $processor::$return = $commandSubscriberReturns;
+
+ return $processor;
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPassTest.php
new file mode 100644
index 000000000..c2975051b
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildConsumptionExtensionsPassTest.php
@@ -0,0 +1,283 @@
+assertClassImplements(CompilerPassInterface::class, BuildConsumptionExtensionsPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildConsumptionExtensionsPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildConsumptionExtensionsPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoConsumptionExtensionsServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'baz');
+
+ $pass = new BuildConsumptionExtensionsPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.consumption_extensions" not found');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterClientExtension()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'foo'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'foo'])
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ new Reference('aBarExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldIgnoreOtherClientExtensions()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'foo'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'anotherName'])
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldAddExtensionIfClientAll()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'all'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'anotherName'])
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldTreatTagsWithoutClientAsDefaultClient()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension')
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension')
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ new Reference('aBarExtension'),
+ ], $extensions->getArgument(0));
+ }
+
+ public function testShouldOrderExtensionsByPriority()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension', ['priority' => 6]);
+ $container->setDefinition('foo_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension', ['priority' => -5]);
+ $container->setDefinition('bar_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension', ['priority' => 2]);
+ $container->setDefinition('baz_extension', $extension);
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ $orderedExtensions = $extensions->getArgument(0);
+
+ $this->assertCount(3, $orderedExtensions);
+ $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[0]);
+ $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[1]);
+ $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[2]);
+ }
+
+ public function testShouldAssumePriorityZeroIfPriorityIsNotSet()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+
+ $extensions = new Definition();
+ $extensions->addArgument([]);
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension');
+ $container->setDefinition('foo_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension', ['priority' => 1]);
+ $container->setDefinition('bar_extension', $extension);
+
+ $extension = new Definition();
+ $extension->addTag('enqueue.consumption_extension', ['priority' => -1]);
+ $container->setDefinition('baz_extension', $extension);
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ $orderedExtensions = $extensions->getArgument(0);
+
+ $this->assertCount(3, $orderedExtensions);
+ $this->assertEquals(new Reference('bar_extension'), $orderedExtensions[0]);
+ $this->assertEquals(new Reference('foo_extension'), $orderedExtensions[1]);
+ $this->assertEquals(new Reference('baz_extension'), $orderedExtensions[2]);
+ }
+
+ public function testShouldMergeWithAddedPreviously()
+ {
+ $extensions = new Definition();
+ $extensions->addArgument([
+ 'aBarExtension' => 'aBarServiceIdAddedPreviously',
+ 'aOloloExtension' => 'aOloloServiceIdAddedPreviously',
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $extensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension')
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension')
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($extensions->getArgument(0));
+ $this->assertCount(4, $extensions->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorWithMatchedNameToCorrespondingExtensions()
+ {
+ $fooExtensions = new Definition();
+ $fooExtensions->addArgument([]);
+
+ $barExtensions = new Definition();
+ $barExtensions->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.consumption_extensions', $fooExtensions);
+ $container->setDefinition('enqueue.client.bar.consumption_extensions', $barExtensions);
+
+ $container->register('aFooExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'foo'])
+ ;
+ $container->register('aBarExtension', ExtensionInterface::class)
+ ->addTag('enqueue.consumption_extension', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildConsumptionExtensionsPass();
+ $pass->process($container);
+
+ self::assertIsArray($fooExtensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aFooExtension'),
+ ], $fooExtensions->getArgument(0));
+
+ self::assertIsArray($barExtensions->getArgument(0));
+ $this->assertEquals([
+ new Reference('aBarExtension'),
+ ], $barExtensions->getArgument(0));
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRegistryPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRegistryPassTest.php
new file mode 100644
index 000000000..5c9ac4840
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRegistryPassTest.php
@@ -0,0 +1,151 @@
+assertClassImplements(CompilerPassInterface::class, BuildProcessorRegistryPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildProcessorRegistryPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildProcessorRegistryPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoProcessorRegistryServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+
+ $pass = new BuildProcessorRegistryPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.processor_registry" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowsIfNoRouteCollectionServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->register('enqueue.client.foo.processor_registry');
+
+ $pass = new BuildProcessorRegistryPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.route_collection" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowsIfNoRouteProcessorServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->register('enqueue.client.foo.processor_registry');
+ $container->register('enqueue.client.foo.route_collection');
+
+ $pass = new BuildProcessorRegistryPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.router_processor" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowIfProcessorServiceIdOptionNotSet()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route('aCommand', Route::COMMAND, 'aProcessor'))->toArray(),
+ ]);
+ $container->register('enqueue.client.aName.processor_registry')->addArgument([]);
+ $container->register('enqueue.client.aName.router_processor');
+
+ $pass = new BuildProcessorRegistryPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The route option "processor_service_id" is required');
+ $pass->process($container);
+ }
+
+ public function testShouldPassLocatorAsFirstArgument()
+ {
+ $registry = new Definition();
+ $registry->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['aName']);
+ $container->register('enqueue.client.aName.route_collection')->addArgument([
+ (new Route(
+ 'aCommand',
+ Route::COMMAND,
+ 'aBarProcessor',
+ ['processor_service_id' => 'aBarServiceId']
+ ))->toArray(),
+ (new Route(
+ 'aTopic',
+ Route::TOPIC,
+ 'aFooProcessor',
+ ['processor_service_id' => 'aFooServiceId']
+ ))->toArray(),
+ ]);
+ $container->setDefinition('enqueue.client.aName.processor_registry', $registry);
+ $container->register('enqueue.client.aName.router_processor');
+
+ $pass = new BuildProcessorRegistryPass();
+ $pass->process($container);
+
+ $this->assertLocatorServices($container, $registry->getArgument(0), [
+ '%enqueue.client.aName.router_processor%' => 'enqueue.client.aName.router_processor',
+ 'aBarProcessor' => 'aBarServiceId',
+ 'aFooProcessor' => 'aFooServiceId',
+ ]);
+ }
+
+ private function assertLocatorServices(ContainerBuilder $container, $locatorId, array $locatorServices)
+ {
+ $this->assertInstanceOf(Reference::class, $locatorId);
+ $locatorId = (string) $locatorId;
+
+ $this->assertTrue($container->hasDefinition($locatorId));
+ $this->assertMatchesRegularExpression('/\.?service_locator\..*?\.enqueue\./', $locatorId);
+
+ $match = [];
+ if (false == preg_match('/(\.?service_locator\..*?)\.enqueue\./', $locatorId, $match)) {
+ $this->fail('preg_match should not failed');
+ }
+
+ $this->assertTrue($container->hasDefinition($match[1]));
+ $locator = $container->getDefinition($match[1]);
+
+ $this->assertContainsOnly(ServiceClosureArgument::class, $locator->getArgument(0));
+ $actualServices = array_map(function (ServiceClosureArgument $value) {
+ return (string) $value->getValues()[0];
+ }, $locator->getArgument(0));
+
+ $this->assertEquals($locatorServices, $actualServices);
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRoutesPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRoutesPassTest.php
new file mode 100644
index 000000000..0351c45f5
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildProcessorRoutesPassTest.php
@@ -0,0 +1,302 @@
+assertClassImplements(CompilerPassInterface::class, BuildProcessorRoutesPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildProcessorRoutesPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildProcessorRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoRouteCollectionServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'baz');
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.route_collection" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowIfBothTopicAndCommandAttributesAreSet()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['topic' => 'foo', 'command' => 'bar'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Either "topic" or "command" tag attribute must be set on service "aFooProcessor". Both are set.');
+ $pass->process($container);
+ }
+
+ public function testThrowIfNeitherTopicNorCommandAttributesAreSet()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', [])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Either "topic" or "command" tag attribute must be set on service "aFooProcessor". None is set.');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterProcessorWithMatchedName()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'bar');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['client' => 'foo', 'topic' => 'foo'])
+ ;
+ $container->register('aProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['client' => 'bar', 'command' => 'foo'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorWithoutNameToDefaultClient()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['topic' => 'foo'])
+ ;
+ $container->register('aProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['client' => 'bar', 'command' => 'foo'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorIfClientNameEqualsAll()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['client' => 'all', 'topic' => 'foo'])
+ ;
+ $container->register('aProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['client' => 'bar', 'command' => 'foo'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterAsTopicProcessor()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['topic' => 'aTopic'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegisterAsCommandProcessor()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['command' => 'aCommand'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegisterWithCustomProcessorName()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['command' => 'aCommand', 'processor' => 'customProcessorName'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'customProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldMergeExtractedRoutesWithAlreadySetInCollection()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([
+ (new Route('aTopic', Route::TOPIC, 'aProcessor'))->toArray(),
+ (new Route('aCommand', Route::COMMAND, 'aProcessor'))->toArray(),
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', 'aProcessorClass')
+ ->addTag('enqueue.processor', ['command' => 'fooCommand'])
+ ;
+
+ $pass = new BuildProcessorRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(3, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'fooCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPassTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPassTest.php
new file mode 100644
index 000000000..a954d9a41
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/BuildTopicSubscriberRoutesPassTest.php
@@ -0,0 +1,423 @@
+assertClassImplements(CompilerPassInterface::class, BuildTopicSubscriberRoutesPass::class);
+ }
+
+ public function testShouldBeFinal()
+ {
+ $this->assertClassFinal(BuildTopicSubscriberRoutesPass::class);
+ }
+
+ public function testThrowIfEnqueueClientsParameterNotSet()
+ {
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The "enqueue.clients" parameter must be set.');
+ $pass->process(new ContainerBuilder());
+ }
+
+ public function testThrowsIfNoRouteCollectionServiceFoundForConfiguredTransport()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo', 'bar']);
+ $container->setParameter('enqueue.default_client', 'baz');
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Service "enqueue.client.foo.route_collection" not found');
+ $pass->process($container);
+ }
+
+ public function testThrowIfTaggedProcessorIsBuiltByFactory()
+ {
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->register('enqueue.client.foo.route_collection', RouteCollection::class)
+ ->addArgument([])
+ ;
+ $container->register('aProcessor', Processor::class)
+ ->setFactory('foo')
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('The topic subscriber tag could not be applied to a service created by factory.');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterProcessorWithMatchedName()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'bar');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber', ['client' => 'foo'])
+ ;
+ $container->register('aProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorWithoutNameToDefaultClient()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber')
+ ;
+ $container->register('aProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorIfClientNameEqualsAll()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber', ['client' => 'all'])
+ ;
+ $container->register('aProcessor', get_class($this->createTopicSubscriberProcessor()))
+ ->addTag('enqueue.topic_subscriber', ['client' => 'bar'])
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+ }
+
+ public function testShouldRegisterProcessorIfTopicsIsString()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor('fooTopic');
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(1, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testThrowIfTopicSubscriberReturnsNothing()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor(null);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Topic subscriber must return something.');
+ $pass->process($container);
+ }
+
+ public function testShouldRegisterProcessorIfTopicsAreStrings()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor(['fooTopic', 'barTopic']);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(2, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ [
+ 'source' => 'barTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegisterProcessorIfTopicsAreParamArrays()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor([
+ ['topic' => 'fooTopic', 'processor' => 'aCustomFooProcessorName', 'anOption' => 'aFooVal'],
+ ['topic' => 'barTopic', 'processor' => 'aCustomBarProcessorName', 'anOption' => 'aBarVal'],
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(2, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aCustomFooProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aFooVal',
+ ],
+ [
+ 'source' => 'barTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aCustomBarProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aBarVal',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testThrowIfTopicSubscriberParamsInvalid()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor(['fooBar', true]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Topic subscriber configuration is invalid');
+ $pass->process($container);
+ }
+
+ public function testShouldMergeExtractedRoutesWithAlreadySetInCollection()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([
+ (new Route('aTopic', Route::TOPIC, 'aProcessor'))->toArray(),
+ (new Route('aCommand', Route::COMMAND, 'aProcessor'))->toArray(),
+ ]);
+
+ $processor = $this->createTopicSubscriberProcessor(['fooTopic']);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['foo']);
+ $container->setParameter('enqueue.default_client', 'foo');
+ $container->setDefinition('enqueue.client.foo.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(3, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'aTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'aCommand',
+ 'source_type' => 'enqueue.client.command_route',
+ 'processor' => 'aProcessor',
+ ],
+ [
+ 'source' => 'fooTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aFooProcessor',
+ 'processor_service_id' => 'aFooProcessor',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ public function testShouldRegister08TopicSubscriber()
+ {
+ $routeCollection = new Definition(RouteCollection::class);
+ $routeCollection->addArgument([]);
+
+ $processor = $this->createTopicSubscriberProcessor([
+ 'fooTopic' => ['processorName' => 'aCustomFooProcessorName', 'queueName' => 'fooQueue', 'queueNameHardcoded' => true, 'anOption' => 'aFooVal'],
+ 'barTopic' => ['processorName' => 'aCustomBarProcessorName', 'anOption' => 'aBarVal'],
+ ]);
+
+ $container = new ContainerBuilder();
+ $container->setParameter('enqueue.clients', ['default']);
+ $container->setParameter('enqueue.default_client', 'default');
+ $container->setDefinition('enqueue.client.default.route_collection', $routeCollection);
+ $container->register('aFooProcessor', $processor::class)
+ ->addTag('enqueue.topic_subscriber')
+ ;
+
+ $pass = new BuildTopicSubscriberRoutesPass();
+ $pass->process($container);
+
+ self::assertIsArray($routeCollection->getArgument(0));
+ $this->assertCount(2, $routeCollection->getArgument(0));
+
+ $this->assertEquals(
+ [
+ [
+ 'source' => 'fooTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aCustomFooProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aFooVal',
+ 'queue' => 'fooQueue',
+ 'prefix_queue' => false,
+ ],
+ [
+ 'source' => 'barTopic',
+ 'source_type' => 'enqueue.client.topic_route',
+ 'processor' => 'aCustomBarProcessorName',
+ 'processor_service_id' => 'aFooProcessor',
+ 'anOption' => 'aBarVal',
+ ],
+ ],
+ $routeCollection->getArgument(0)
+ );
+ }
+
+ private function createTopicSubscriberProcessor($topicSubscriberReturns = ['aTopic'])
+ {
+ $processor = new class implements Processor, TopicSubscriberInterface {
+ public static $return;
+
+ public function process(InteropMessage $message, Context $context)
+ {
+ return self::ACK;
+ }
+
+ public static function getSubscribedTopics()
+ {
+ return static::$return;
+ }
+ };
+
+ $processor::$return = $topicSubscriberReturns;
+
+ return $processor;
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/ClientFactoryTest.php b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/ClientFactoryTest.php
new file mode 100644
index 000000000..9f37dff47
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/DependencyInjection/ClientFactoryTest.php
@@ -0,0 +1,54 @@
+assertClassFinal(ClientFactory::class);
+ }
+
+ public function testThrowIfEmptyNameGivenOnConstruction()
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $this->expectExceptionMessage('The name could not be empty.');
+
+ new ClientFactory('');
+ }
+
+ public function testShouldCreateDriverFromDsn()
+ {
+ $container = new ContainerBuilder();
+
+ $transport = new ClientFactory('default');
+
+ $serviceId = $transport->createDriver($container, ['dsn' => 'foo://bar/baz', 'foo' => 'fooVal']);
+
+ $this->assertEquals('enqueue.client.default.driver', $serviceId);
+
+ $this->assertTrue($container->hasDefinition('enqueue.client.default.driver'));
+
+ $this->assertNotEmpty($container->getDefinition('enqueue.client.default.driver')->getFactory());
+ $this->assertEquals(
+ [new Reference('enqueue.client.default.driver_factory'), 'create'],
+ $container->getDefinition('enqueue.client.default.driver')->getFactory())
+ ;
+ $this->assertEquals(
+ [
+ new Reference('enqueue.transport.default.connection_factory'),
+ new Reference('enqueue.client.default.config'),
+ new Reference('enqueue.client.default.route_collection'),
+ ],
+ $container->getDefinition('enqueue.client.default.driver')->getArguments())
+ ;
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/FlushSpoolProducerListenerTest.php b/pkg/enqueue/Tests/Symfony/Client/FlushSpoolProducerListenerTest.php
new file mode 100644
index 000000000..539d332ee
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/FlushSpoolProducerListenerTest.php
@@ -0,0 +1,62 @@
+assertClassImplements(EventSubscriberInterface::class, FlushSpoolProducerListener::class);
+ }
+
+ public function testShouldSubscribeOnKernelTerminateEvent()
+ {
+ $events = FlushSpoolProducerListener::getSubscribedEvents();
+
+ self::assertIsArray($events);
+ $this->assertArrayHasKey(KernelEvents::TERMINATE, $events);
+
+ $this->assertEquals('flushMessages', $events[KernelEvents::TERMINATE]);
+ }
+
+ public function testShouldSubscribeOnConsoleTerminateEvent()
+ {
+ $events = FlushSpoolProducerListener::getSubscribedEvents();
+
+ self::assertIsArray($events);
+ $this->assertArrayHasKey(ConsoleEvents::TERMINATE, $events);
+
+ $this->assertEquals('flushMessages', $events[ConsoleEvents::TERMINATE]);
+ }
+
+ public function testShouldFlushSpoolProducerOnFlushMessagesCall()
+ {
+ $producerMock = $this->createSpoolProducerMock();
+ $producerMock
+ ->expects($this->once())
+ ->method('flush')
+ ;
+
+ $listener = new FlushSpoolProducerListener($producerMock);
+
+ $listener->flushMessages();
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|SpoolProducer
+ */
+ private function createSpoolProducerMock()
+ {
+ return $this->createMock(SpoolProducer::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/Meta/QueuesCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/Meta/QueuesCommandTest.php
deleted file mode 100644
index 048e7d3fe..000000000
--- a/pkg/enqueue/Tests/Symfony/Client/Meta/QueuesCommandTest.php
+++ /dev/null
@@ -1,107 +0,0 @@
-assertClassExtends(Command::class, QueuesCommand::class);
- }
-
- public function testCouldBeConstructedWithQueueMetaRegistryAsFirstArgument()
- {
- new QueuesCommand($this->createQueueMetaRegistryStub());
- }
-
- public function testShouldHaveCommandName()
- {
- $command = new QueuesCommand($this->createQueueMetaRegistryStub());
-
- $this->assertEquals('enqueue:queues', $command->getName());
- }
-
- public function testShouldHaveCommandAliases()
- {
- $command = new QueuesCommand($this->createQueueMetaRegistryStub());
-
- $this->assertEquals(['enq:m:q', 'debug:enqueue:queues'], $command->getAliases());
- }
-
- public function testShouldShowMessageFoundZeroDestinationsIfAnythingInRegistry()
- {
- $command = new QueuesCommand($this->createQueueMetaRegistryStub());
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('Found 0 destinations', $output);
- }
-
- public function testShouldShowMessageFoundTwoDestinations()
- {
- $command = new QueuesCommand($this->createQueueMetaRegistryStub([
- new QueueMeta('aClientName', 'aDestinationName'),
- new QueueMeta('anotherClientName', 'anotherDestinationName'),
- ]));
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('Found 2 destinations', $output);
- }
-
- public function testShouldShowInfoAboutDestinations()
- {
- $command = new QueuesCommand($this->createQueueMetaRegistryStub([
- new QueueMeta('aFooClientName', 'aFooDestinationName', ['fooSubscriber']),
- new QueueMeta('aBarClientName', 'aBarDestinationName', ['barSubscriber']),
- ]));
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('aFooClientName', $output);
- $this->assertContains('aFooDestinationName', $output);
- $this->assertContains('fooSubscriber', $output);
- $this->assertContains('aBarClientName', $output);
- $this->assertContains('aBarDestinationName', $output);
- $this->assertContains('barSubscriber', $output);
- }
-
- /**
- * @param Command $command
- * @param string[] $arguments
- *
- * @return string
- */
- protected function executeCommand(Command $command, array $arguments = [])
- {
- $tester = new CommandTester($command);
- $tester->execute($arguments);
-
- return $tester->getDisplay();
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|QueueMetaRegistry
- * @param mixed $destinations
- */
- protected function createQueueMetaRegistryStub($destinations = [])
- {
- $registryMock = $this->createMock(QueueMetaRegistry::class);
- $registryMock
- ->expects($this->any())
- ->method('getQueuesMeta')
- ->willReturn($destinations)
- ;
-
- return $registryMock;
- }
-}
diff --git a/pkg/enqueue/Tests/Symfony/Client/Meta/TopicsCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/Meta/TopicsCommandTest.php
deleted file mode 100644
index 3cf4c5efb..000000000
--- a/pkg/enqueue/Tests/Symfony/Client/Meta/TopicsCommandTest.php
+++ /dev/null
@@ -1,90 +0,0 @@
-assertClassExtends(Command::class, TopicsCommand::class);
- }
-
- public function testCouldBeConstructedWithTopicMetaRegistryAsFirstArgument()
- {
- new TopicsCommand(new TopicMetaRegistry([]));
- }
-
- public function testShouldHaveCommandName()
- {
- $command = new TopicsCommand(new TopicMetaRegistry([]));
-
- $this->assertEquals('enqueue:topics', $command->getName());
- }
-
- public function testShouldHaveCommandAliases()
- {
- $command = new TopicsCommand(new TopicMetaRegistry([]));
-
- $this->assertEquals(['enq:m:t', 'debug:enqueue:topics'], $command->getAliases());
- }
-
- public function testShouldShowMessageFoundZeroTopicsIfAnythingInRegistry()
- {
- $command = new TopicsCommand(new TopicMetaRegistry([]));
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('Found 0 topics', $output);
- }
-
- public function testShouldShowMessageFoundTwoTopics()
- {
- $command = new TopicsCommand(new TopicMetaRegistry([
- 'fooTopic' => [],
- 'barTopic' => [],
- ]));
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('Found 2 topics', $output);
- }
-
- public function testShouldShowInfoAboutTopics()
- {
- $command = new TopicsCommand(new TopicMetaRegistry([
- 'fooTopic' => ['description' => 'fooDescription', 'processors' => ['fooSubscriber']],
- 'barTopic' => ['description' => 'barDescription', 'processors' => ['barSubscriber']],
- ]));
-
- $output = $this->executeCommand($command);
-
- $this->assertContains('fooTopic', $output);
- $this->assertContains('fooDescription', $output);
- $this->assertContains('fooSubscriber', $output);
- $this->assertContains('barTopic', $output);
- $this->assertContains('barDescription', $output);
- $this->assertContains('barSubscriber', $output);
- }
-
- /**
- * @param Command $command
- * @param string[] $arguments
- *
- * @return string
- */
- protected function executeCommand(Command $command, array $arguments = [])
- {
- $tester = new CommandTester($command);
- $tester->execute($arguments);
-
- return $tester->getDisplay();
- }
-}
diff --git a/pkg/enqueue/Tests/Symfony/Client/Mock/SetupBrokerExtensionCommand.php b/pkg/enqueue/Tests/Symfony/Client/Mock/SetupBrokerExtensionCommand.php
index 603fb090a..c21750592 100644
--- a/pkg/enqueue/Tests/Symfony/Client/Mock/SetupBrokerExtensionCommand.php
+++ b/pkg/enqueue/Tests/Symfony/Client/Mock/SetupBrokerExtensionCommand.php
@@ -3,9 +3,10 @@
namespace Enqueue\Tests\Symfony\Client\Mock;
use Enqueue\Client\Config;
-use Enqueue\Client\NullDriver;
+use Enqueue\Client\Driver\GenericDriver;
+use Enqueue\Client\RouteCollection;
+use Enqueue\Null\NullContext;
use Enqueue\Symfony\Client\SetupBrokerExtensionCommandTrait;
-use Enqueue\Transport\Null\NullContext;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
@@ -28,8 +29,14 @@ protected function configure()
$this->configureSetupBrokerExtension();
}
- protected function execute(InputInterface $input, OutputInterface $output)
+ protected function execute(InputInterface $input, OutputInterface $output): int
{
- $this->extension = $this->getSetupBrokerExtension($input, new NullDriver(new NullContext(), Config::create()));
+ $this->extension = $this->getSetupBrokerExtension($input, new GenericDriver(
+ new NullContext(),
+ Config::create(),
+ new RouteCollection([])
+ ));
+
+ return 0;
}
}
diff --git a/pkg/enqueue/Tests/Symfony/Client/ProduceCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/ProduceCommandTest.php
new file mode 100644
index 000000000..daa909175
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/ProduceCommandTest.php
@@ -0,0 +1,284 @@
+assertClassExtends(Command::class, ProduceCommand::class);
+ }
+
+ public function testShouldNotBeFinal()
+ {
+ $this->assertClassNotFinal(ProduceCommand::class);
+ }
+
+ public function testShouldHaveAsCommandAttributeWithCommandName()
+ {
+ $commandClass = ProduceCommand::class;
+
+ $reflectionClass = new \ReflectionClass($commandClass);
+
+ $attributes = $reflectionClass->getAttributes(AsCommand::class);
+
+ $this->assertNotEmpty($attributes, 'The command does not have the AsCommand attribute.');
+
+ // Get the first attribute instance (assuming there is only one AsCommand attribute)
+ $asCommandAttribute = $attributes[0];
+
+ // Verify the 'name' parameter value
+ $attributeInstance = $asCommandAttribute->newInstance();
+ $this->assertEquals('enqueue:produce', $attributeInstance->name, 'The command name is not set correctly in the AsCommand attribute.');
+ }
+
+ public function testShouldHaveExpectedOptions()
+ {
+ $command = new ProduceCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $options = $command->getDefinition()->getOptions();
+ $this->assertCount(4, $options);
+ $this->assertArrayHasKey('client', $options);
+ $this->assertArrayHasKey('topic', $options);
+ $this->assertArrayHasKey('command', $options);
+ $this->assertArrayHasKey('header', $options);
+ }
+
+ public function testShouldHaveExpectedAttributes()
+ {
+ $command = new ProduceCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $arguments = $command->getDefinition()->getArguments();
+ $this->assertCount(1, $arguments);
+
+ $this->assertArrayHasKey('message', $arguments);
+ }
+
+ public function testThrowIfNeitherTopicNorCommandOptionsAreSet()
+ {
+ $producerMock = $this->createProducerMock();
+ $producerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $producerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $producerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Either topic or command option should be set, none is set.');
+ $tester->execute([
+ 'message' => 'theMessage',
+ ]);
+ }
+
+ public function testThrowIfBothTopicAndCommandOptionsAreSet()
+ {
+ $producerMock = $this->createProducerMock();
+ $producerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $producerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $producerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Either topic or command option should be set, both are set.');
+ $tester->execute([
+ 'message' => 'theMessage',
+ '--topic' => 'theTopic',
+ '--command' => 'theCommand',
+ ]);
+ }
+
+ public function testShouldSendEventToDefaultTransport()
+ {
+ $header = 'Content-Type: text/plain';
+ $payload = 'theMessage';
+
+ $producerMock = $this->createProducerMock();
+ $producerMock
+ ->expects($this->once())
+ ->method('sendEvent')
+ ->with('theTopic', new Message($payload, [], [$header]))
+ ;
+ $producerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $producerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => $payload,
+ '--header' => $header,
+ '--topic' => 'theTopic',
+ ]);
+ }
+
+ public function testShouldSendCommandToDefaultTransport()
+ {
+ $producerMock = $this->createProducerMock();
+ $producerMock
+ ->expects($this->once())
+ ->method('sendCommand')
+ ->with('theCommand', 'theMessage')
+ ;
+ $producerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $producerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => 'theMessage',
+ '--command' => 'theCommand',
+ ]);
+ }
+
+ public function testShouldSendEventToFooTransport()
+ {
+ $header = 'Content-Type: text/plain';
+ $payload = 'theMessage';
+
+ $defaultProducerMock = $this->createProducerMock();
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $fooProducerMock = $this->createProducerMock();
+ $fooProducerMock
+ ->expects($this->once())
+ ->method('sendEvent')
+ ->with('theTopic', new Message($payload, [], [$header]))
+ ;
+ $fooProducerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $defaultProducerMock,
+ 'enqueue.client.foo.producer' => $fooProducerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => $payload,
+ '--header' => $header,
+ '--topic' => 'theTopic',
+ '--client' => 'foo',
+ ]);
+ }
+
+ public function testShouldSendCommandToFooTransport()
+ {
+ $defaultProducerMock = $this->createProducerMock();
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $fooProducerMock = $this->createProducerMock();
+ $fooProducerMock
+ ->expects($this->once())
+ ->method('sendCommand')
+ ->with('theCommand', 'theMessage')
+ ;
+ $fooProducerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $defaultProducerMock,
+ 'enqueue.client.foo.producer' => $fooProducerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ 'message' => 'theMessage',
+ '--command' => 'theCommand',
+ '--client' => 'foo',
+ ]);
+ }
+
+ public function testThrowIfClientNotFound()
+ {
+ $defaultProducerMock = $this->createProducerMock();
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendEvent')
+ ;
+ $defaultProducerMock
+ ->expects($this->never())
+ ->method('sendCommand')
+ ;
+
+ $command = new ProduceCommand(new Container([
+ 'enqueue.client.default.producer' => $defaultProducerMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Client "bar" is not supported.');
+ $tester->execute([
+ 'message' => 'theMessage',
+ '--command' => 'theCommand',
+ '--client' => 'bar',
+ ]);
+ }
+
+ /**
+ * @return \PHPUnit\Framework\MockObject\MockObject|ProducerInterface
+ */
+ private function createProducerMock()
+ {
+ return $this->createMock(ProducerInterface::class);
+ }
+}
diff --git a/pkg/enqueue/Tests/Symfony/Client/ProduceMessageCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/ProduceMessageCommandTest.php
deleted file mode 100644
index 6cbfbfc1d..000000000
--- a/pkg/enqueue/Tests/Symfony/Client/ProduceMessageCommandTest.php
+++ /dev/null
@@ -1,74 +0,0 @@
-createProducerMock());
- }
-
- public function testShouldHaveCommandName()
- {
- $command = new ProduceMessageCommand($this->createProducerMock());
-
- $this->assertEquals('enqueue:produce', $command->getName());
- }
-
- public function testShouldHaveCommandAliases()
- {
- $command = new ProduceMessageCommand($this->createProducerMock());
-
- $this->assertEquals(['enq:p'], $command->getAliases());
- }
-
- public function testShouldHaveExpectedOptions()
- {
- $command = new ProduceMessageCommand($this->createProducerMock());
-
- $options = $command->getDefinition()->getOptions();
- $this->assertCount(0, $options);
- }
-
- public function testShouldHaveExpectedAttributes()
- {
- $command = new ProduceMessageCommand($this->createProducerMock());
-
- $arguments = $command->getDefinition()->getArguments();
- $this->assertCount(2, $arguments);
-
- $this->assertArrayHasKey('topic', $arguments);
- $this->assertArrayHasKey('message', $arguments);
- }
-
- public function testShouldExecuteConsumptionAndUseDefaultQueueName()
- {
- $producerMock = $this->createProducerMock();
- $producerMock
- ->expects($this->once())
- ->method('send')
- ->with('theTopic', 'theMessage')
- ;
-
- $command = new ProduceMessageCommand($producerMock);
-
- $tester = new CommandTester($command);
- $tester->execute([
- 'topic' => 'theTopic',
- 'message' => 'theMessage',
- ]);
- }
-
- /**
- * @return \PHPUnit_Framework_MockObject_MockObject|MessageProducerInterface
- */
- private function createProducerMock()
- {
- return $this->createMock(MessageProducerInterface::class);
- }
-}
diff --git a/pkg/enqueue/Tests/Symfony/Client/RoutesCommandTest.php b/pkg/enqueue/Tests/Symfony/Client/RoutesCommandTest.php
new file mode 100644
index 000000000..89bd7f745
--- /dev/null
+++ b/pkg/enqueue/Tests/Symfony/Client/RoutesCommandTest.php
@@ -0,0 +1,366 @@
+assertClassExtends(Command::class, RoutesCommand::class);
+ }
+
+ public function testShouldNotBeFinal()
+ {
+ $this->assertClassNotFinal(RoutesCommand::class);
+ }
+
+ public function testShouldHaveAsCommandAttributeWithCommandName()
+ {
+ $commandClass = RoutesCommand::class;
+
+ $reflectionClass = new \ReflectionClass($commandClass);
+
+ $attributes = $reflectionClass->getAttributes(AsCommand::class);
+
+ $this->assertNotEmpty($attributes, 'The command does not have the AsCommand attribute.');
+
+ // Get the first attribute instance (assuming there is only one AsCommand attribute)
+ $asCommandAttribute = $attributes[0];
+
+ // Verify the 'name' parameter value
+ $attributeInstance = $asCommandAttribute->newInstance();
+ $this->assertEquals('enqueue:routes', $attributeInstance->name, 'The command name is not set correctly in the AsCommand attribute.');
+ }
+
+ public function testShouldHaveCommandAliases()
+ {
+ $command = new RoutesCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $this->assertEquals(['debug:enqueue:routes'], $command->getAliases());
+ }
+
+ public function testShouldHaveExpectedOptions()
+ {
+ $command = new RoutesCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $options = $command->getDefinition()->getOptions();
+ $this->assertCount(2, $options);
+
+ $this->assertArrayHasKey('show-route-options', $options);
+ $this->assertArrayHasKey('client', $options);
+ }
+
+ public function testShouldHaveExpectedAttributes()
+ {
+ $command = new RoutesCommand($this->createMock(ContainerInterface::class), 'default');
+
+ $arguments = $command->getDefinition()->getArguments();
+ $this->assertCount(0, $arguments);
+ }
+
+ public function testShouldOutputEmptyRouteCollection()
+ {
+ $routeCollection = new RouteCollection([]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $tester->execute([]);
+
+ $expectedOutput = <<<'OUTPUT'
+Found 0 routes
+
+
+OUTPUT;
+
+ $this->assertCommandOutput($expectedOutput, $tester);
+ }
+
+ public function testShouldUseFooDriver()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooTopic', Route::TOPIC, 'processor'),
+ ]);
+
+ $defaultDriverMock = $this->createMock(DriverInterface::class);
+ $defaultDriverMock
+ ->expects($this->never())
+ ->method('getConfig')
+ ;
+
+ $defaultDriverMock
+ ->expects($this->never())
+ ->method('getRouteCollection')
+ ;
+
+ $fooDriverMock = $this->createDriverStub(Config::create(), $routeCollection);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $defaultDriverMock,
+ 'enqueue.client.foo.driver' => $fooDriverMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+ $tester->execute([
+ '--client' => 'foo',
+ ]);
+
+ $this->assertStringContainsString('Found 1 routes', $tester->getDisplay());
+ }
+
+ public function testThrowIfClientNotFound()
+ {
+ $defaultDriverMock = $this->createMock(DriverInterface::class);
+ $defaultDriverMock
+ ->expects($this->never())
+ ->method('getConfig')
+ ;
+
+ $defaultDriverMock
+ ->expects($this->never())
+ ->method('getRouteCollection')
+ ;
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $defaultDriverMock,
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $this->expectException(\LogicException::class);
+ $this->expectExceptionMessage('Client "foo" is not supported.');
+ $tester->execute([
+ '--client' => 'foo',
+ ]);
+ }
+
+ public function testShouldOutputTopicRouteInfo()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooTopic', Route::TOPIC, 'processor'),
+ new Route('barTopic', Route::TOPIC, 'processor'),
+ ]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $tester->execute([]);
+
+ $expectedOutput = <<<'OUTPUT'
+Found 2 routes
+
++-------+----------+--------------------+-----------+----------+
+| Type | Source | Queue | Processor | Options |
++-------+----------+--------------------+-----------+----------+
+| topic | fooTopic | default (prefixed) | processor | (hidden) |
+| topic | barTopic | default (prefixed) | processor | (hidden) |
++-------+----------+--------------------+-----------+----------+
+
+OUTPUT;
+
+ $this->assertCommandOutput($expectedOutput, $tester);
+ }
+
+ public function testShouldOutputCommandRouteInfo()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'processor', ['foo' => 'fooVal', 'bar' => 'barVal']),
+ new Route('barCommand', Route::COMMAND, 'processor', ['foo' => 'fooVal', 'bar' => 'barVal']),
+ ]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $tester->execute([]);
+
+ $expectedOutput = <<<'OUTPUT'
+Found 2 routes
+
++---------+------------+--------------------+-----------+----------+
+| Type | Source | Queue | Processor | Options |
++---------+------------+--------------------+-----------+----------+
+| command | fooCommand | default (prefixed) | processor | (hidden) |
+| command | barCommand | default (prefixed) | processor | (hidden) |
++---------+------------+--------------------+-----------+----------+
+
+OUTPUT;
+
+ $this->assertCommandOutput($expectedOutput, $tester);
+ }
+
+ public function testShouldCorrectlyOutputPrefixedCustomQueue()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'processor', ['queue' => 'foo']),
+ new Route('barTopic', Route::TOPIC, 'processor', ['queue' => 'bar']),
+ ]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $exitCode = $tester->execute([]);
+ $this->assertSame(0, $exitCode);
+
+ $expectedOutput = <<<'OUTPUT'
+Found 2 routes
+
++---------+------------+----------------+-----------+----------+
+| Type | Source | Queue | Processor | Options |
++---------+------------+----------------+-----------+----------+
+| topic | barTopic | bar (prefixed) | processor | (hidden) |
+| command | fooCommand | foo (prefixed) | processor | (hidden) |
++---------+------------+----------------+-----------+----------+
+
+OUTPUT;
+
+ $this->assertCommandOutput($expectedOutput, $tester);
+ }
+
+ public function testShouldCorrectlyOutputNotPrefixedCustomQueue()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'processor', ['queue' => 'foo', 'prefix_queue' => false]),
+ new Route('barTopic', Route::TOPIC, 'processor', ['queue' => 'bar', 'prefix_queue' => false]),
+ ]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $tester->execute([]);
+
+ $expectedOutput = <<<'OUTPUT'
+Found 2 routes
+
++---------+------------+-------------+-----------+----------+
+| Type | Source | Queue | Processor | Options |
++---------+------------+-------------+-----------+----------+
+| topic | barTopic | bar (as is) | processor | (hidden) |
+| command | fooCommand | foo (as is) | processor | (hidden) |
++---------+------------+-------------+-----------+----------+
+
+OUTPUT;
+
+ $this->assertCommandOutput($expectedOutput, $tester);
+ }
+
+ public function testShouldCorrectlyOutputExternalRoute()
+ {
+ $routeCollection = new RouteCollection([
+ new Route('fooCommand', Route::COMMAND, 'processor', ['external' => true]),
+ new Route('barTopic', Route::TOPIC, 'processor', ['external' => true]),
+ ]);
+
+ $command = new RoutesCommand(new Container([
+ 'enqueue.client.default.driver' => $this->createDriverStub(Config::create(), $routeCollection),
+ ]), 'default');
+
+ $tester = new CommandTester($command);
+
+ $tester->execute([]);
+
+ $expectedOutput = <<