diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ffabadbe..de8cfaf9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -226,7 +226,7 @@ parameters: path: test/DependencyInjection/SentryExtensionTest.php - - message: "#^Method Sentry\\\\SentryBundle\\\\Test\\\\End2End\\\\App\\\\Kernel\\:\\:build\\(\\) has no return typehint specified\\.$#" + message: "#^Comparison operation \"\\>\\=\" between 50102 and 40300 is always true\\.$#" count: 1 path: test/End2End/App/Kernel.php @@ -240,6 +240,11 @@ parameters: count: 1 path: test/End2End/End2EndTest.php + - + message: "#^Comparison operation \"\\<\" between 50102 and 40300 is always false\\.$#" + count: 1 + path: test/End2End/End2EndTest.php + - message: "#^Method Sentry\\\\SentryBundle\\\\Test\\\\ErrorTypesParserTest\\:\\:testParse\\(\\) has parameter \\$value with no typehint specified\\.$#" count: 1 diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php index 10ffe610..e2945b27 100644 --- a/src/DependencyInjection/Configuration.php +++ b/src/DependencyInjection/Configuration.php @@ -143,7 +143,7 @@ public function getConfigTreeBuilder(): TreeBuilder $listenerPriorities->scalarNode('console_error') ->defaultValue(128); $listenerPriorities->scalarNode('worker_error') - ->defaultValue(128); + ->defaultValue(99); // Monolog handler configuration $monologConfiguration = $rootNode->children() diff --git a/test/DependencyInjection/ConfigurationTest.php b/test/DependencyInjection/ConfigurationTest.php index 2d5a3d5e..2f3027ee 100644 --- a/test/DependencyInjection/ConfigurationTest.php +++ b/test/DependencyInjection/ConfigurationTest.php @@ -51,7 +51,7 @@ public function testConfigurationDefaults(): void 'console' => 1, 'request_error' => 128, 'console_error' => 128, - 'worker_error' => 128, + 'worker_error' => 99, ], 'options' => [ 'class_serializers' => [], diff --git a/test/End2End/App/Controller/MessengerController.php b/test/End2End/App/Controller/MessengerController.php new file mode 100644 index 00000000..b7fb69b9 --- /dev/null +++ b/test/End2End/App/Controller/MessengerController.php @@ -0,0 +1,32 @@ +messenger = $messenger; + } + + public function dispatchMessage(): Response + { + $this->messenger->dispatch(new FooMessage()); + + return new Response('Success'); + } + + public function dispatchUnrecoverableMessage(): Response + { + $this->messenger->dispatch(new FooMessage(false)); + + return new Response('Success'); + } +} diff --git a/test/End2End/App/Kernel.php b/test/End2End/App/Kernel.php index b66bab8e..11a5785f 100644 --- a/test/End2End/App/Kernel.php +++ b/test/End2End/App/Kernel.php @@ -6,31 +6,35 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\BundleInterface; use Symfony\Component\HttpKernel\Kernel as SymfonyKernel; +use Symfony\Component\Messenger\MessageBusInterface; class Kernel extends SymfonyKernel { /** * @return BundleInterface[] */ - public function registerBundles() + public function registerBundles(): array { - $bundles = [ + return [ new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new \Symfony\Bundle\MonologBundle\MonologBundle(), new \Sentry\SentryBundle\SentryBundle(), ]; - - return $bundles; } public function registerContainerConfiguration(LoaderInterface $loader): void { $loader->load(__DIR__ . '/config.yml'); + + if (interface_exists(MessageBusInterface::class) && self::VERSION_ID >= 40300) { + $loader->load(__DIR__ . '/messenger.yml'); + } } - protected function build(ContainerBuilder $container) + protected function build(ContainerBuilder $container): void { $container->setParameter('routing_config_dir', __DIR__); + parent::build($container); } } diff --git a/test/End2End/App/Messenger/FooMessage.php b/test/End2End/App/Messenger/FooMessage.php new file mode 100644 index 00000000..d340c992 --- /dev/null +++ b/test/End2End/App/Messenger/FooMessage.php @@ -0,0 +1,19 @@ +shouldRetry = $shouldRetry; + } + + public function shouldRetry(): bool + { + return $this->shouldRetry; + } +} diff --git a/test/End2End/App/Messenger/FooMessageHandler.php b/test/End2End/App/Messenger/FooMessageHandler.php new file mode 100644 index 00000000..9ff6bbdf --- /dev/null +++ b/test/End2End/App/Messenger/FooMessageHandler.php @@ -0,0 +1,19 @@ +shouldRetry()) { + throw new class() extends \Exception implements UnrecoverableExceptionInterface { + }; + } + + throw new \Exception('This is an intentional failure while handling a message of class ' . get_class($message)); + } +} diff --git a/test/End2End/App/Messenger/StaticInMemoryTransport.php b/test/End2End/App/Messenger/StaticInMemoryTransport.php new file mode 100644 index 00000000..8c9a4a6c --- /dev/null +++ b/test/End2End/App/Messenger/StaticInMemoryTransport.php @@ -0,0 +1,93 @@ +getMessage()); + unset(self::$queue[$id]); + } + + /** + * {@inheritdoc} + */ + public function reject(Envelope $envelope): void + { + self::$rejected[] = $envelope; + $id = spl_object_hash($envelope->getMessage()); + unset(self::$queue[$id]); + } + + /** + * {@inheritdoc} + */ + public function send(Envelope $envelope): Envelope + { + self::$sent[] = $envelope; + $id = spl_object_hash($envelope->getMessage()); + self::$queue[$id] = $envelope; + + return $envelope; + } + + public static function reset(): void + { + self::$sent = self::$queue = self::$rejected = self::$acknowledged = []; + } + + /** + * @return Envelope[] + */ + public function getAcknowledged(): array + { + return self::$acknowledged; + } + + /** + * @return Envelope[] + */ + public function getRejected(): array + { + return self::$rejected; + } + + /** + * @return Envelope[] + */ + public function getSent(): array + { + return self::$sent; + } +} diff --git a/test/End2End/App/Messenger/StaticInMemoryTransportFactory.php b/test/End2End/App/Messenger/StaticInMemoryTransportFactory.php new file mode 100644 index 00000000..49a55bf2 --- /dev/null +++ b/test/End2End/App/Messenger/StaticInMemoryTransportFactory.php @@ -0,0 +1,26 @@ +assertLastEventIdIsNotNull($client); } + public function testMessengerCaptureHardFailure(): void + { + $this->skipIfMessengerIsMissing(); + + $client = static::createClient(); + + $client->request('GET', '/dispatch-unrecoverable-message'); + + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + + $this->consumeOneMessage($client->getKernel()); + + $this->assertLastEventIdIsNotNull($client); + } + + public function testMessengerCaptureSoftFailCanBeDisabled(): void + { + $this->skipIfMessengerIsMissing(); + + $client = static::createClient(); + + $client->request('GET', '/dispatch-message'); + + $response = $client->getResponse(); + + $this->assertInstanceOf(Response::class, $response); + $this->assertSame(200, $response->getStatusCode()); + + $this->consumeOneMessage($client->getKernel()); + + $this->assertLastEventIdIsNull($client); + } + + private function consumeOneMessage(KernelInterface $kernel): void + { + $application = new Application($kernel); + + $command = $application->find('messenger:consume'); + $commandTester = new CommandTester($command); + $commandTester->execute([ + 'receivers' => ['async'], + '--limit' => 1, + '--time-limit' => 1, + '-vvv' => true, + ]); + + $this->assertSame(0, $commandTester->getStatusCode()); + } + private function assertLastEventIdIsNotNull(KernelBrowser $client): void { $container = $client->getContainer(); @@ -123,4 +179,22 @@ private function assertLastEventIdIsNotNull(KernelBrowser $client): void $this->assertNotNull($hub->getLastEventId(), 'Last error not captured'); } + + private function assertLastEventIdIsNull(KernelBrowser $client): void + { + $container = $client->getContainer(); + $this->assertNotNull($container); + + $hub = $container->get('test.hub'); + $this->assertInstanceOf(HubInterface::class, $hub); + + $this->assertNull($hub->getLastEventId(), 'Some error was captured'); + } + + private function skipIfMessengerIsMissing(): void + { + if (! interface_exists(MessageBusInterface::class) || Kernel::VERSION_ID < 40300) { + $this->markTestSkipped('Messenger missing'); + } + } }