name: FailureIntegrationTest class_comment: null dependencies: - name: TestCase type: class source: PHPUnit\Framework\TestCase - name: NullLogger type: class source: Psr\Log\NullLogger - name: Container type: class source: Symfony\Component\DependencyInjection\Container - name: ServiceLocator type: class source: Symfony\Component\DependencyInjection\ServiceLocator - name: EventDispatcher type: class source: Symfony\Component\EventDispatcher\EventDispatcher - name: Envelope type: class source: Symfony\Component\Messenger\Envelope - name: WorkerMessageFailedEvent type: class source: Symfony\Component\Messenger\Event\WorkerMessageFailedEvent - name: AddErrorDetailsStampListener type: class source: Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener - name: SendFailedMessageForRetryListener type: class source: Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener - name: SendFailedMessageToFailureTransportListener type: class source: Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener - name: StopWorkerOnMessageLimitListener type: class source: Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener - name: DelayedMessageHandlingException type: class source: Symfony\Component\Messenger\Exception\DelayedMessageHandlingException - name: HandlerFailedException type: class source: Symfony\Component\Messenger\Exception\HandlerFailedException - name: ValidationFailedException type: class source: Symfony\Component\Messenger\Exception\ValidationFailedException - name: HandlerDescriptor type: class source: Symfony\Component\Messenger\Handler\HandlerDescriptor - name: HandlersLocator type: class source: Symfony\Component\Messenger\Handler\HandlersLocator - name: MessageBus type: class source: Symfony\Component\Messenger\MessageBus - name: AddBusNameStampMiddleware type: class source: Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware - name: DispatchAfterCurrentBusMiddleware type: class source: Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware - name: FailedMessageProcessingMiddleware type: class source: Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware - name: HandleMessageMiddleware type: class source: Symfony\Component\Messenger\Middleware\HandleMessageMiddleware - name: SendMessageMiddleware type: class source: Symfony\Component\Messenger\Middleware\SendMessageMiddleware - name: ValidationMiddleware type: class source: Symfony\Component\Messenger\Middleware\ValidationMiddleware - name: MultiplierRetryStrategy type: class source: Symfony\Component\Messenger\Retry\MultiplierRetryStrategy - name: BusNameStamp type: class source: Symfony\Component\Messenger\Stamp\BusNameStamp - name: DispatchAfterCurrentBusStamp type: class source: Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp - name: ErrorDetailsStamp type: class source: Symfony\Component\Messenger\Stamp\ErrorDetailsStamp - name: SentToFailureTransportStamp type: class source: Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp - name: DummyMessage type: class source: Symfony\Component\Messenger\Tests\Fixtures\DummyMessage - name: ReceiverInterface type: class source: Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface - name: SenderInterface type: class source: Symfony\Component\Messenger\Transport\Sender\SenderInterface - name: SendersLocator type: class source: Symfony\Component\Messenger\Transport\Sender\SendersLocator - name: Worker type: class source: Symfony\Component\Messenger\Worker - name: ConstraintViolation type: class source: Symfony\Component\Validator\ConstraintViolation - name: ConstraintViolationList type: class source: Symfony\Component\Validator\ConstraintViolationList - name: ValidatorInterface type: class source: Symfony\Component\Validator\Validator\ValidatorInterface properties: [] methods: - name: testRequeueMechanism visibility: public parameters: [] comment: null - name: getMessagesWaitingToBeReceived visibility: public parameters: [] comment: "# @var Envelope $failedEnvelope */\n# $failedEnvelope = $failureTransport->getMessagesWaitingToBeReceived()[0];\n\ # /** @var SentToFailureTransportStamp $sentToFailureStamp */\n# $sentToFailureStamp\ \ = $failedEnvelope->last(SentToFailureTransportStamp::class);\n# $this->assertNotNull($sentToFailureStamp);\n\ # /** @var ErrorDetailsStamp $errorDetailsStamp */\n# $errorDetailsStamp = $failedEnvelope->last(ErrorDetailsStamp::class);\n\ # $this->assertNotNull($errorDetailsStamp);\n# $this->assertSame('Failure from\ \ call 2', $errorDetailsStamp->getExceptionMessage());\n# \n# /*\n# * Failed message\ \ is handled, fails, and sent for a retry\n# */\n# $throwable = $runWorker('the_failure_transport');\n\ # // make sure this is failing for the reason we think\n# $this->assertInstanceOf(HandlerFailedException::class,\ \ $throwable);\n# // only the \"failed\" handler is called a 3rd time\n# $this->assertSame(3,\ \ $transport1HandlerThatFails->getTimesCalled());\n# $this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());\n\ # // handling fails again, message is retried\n# $this->assertCount(1, $failureTransport->getMessagesWaitingToBeReceived());\n\ # // transport2 still only holds the original message\n# // a new message was\ \ never mistakenly delivered to it\n# $this->assertCount(1, $transport2->getMessagesWaitingToBeReceived());\n\ # \n# /*\n# * Message is retried on failure transport then discarded\n# */\n#\ \ $runWorker('the_failure_transport');\n# // only the \"failed\" handler is called\ \ a 4th time\n# $this->assertSame(4, $transport1HandlerThatFails->getTimesCalled());\n\ # $this->assertSame(1, $allTransportHandlerThatWorks->getTimesCalled());\n# //\ \ handling fails again, message is discarded\n# $this->assertCount(0, $failureTransport->getMessagesWaitingToBeReceived());\n\ # \n# /*\n# * Execute handlers on transport2\n# */\n# $runWorker('transport2');\n\ # // transport1 handler is not called again\n# $this->assertSame(4, $transport1HandlerThatFails->getTimesCalled());\n\ # // all transport handler is now called again\n# $this->assertSame(2, $allTransportHandlerThatWorks->getTimesCalled());\n\ # // transport1 handler called for the first time\n# $this->assertSame(1, $transport2HandlerThatWorks->getTimesCalled());\n\ # // all transport should be empty\n# $this->assertEmpty($transport1->getMessagesWaitingToBeReceived());\n\ # $this->assertEmpty($transport2->getMessagesWaitingToBeReceived());\n# $this->assertEmpty($failureTransport->getMessagesWaitingToBeReceived());\n\ # \n# /*\n# * Dispatch the original message again\n# */\n# $bus->dispatch($envelope);\n\ # // handle the failing message so it goes into the failure transport\n# $runWorker('transport1');\n\ # $runWorker('transport1');\n# // now make the handler work!\n# $transport1HandlerThatFails->setShouldThrow(false);\n\ # $runWorker('the_failure_transport');\n# // the failure transport is empty because\ \ it worked\n# $this->assertEmpty($failureTransport->getMessagesWaitingToBeReceived());\n\ # }\n# \n# public function testMultipleFailedTransportsWithoutGlobalFailureTransport()\n\ # {\n# $transport1 = new DummyFailureTestSenderAndReceiver();\n# $transport2 =\ \ new DummyFailureTestSenderAndReceiver();\n# $failureTransport1 = new DummyFailureTestSenderAndReceiver();\n\ # $failureTransport2 = new DummyFailureTestSenderAndReceiver();\n# \n# $sendersLocatorFailureTransport\ \ = new ServiceLocator([\n# 'transport1' => fn () => $failureTransport1,\n# 'transport2'\ \ => fn () => $failureTransport2,\n# ]);\n# \n# $transports = [\n# 'transport1'\ \ => $transport1,\n# 'transport2' => $transport2,\n# 'the_failure_transport1'\ \ => $failureTransport1,\n# 'the_failure_transport2' => $failureTransport2,\n\ # ];\n# \n# $locator = new Container();\n# \n# foreach ($transports as $transportName\ \ => $transport) {\n# $locator->set($transportName, $transport);\n# }\n# \n# $senderLocator\ \ = new SendersLocator(\n# [DummyMessage::class => ['transport1', 'transport2']],\n\ # $locator\n# );\n# \n# // using to so we can lazily get the bus later and avoid\ \ circular problem\n# $transport1HandlerThatFails = new DummyTestHandler(true);\n\ # $transport2HandlerThatFails = new DummyTestHandler(true);\n# $handlerLocator\ \ = new HandlersLocator([\n# DummyMessage::class => [\n# new HandlerDescriptor($transport1HandlerThatFails,\ \ [\n# 'from_transport' => 'transport1',\n# ]),\n# new HandlerDescriptor($transport2HandlerThatFails,\ \ [\n# 'from_transport' => 'transport2',\n# ]),\n# ],\n# ]);\n# \n# $dispatcher\ \ = new EventDispatcher();\n# $bus = new MessageBus([\n# new FailedMessageProcessingMiddleware(),\n\ # new SendMessageMiddleware($senderLocator),\n# new HandleMessageMiddleware($handlerLocator),\n\ # ]);\n# \n# $dispatcher->addSubscriber(new SendFailedMessageForRetryListener($locator,\ \ new Container()));\n# $dispatcher->addSubscriber(new SendFailedMessageToFailureTransportListener(\n\ # $sendersLocatorFailureTransport,\n# new NullLogger()\n# ));\n# $dispatcher->addSubscriber(new\ \ StopWorkerOnMessageLimitListener(1));\n# \n# $runWorker = function (string $transportName)\ \ use ($transports, $bus, $dispatcher): ?\\Throwable {\n# $throwable = null;\n\ # $failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable)\ \ {\n# $throwable = $event->getThrowable();\n# };\n# $dispatcher->addListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# $worker = new Worker([$transportName => $transports[$transportName]],\ \ $bus, $dispatcher);\n# \n# $worker->run();\n# \n# $dispatcher->removeListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# return $throwable;\n# };\n# \n# // send the message\n\ # $envelope = new Envelope(new DummyMessage('API'));\n# $bus->dispatch($envelope);\n\ # \n# // message has been sent\n# $this->assertCount(1, $transport1->getMessagesWaitingToBeReceived());\n\ # $this->assertCount(1, $transport2->getMessagesWaitingToBeReceived());\n# $this->assertCount(0,\ \ $failureTransport1->getMessagesWaitingToBeReceived());\n# $this->assertCount(0,\ \ $failureTransport2->getMessagesWaitingToBeReceived());\n# \n# // Receive the\ \ message from \"transport1\"\n# $throwable = $runWorker('transport1');\n# $this->assertInstanceOf(HandlerFailedException::class,\ \ $throwable);\n# // handler for transport1 is called\n# $this->assertSame(1,\ \ $transport1HandlerThatFails->getTimesCalled());\n# $this->assertSame(0, $transport2HandlerThatFails->getTimesCalled());\n\ # // one handler failed and the message is sent to the failed transport of transport1\n\ # $this->assertCount(1, $failureTransport1->getMessagesWaitingToBeReceived());\n\ # $this->assertCount(0, $failureTransport2->getMessagesWaitingToBeReceived());\n\ # \n# // consume the failure message failed on \"transport1\"\n# $runWorker('the_failure_transport1');\n\ # // \"transport1\" handler is called again from the \"the_failed_transport1\"\ \ and it fails\n# $this->assertSame(2, $transport1HandlerThatFails->getTimesCalled());\n\ # $this->assertSame(0, $transport2HandlerThatFails->getTimesCalled());\n# $this->assertCount(0,\ \ $failureTransport1->getMessagesWaitingToBeReceived());\n# $this->assertCount(0,\ \ $failureTransport2->getMessagesWaitingToBeReceived());\n# \n# // Receive the\ \ message from \"transport2\"\n# $throwable = $runWorker('transport2');\n# $this->assertInstanceOf(HandlerFailedException::class,\ \ $throwable);\n# $this->assertSame(2, $transport1HandlerThatFails->getTimesCalled());\n\ # // handler for \"transport2\" is called\n# $this->assertSame(1, $transport2HandlerThatFails->getTimesCalled());\n\ # $this->assertCount(0, $failureTransport1->getMessagesWaitingToBeReceived());\n\ # // the failure transport \"the_failure_transport2\" has 1 new message failed\ \ from \"transport2\"\n# $this->assertCount(1, $failureTransport2->getMessagesWaitingToBeReceived());\n\ # \n# // Consume the failure message failed on \"transport2\"\n# $runWorker('the_failure_transport2');\n\ # $this->assertSame(2, $transport1HandlerThatFails->getTimesCalled());\n# // \"\ transport2\" handler is called again from the \"the_failed_transport2\" and it\ \ fails\n# $this->assertSame(2, $transport2HandlerThatFails->getTimesCalled());\n\ # $this->assertCount(0, $failureTransport1->getMessagesWaitingToBeReceived());\n\ # // After the message fails again, the message is discarded from the \"the_failure_transport2\"\ \n# $this->assertCount(0, $failureTransport2->getMessagesWaitingToBeReceived());\n\ # }\n# \n# public function testStampsAddedByMiddlewaresDontDisappearWhenDelayedMessageFails()\n\ # {\n# $transport1 = new DummyFailureTestSenderAndReceiver();\n# \n# $transports\ \ = [\n# 'transport1' => $transport1,\n# ];\n# \n# $locator = new Container();\n\ # \n# foreach ($transports as $transportName => $transport) {\n# $locator->set($transportName,\ \ $transport);\n# }\n# \n# $senderLocator = new SendersLocator([], $locator);\n\ # \n# $retryStrategyLocator = new Container();\n# $retryStrategyLocator->set('transport1',\ \ new MultiplierRetryStrategy(1));\n# \n# $syncHandlerThatFails = new DummyTestHandler(true);\n\ # \n# $middlewareStack = new \\ArrayIterator([\n# new AddBusNameStampMiddleware('some.bus'),\n\ # new DispatchAfterCurrentBusMiddleware(),\n# new SendMessageMiddleware($senderLocator),\n\ # ]);\n# \n# $bus = new MessageBus($middlewareStack);\n# \n# $transport1Handler\ \ = fn () => $bus->dispatch(new \\stdClass(), [new DispatchAfterCurrentBusStamp()]);\n\ # \n# $handlerLocator = new HandlersLocator([\n# DummyMessage::class => [new HandlerDescriptor($transport1Handler)],\n\ # \\stdClass::class => [new HandlerDescriptor($syncHandlerThatFails)],\n# ]);\n\ # \n# $middlewareStack->append(new HandleMessageMiddleware($handlerLocator));\n\ # \n# $dispatcher = new EventDispatcher();\n# \n# $dispatcher->addSubscriber(new\ \ SendFailedMessageForRetryListener($locator, $retryStrategyLocator));\n# $dispatcher->addSubscriber(new\ \ StopWorkerOnMessageLimitListener(1));\n# \n# $runWorker = function (string $transportName)\ \ use ($transports, $bus, $dispatcher): ?\\Throwable {\n# $throwable = null;\n\ # $failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable)\ \ {\n# $throwable = $event->getThrowable();\n# };\n# $dispatcher->addListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# $worker = new Worker([$transportName => $transports[$transportName]],\ \ $bus, $dispatcher);\n# \n# $worker->run();\n# \n# $dispatcher->removeListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# return $throwable;\n# };\n# \n# // Simulate receive\ \ from external source\n# $transport1->send(new Envelope(new DummyMessage('API')));\n\ # \n# // Receive the message from \"transport1\"\n# $throwable = $runWorker('transport1');\n\ # \n# $this->assertInstanceOf(DelayedMessageHandlingException::class, $throwable,\ \ $throwable->getMessage());\n# $this->assertSame(1, $syncHandlerThatFails->getTimesCalled());\n\ # \n# $messagesWaiting = $transport1->getMessagesWaitingToBeReceived();\n# \n\ # // Stamps should not be dropped on message that's queued for retry\n# $this->assertCount(1,\ \ $messagesWaiting);\n# $this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());\n\ # }\n# \n# public function testStampsAddedByMiddlewaresDontDisappearWhenValidationFails()\n\ # {\n# $transport1 = new DummyFailureTestSenderAndReceiver();\n# \n# $transports\ \ = [\n# 'transport1' => $transport1,\n# ];\n# \n# $locator = new Container();\n\ # $locator->set('transport1', $transport1);\n# \n# $senderLocator = new SendersLocator([],\ \ $locator);\n# \n# $retryStrategyLocator = new Container();\n# $retryStrategyLocator->set('transport1',\ \ new MultiplierRetryStrategy(1));\n# \n# $violationList = new ConstraintViolationList([new\ \ ConstraintViolation('validation failed', null, [], null, null, null)]);\n# $validator\ \ = $this->createMock(ValidatorInterface::class);\n# $validator->expects($this->once())->method('validate')->willReturn($violationList);\n\ # \n# $middlewareStack = new \\ArrayIterator([\n# new AddBusNameStampMiddleware('some.bus'),\n\ # new ValidationMiddleware($validator),\n# new SendMessageMiddleware($senderLocator),\n\ # ]);\n# \n# $bus = new MessageBus($middlewareStack);\n# \n# $transport1Handler\ \ = fn () => $bus->dispatch(new \\stdClass(), [new DispatchAfterCurrentBusStamp()]);\n\ # \n# $handlerLocator = new HandlersLocator([\n# DummyMessage::class => [new HandlerDescriptor($transport1Handler)],\n\ # ]);\n# \n# $middlewareStack->append(new HandleMessageMiddleware($handlerLocator));\n\ # \n# $dispatcher = new EventDispatcher();\n# \n# $dispatcher->addSubscriber(new\ \ SendFailedMessageForRetryListener($locator, $retryStrategyLocator));\n# $dispatcher->addSubscriber(new\ \ StopWorkerOnMessageLimitListener(1));\n# \n# $runWorker = function (string $transportName)\ \ use ($transports, $bus, $dispatcher): ?\\Throwable {\n# $throwable = null;\n\ # $failedListener = function (WorkerMessageFailedEvent $event) use (&$throwable)\ \ {\n# $throwable = $event->getThrowable();\n# };\n# $dispatcher->addListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# $worker = new Worker([$transportName => $transports[$transportName]],\ \ $bus, $dispatcher);\n# \n# $worker->run();\n# \n# $dispatcher->removeListener(WorkerMessageFailedEvent::class,\ \ $failedListener);\n# \n# return $throwable;\n# };\n# \n# // Simulate receive\ \ from external source\n# $transport1->send(new Envelope(new DummyMessage('API')));\n\ # \n# // Receive the message from \"transport1\"\n# $throwable = $runWorker('transport1');\n\ # \n# $this->assertInstanceOf(ValidationFailedException::class, $throwable, $throwable->getMessage());\n\ # \n# $messagesWaiting = $transport1->getMessagesWaitingToBeReceived();\n# \n\ # // Stamps should not be dropped on message that's queued for retry\n# $this->assertCount(1,\ \ $messagesWaiting);\n# $this->assertSame('some.bus', $messagesWaiting[0]->last(BusNameStamp::class)?->getBusName());\n\ # }\n# }\n# \n# class DummyFailureTestSenderAndReceiver implements ReceiverInterface,\ \ SenderInterface\n# {\n# private array $messagesWaiting = [];\n# \n# public function\ \ get(): iterable\n# {\n# $message = array_shift($this->messagesWaiting);\n# \n\ # if (null === $message) {\n# return [];\n# }\n# \n# return [$message];\n# }\n\ # \n# public function ack(Envelope $envelope): void\n# {\n# }\n# \n# public function\ \ reject(Envelope $envelope): void\n# {\n# }\n# \n# public function send(Envelope\ \ $envelope): Envelope\n# {\n# $this->messagesWaiting[] = $envelope;\n# \n# return\ \ $envelope;\n# }\n# \n# /**\n# * @return Envelope[]" - name: __construct visibility: public parameters: - name: shouldThrow comment: null - name: __invoke visibility: public parameters: [] comment: null - name: getTimesCalled visibility: public parameters: [] comment: null - name: setShouldThrow visibility: public parameters: - name: shouldThrow comment: null traits: - PHPUnit\Framework\TestCase - Psr\Log\NullLogger - Symfony\Component\DependencyInjection\Container - Symfony\Component\DependencyInjection\ServiceLocator - Symfony\Component\EventDispatcher\EventDispatcher - Symfony\Component\Messenger\Envelope - Symfony\Component\Messenger\Event\WorkerMessageFailedEvent - Symfony\Component\Messenger\EventListener\AddErrorDetailsStampListener - Symfony\Component\Messenger\EventListener\SendFailedMessageForRetryListener - Symfony\Component\Messenger\EventListener\SendFailedMessageToFailureTransportListener - Symfony\Component\Messenger\EventListener\StopWorkerOnMessageLimitListener - Symfony\Component\Messenger\Exception\DelayedMessageHandlingException - Symfony\Component\Messenger\Exception\HandlerFailedException - Symfony\Component\Messenger\Exception\ValidationFailedException - Symfony\Component\Messenger\Handler\HandlerDescriptor - Symfony\Component\Messenger\Handler\HandlersLocator - Symfony\Component\Messenger\MessageBus - Symfony\Component\Messenger\Middleware\AddBusNameStampMiddleware - Symfony\Component\Messenger\Middleware\DispatchAfterCurrentBusMiddleware - Symfony\Component\Messenger\Middleware\FailedMessageProcessingMiddleware - Symfony\Component\Messenger\Middleware\HandleMessageMiddleware - Symfony\Component\Messenger\Middleware\SendMessageMiddleware - Symfony\Component\Messenger\Middleware\ValidationMiddleware - Symfony\Component\Messenger\Retry\MultiplierRetryStrategy - Symfony\Component\Messenger\Stamp\BusNameStamp - Symfony\Component\Messenger\Stamp\DispatchAfterCurrentBusStamp - Symfony\Component\Messenger\Stamp\ErrorDetailsStamp - Symfony\Component\Messenger\Stamp\SentToFailureTransportStamp - Symfony\Component\Messenger\Tests\Fixtures\DummyMessage - Symfony\Component\Messenger\Transport\Receiver\ReceiverInterface - Symfony\Component\Messenger\Transport\Sender\SenderInterface - Symfony\Component\Messenger\Transport\Sender\SendersLocator - Symfony\Component\Messenger\Worker - Symfony\Component\Validator\ConstraintViolation - Symfony\Component\Validator\ConstraintViolationList - Symfony\Component\Validator\Validator\ValidatorInterface interfaces: - ReceiverInterface