diff --git a/composer.json b/composer.json index fd3e594f..d3e1ca15 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,7 @@ "require-dev": { "maglnet/composer-require-checker": "^4.7.1", "phpbench/phpbench": "^1.4.1", - "phpunit/phpunit": "^10.5.45", + "phpunit/phpunit": "^10.5.45 || ^12.4", "rector/rector": "^2.0.11", "roave/infection-static-analysis-plugin": "^1.35", "spatie/phpunit-watcher": "^1.24", diff --git a/src/Debug/QueueDecorator.php b/src/Debug/QueueDecorator.php index adc8e6b1..0c4294e6 100644 --- a/src/Debug/QueueDecorator.php +++ b/src/Debug/QueueDecorator.php @@ -45,7 +45,7 @@ public function listen(): void $this->queue->listen(); } - public function withAdapter(AdapterInterface $adapter): QueueInterface + public function withAdapter(AdapterInterface $adapter): static { return new self($this->queue->withAdapter($adapter), $this->collector); } diff --git a/src/Queue.php b/src/Queue.php index faf890df..5088c3eb 100644 --- a/src/Queue.php +++ b/src/Queue.php @@ -109,7 +109,7 @@ public function status(string|int $id): JobStatus return $this->adapter->status($id); } - public function withAdapter(AdapterInterface $adapter): self + public function withAdapter(AdapterInterface $adapter): static { $new = clone $this; $new->adapter = $adapter; diff --git a/src/QueueInterface.php b/src/QueueInterface.php index 3c8b9a3a..f31d36c0 100644 --- a/src/QueueInterface.php +++ b/src/QueueInterface.php @@ -43,7 +43,7 @@ public function listen(): void; */ public function status(string|int $id): JobStatus; - public function withAdapter(AdapterInterface $adapter): self; + public function withAdapter(AdapterInterface $adapter): static; public function getChannel(): string; } diff --git a/stubs/StubQueue.php b/stubs/StubQueue.php index dc19ed62..4d2ed10a 100644 --- a/stubs/StubQueue.php +++ b/stubs/StubQueue.php @@ -46,7 +46,7 @@ public function getAdapter(): ?AdapterInterface return $this->adapter; } - public function withAdapter(AdapterInterface $adapter): QueueInterface + public function withAdapter(AdapterInterface $adapter): static { $new = clone $this; $new->adapter = $adapter; diff --git a/tests/App/DummyQueue.php b/tests/App/DummyQueue.php index 149845e7..579afb13 100644 --- a/tests/App/DummyQueue.php +++ b/tests/App/DummyQueue.php @@ -39,7 +39,7 @@ public function status(string|int $id): JobStatus throw new Exception('`status()` method is not implemented yet.'); } - public function withAdapter(AdapterInterface $adapter): QueueInterface + public function withAdapter(AdapterInterface $adapter): static { throw new Exception('`withAdapter()` method is not implemented yet.'); } diff --git a/tests/Unit/Cli/SignalLoopTest.php b/tests/Unit/Cli/SignalLoopTest.php new file mode 100644 index 00000000..37ed71ba --- /dev/null +++ b/tests/Unit/Cli/SignalLoopTest.php @@ -0,0 +1,116 @@ +canContinue()); + } + + public function testSuspendAndResume(): void + { + $loop = new SignalLoop(0); + pcntl_signal(\SIGALRM, static function (): void { + posix_kill(getmypid(), \SIGCONT); + }); + + posix_kill(getmypid(), \SIGTSTP); + pcntl_alarm(1); + + $start = microtime(true); + $result = $loop->canContinue(); + $elapsed = microtime(true) - $start; + + self::assertTrue($result); + self::assertGreaterThan(0.5, $elapsed); + } + + #[DataProvider('exitSignalProvider')] + public function testExitSignals(int $signal): void + { + $loop = new SignalLoop(0); + + self::assertTrue($loop->canContinue(), 'Loop should continue'); + posix_kill(getmypid(), $signal); + + self::assertFalse($loop->canContinue(), "Loop should not continue after receiving signal {$signal}"); + } + + public static function exitSignalProvider(): iterable + { + yield 'SIGHUP' => [\SIGHUP]; + yield 'SIGINT' => [\SIGINT]; + yield 'SIGTERM' => [\SIGTERM]; + } + + public function testResumeSignal(): void + { + $loop = new SignalLoop(0); + + // First suspend the loop + posix_kill(getmypid(), \SIGTSTP); + + // Then immediately resume + posix_kill(getmypid(), \SIGCONT); + + $start = microtime(true); + $result = $loop->canContinue(); + $elapsed = microtime(true) - $start; + + self::assertTrue($result); + self::assertLessThan(0.1, $elapsed, 'Loop should resume quickly without waiting'); + } + + public function testMultipleExitSignals(): void + { + $loop = new SignalLoop(0); + + // Send multiple exit signals + posix_kill(getmypid(), \SIGINT); + posix_kill(getmypid(), \SIGTERM); + + $result = $loop->canContinue(); + + self::assertFalse($result, 'Loop should not continue after receiving any exit signal'); + } + + public function testSuspendOverridesResume(): void + { + $loop = new SignalLoop(0); + + // Resume first + posix_kill(getmypid(), \SIGCONT); + // Then suspend + posix_kill(getmypid(), \SIGTSTP); + + // Set up alarm to resume after 1 second + pcntl_signal(\SIGALRM, static function (): void { + posix_kill(getmypid(), \SIGCONT); + }); + pcntl_alarm(1); + + $start = microtime(true); + $result = $loop->canContinue(); + $elapsed = microtime(true) - $start; + + self::assertTrue($result); + self::assertGreaterThan(0.5, $elapsed, 'Loop should wait for resume after suspend'); + } +} diff --git a/tests/Unit/Debug/QueueProviderInterfaceProxyTest.php b/tests/Unit/Debug/QueueProviderInterfaceProxyTest.php index 2bde59c5..8c404857 100644 --- a/tests/Unit/Debug/QueueProviderInterfaceProxyTest.php +++ b/tests/Unit/Debug/QueueProviderInterfaceProxyTest.php @@ -23,4 +23,14 @@ public function testGet(): void $this->assertInstanceOf(QueueDecorator::class, $factory->get('test')); } + + public function testHas(): void + { + $queueFactory = $this->createMock(QueueProviderInterface::class); + $queueFactory->expects($this->once())->method('has')->with('test')->willReturn(true); + $collector = new QueueCollector(); + $factory = new QueueProviderInterfaceProxy($queueFactory, $collector); + + $this->assertTrue($factory->has('test')); + } } diff --git a/tests/Unit/Debug/QueueWorkerInterfaceProxyTest.php b/tests/Unit/Debug/QueueWorkerInterfaceProxyTest.php new file mode 100644 index 00000000..acd63e3e --- /dev/null +++ b/tests/Unit/Debug/QueueWorkerInterfaceProxyTest.php @@ -0,0 +1,33 @@ +startup(); + $proxy = new QueueWorkerInterfaceProxy(new StubWorker(), $collector); + + $result = $proxy->process($message, new DummyQueue('chan')); + + self::assertSame($message, $result); + + $collected = $collector->getCollected(); + self::assertArrayHasKey('processingMessages', $collected); + self::assertArrayHasKey('chan', $collected['processingMessages']); + self::assertCount(1, $collected['processingMessages']['chan']); + self::assertSame($message, $collected['processingMessages']['chan'][0]); + } +} diff --git a/tests/Unit/Message/EnvelopeTraitTest.php b/tests/Unit/Message/EnvelopeTest.php similarity index 56% rename from tests/Unit/Message/EnvelopeTraitTest.php rename to tests/Unit/Message/EnvelopeTest.php index 6ae7f085..9dbba5f8 100644 --- a/tests/Unit/Message/EnvelopeTraitTest.php +++ b/tests/Unit/Message/EnvelopeTest.php @@ -6,8 +6,10 @@ use PHPUnit\Framework\TestCase; use Yiisoft\Queue\Tests\App\DummyEnvelope; +use Yiisoft\Queue\Message\EnvelopeInterface; +use Yiisoft\Queue\Message\Message; -final class EnvelopeTraitTest extends TestCase +final class EnvelopeTest extends TestCase { public function testFromData(): void { @@ -23,4 +25,14 @@ public function testFromData(): void $this->assertArrayHasKey('meta', $envelope->getMetadata()); $this->assertSame('data', $envelope->getMetadata()['meta']); } + + public function testNonArrayStackIsNormalized(): void + { + $base = new Message('handler', 'data', [EnvelopeInterface::ENVELOPE_STACK_KEY => 'oops']); + $wrapped = new DummyEnvelope($base, 'id-1'); + + $meta = $wrapped->getMetadata(); + self::assertIsArray($meta[EnvelopeInterface::ENVELOPE_STACK_KEY]); + self::assertSame([DummyEnvelope::class], $meta[EnvelopeInterface::ENVELOPE_STACK_KEY]); + } } diff --git a/tests/Unit/Message/JsonMessageSerializerTest.php b/tests/Unit/Message/JsonMessageSerializerTest.php index 46617994..e2495245 100644 --- a/tests/Unit/Message/JsonMessageSerializerTest.php +++ b/tests/Unit/Message/JsonMessageSerializerTest.php @@ -67,9 +67,7 @@ public function testDefaultMessageClassFallbackClassNotSet(): void $this->assertInstanceOf(Message::class, $message); } - /** - * @dataProvider dataUnsupportedPayloadFormat - */ + #[DataProvider('dataUnsupportedPayloadFormat')] public function testPayloadFormat(mixed $payload): void { $serializer = $this->createSerializer(); @@ -87,9 +85,7 @@ public static function dataUnsupportedPayloadFormat(): iterable yield 'null' => [null]; } - /** - * @dataProvider dataUnsupportedMetadataFormat - */ + #[DataProvider('dataUnsupportedMetadataFormat')] public function testMetadataFormat(mixed $meta): void { $payload = ['name' => 'handler', 'data' => 'test', 'meta' => $meta]; @@ -142,6 +138,7 @@ public function testUnserializeEnvelopeStack(): void ]; $serializer = $this->createSerializer(); + /** @var IdEnvelope $message */ $message = $serializer->unserialize(json_encode($payload)); $this->assertEquals($payload['data'], $message->getData()); @@ -185,6 +182,7 @@ public function testSerializeEnvelopeStack(): void $json, ); + /** @var IdEnvelope $message */ $message = $serializer->unserialize($json); $this->assertInstanceOf(IdEnvelope::class, $message); diff --git a/tests/Unit/Middleware/CallableFactoryTest.php b/tests/Unit/Middleware/CallableFactoryTest.php new file mode 100644 index 00000000..ce8247ad --- /dev/null +++ b/tests/Unit/Middleware/CallableFactoryTest.php @@ -0,0 +1,78 @@ + $invokable, + ]); + + $factory = new CallableFactory($container); + $callable = $factory->create('invokable'); + + self::assertIsCallable($callable); + self::assertSame('ok', $callable()); + } + + public function testCreateFromStaticMethodArray(): void + { + $class = new class () { + public static function ping(): string + { + return 'pong'; + } + }; + $className = $class::class; + $container = new SimpleContainer(); + + $factory = new CallableFactory($container); + $callable = $factory->create([$className, 'ping']); + + self::assertIsCallable($callable); + self::assertSame('pong', $callable()); + } + + public function testCreateFromContainerObjectMethod(): void + { + $service = new class () { + public function go(): string + { + return 'ok'; + } + }; + $className = $service::class; + $container = new SimpleContainer([ + $className => $service, + ]); + + $factory = new CallableFactory($container); + $callable = $factory->create([$className, 'go']); + + self::assertIsCallable($callable); + self::assertSame('ok', $callable()); + } + + public function testFriendlyException(): void + { + $e = new InvalidCallableConfigurationException(); + self::assertSame('Invalid callable configuration.', $e->getName()); + self::assertNotNull($e->getSolution()); + self::assertStringContainsString('callable', (string)$e->getSolution()); + } +} diff --git a/tests/Unit/Middleware/FailureHandling/FailureHandlingRequestTest.php b/tests/Unit/Middleware/FailureHandling/FailureHandlingRequestTest.php index 5ee524ac..534111ee 100644 --- a/tests/Unit/Middleware/FailureHandling/FailureHandlingRequestTest.php +++ b/tests/Unit/Middleware/FailureHandling/FailureHandlingRequestTest.php @@ -15,12 +15,23 @@ final class FailureHandlingRequestTest extends TestCase public function testImmutable(): void { $queue = $this->createMock(QueueInterface::class); - $failureHandlingRequest = new FailureHandlingRequest( - new Message('test', 'test'), - new Exception(), + $request1 = new FailureHandlingRequest( + new Message('test', null), + new Exception('exception 1'), $queue ); + $request2 = $request1->withQueue($queue); + $request3 = $request1->withException(new Exception('exception 2')); + $request4 = $request1->withMessage(new Message('test2', null)); - $this->assertNotSame($failureHandlingRequest, $failureHandlingRequest->withQueue($queue)); + $this->assertNotSame($request1, $request2); + + $this->assertNotSame($request1, $request3); + $this->assertEquals($request1->getException()->getMessage(), 'exception 1'); + $this->assertEquals($request3->getException()->getMessage(), 'exception 2'); + + $this->assertNotSame($request1, $request4); + $this->assertEquals($request1->getMessage()->getHandlerName(), 'test'); + $this->assertEquals($request4->getMessage()->getHandlerName(), 'test2'); } } diff --git a/tests/Unit/Middleware/Push/AdapterPushHandlerTest.php b/tests/Unit/Middleware/Push/AdapterPushHandlerTest.php new file mode 100644 index 00000000..3d41ecbb --- /dev/null +++ b/tests/Unit/Middleware/Push/AdapterPushHandlerTest.php @@ -0,0 +1,37 @@ +expectException(AdapterNotConfiguredException::class); + $handler->handlePush($request); + } + + public function testHandlePushUsesAdapter(): void + { + $handler = new AdapterPushHandler(); + $adapter = new FakeAdapter(); + $message = new Message('handler', 'data'); + $request = new PushRequest($message, $adapter); + + $result = $handler->handlePush($request); + + self::assertSame($message, $result->getMessage()); + self::assertSame([$message], $adapter->pushMessages); + } +} diff --git a/tests/Unit/WorkerTest.php b/tests/Unit/WorkerTest.php index 6d84b10b..b8f466f6 100644 --- a/tests/Unit/WorkerTest.php +++ b/tests/Unit/WorkerTest.php @@ -4,8 +4,10 @@ namespace Yiisoft\Queue\Tests\Unit; +use PHPUnit\Framework\Attributes\DataProvider; use Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use RuntimeException; use Yiisoft\Injector\Injector; use Yiisoft\Test\Support\Container\SimpleContainer; @@ -16,6 +18,9 @@ use Yiisoft\Queue\Middleware\Consume\ConsumeMiddlewareDispatcher; use Yiisoft\Queue\Middleware\Consume\MiddlewareFactoryConsumeInterface; use Yiisoft\Queue\Middleware\FailureHandling\FailureMiddlewareDispatcher; +use Yiisoft\Queue\Middleware\Consume\MiddlewareConsumeInterface; +use Yiisoft\Queue\Middleware\FailureHandling\FailureHandlingRequest; +use Yiisoft\Queue\Middleware\FailureHandling\MiddlewareFailureInterface; use Yiisoft\Queue\Middleware\FailureHandling\MiddlewareFactoryFailureInterface; use Yiisoft\Queue\QueueInterface; use Yiisoft\Queue\Tests\App\FakeHandler; @@ -25,87 +30,54 @@ final class WorkerTest extends TestCase { - public function testJobExecutedWithCallableHandler(): void + #[DataProvider('jobExecutedDataProvider')] + public function testJobExecuted($handler, array $containerServices): void { - $handleMessage = null; $message = new Message('simple', ['test-data']); $logger = new SimpleLogger(); - $container = new SimpleContainer(); - $handlers = [ - 'simple' => function (MessageInterface $message) use (&$handleMessage) { - $handleMessage = $message; - }, - ]; + $container = new SimpleContainer($containerServices); + $handlers = ['simple' => $handler]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container, $logger); $worker->process($message, $queue); - $this->assertSame($message, $handleMessage); + + $processedMessages = FakeHandler::$processedMessages; + FakeHandler::$processedMessages = []; + + $this->assertSame([$message], $processedMessages); $messages = $logger->getMessages(); $this->assertNotEmpty($messages); $this->assertStringContainsString('Processing message #null.', $messages[0]['message']); } - public function testJobExecutedWithDefinitionHandler(): void - { - $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => FakeHandler::class]; - - $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); - - $worker->process($message, $queue); - $this->assertSame([$message], $handler::$processedMessages); - } - - public function testJobExecutedWithDefinitionClassHandler(): void - { - $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => [FakeHandler::class, 'execute']]; - - $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); - - $worker->process($message, $queue); - $this->assertSame([$message], $handler::$processedMessages); - } - - public function testJobFailWithDefinitionNotFoundClassButExistInContainerHandler(): void - { - $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer(['not-found-class-name' => $handler]); - $handlers = ['simple' => ['not-found-class-name', 'execute']]; - - $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); - - $worker->process($message, $queue); - $this->assertSame([$message], $handler::$processedMessages); - } - - public function testJobExecutedWithStaticDefinitionHandler(): void + public static function jobExecutedDataProvider(): iterable { - $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); - $handler = new FakeHandler(); - $container = new SimpleContainer([FakeHandler::class => $handler]); - $handlers = ['simple' => FakeHandler::staticExecute(...)]; - - $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); - - $worker->process($message, $queue); - $this->assertSame([$message], $handler::$processedMessages); + yield 'definition' => [ + FakeHandler::class, + [FakeHandler::class => new FakeHandler()], + ]; + yield 'definition-class' => [ + [FakeHandler::class, 'execute'], + [FakeHandler::class => new FakeHandler()], + ]; + yield 'definition-not-found-class-but-exist-in-container' => [ + ['not-found-class-name', 'execute'], + ['not-found-class-name' => new FakeHandler()], + ]; + yield 'static-definition' => [ + FakeHandler::staticExecute(...), + [FakeHandler::class => new FakeHandler()], + ]; + yield 'callable' => [ + function (MessageInterface $message) { + FakeHandler::$processedMessages[] = $message; + }, + [], + ]; } public function testJobFailWithDefinitionUndefinedMethodHandler(): void @@ -113,13 +85,13 @@ public function testJobFailWithDefinitionUndefinedMethodHandler(): void $this->expectExceptionMessage('Queue handler with name "simple" does not exist'); $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); $handler = new FakeHandler(); $container = new SimpleContainer([FakeHandler::class => $handler]); $handlers = ['simple' => [FakeHandler::class, 'undefinedMethod']]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container); $worker->process($message, $queue); } @@ -134,8 +106,9 @@ public function testJobFailWithDefinitionUndefinedClassHandler(): void $container = new SimpleContainer([FakeHandler::class => $handler]); $handlers = ['simple' => ['UndefinedClass', 'handle']]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container, $logger); try { $worker->process($message, $queue); @@ -150,12 +123,12 @@ public function testJobFailWithDefinitionClassNotFoundInContainerHandler(): void { $this->expectExceptionMessage('Queue handler with name "simple" does not exist'); $message = new Message('simple', ['test-data']); - $logger = new SimpleLogger(); $container = new SimpleContainer(); $handlers = ['simple' => [FakeHandler::class, 'execute']]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container); $worker->process($message, $queue); } @@ -168,8 +141,9 @@ public function testJobFailWithDefinitionHandlerException(): void $container = new SimpleContainer([FakeHandler::class => $handler]); $handlers = ['simple' => [FakeHandler::class, 'executeWithException']]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container, $logger); try { $worker->process($message, $queue); @@ -189,28 +163,33 @@ public function testJobFailWithDefinitionHandlerException(): void private function createWorkerByParams( array $handlers, - LoggerInterface $logger, - ContainerInterface $container + ContainerInterface $container, + ?LoggerInterface $logger = null, ): Worker { + /** @var MiddlewareFactoryConsumeInterface&\PHPUnit\Framework\MockObject\MockObject $consumeMiddlewareFactory */ + $consumeMiddlewareFactory = $this->createMock(MiddlewareFactoryConsumeInterface::class); + /** @var MiddlewareFactoryFailureInterface&\PHPUnit\Framework\MockObject\MockObject $failureMiddlewareFactory */ + $failureMiddlewareFactory = $this->createMock(MiddlewareFactoryFailureInterface::class); + return new Worker( $handlers, - $logger, + $logger ?? new NullLogger(), new Injector($container), $container, - new ConsumeMiddlewareDispatcher($this->createMock(MiddlewareFactoryConsumeInterface::class)), - new FailureMiddlewareDispatcher($this->createMock(MiddlewareFactoryFailureInterface::class), []), + new ConsumeMiddlewareDispatcher($consumeMiddlewareFactory), + new FailureMiddlewareDispatcher($failureMiddlewareFactory, []), ); } public function testHandlerNotFoundInContainer(): void { $message = new Message('nonexistent', ['test-data']); - $logger = new SimpleLogger(); $container = new SimpleContainer(); $handlers = []; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Queue handler with name "nonexistent" does not exist'); @@ -220,7 +199,6 @@ public function testHandlerNotFoundInContainer(): void public function testHandlerInContainerNotImplementingInterface(): void { $message = new Message('invalid', ['test-data']); - $logger = new SimpleLogger(); $container = new SimpleContainer([ 'invalid' => new class () { public function handle(): void @@ -230,25 +208,67 @@ public function handle(): void ]); $handlers = []; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container); $this->expectException(RuntimeException::class); $this->expectExceptionMessage('Queue handler with name "invalid" does not exist'); $worker->process($message, $queue); } + public function testJobFailureIsHandledSuccessfully(): void + { + $message = new Message('simple', null); + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ + $queue = $this->createMock(QueueInterface::class); + $queue->method('getChannel')->willReturn('test-channel'); + + $originalException = new RuntimeException('Consume failed'); + /** @var MiddlewareConsumeInterface&\PHPUnit\Framework\MockObject\MockObject $consumeMiddleware */ + $consumeMiddleware = $this->createMock(MiddlewareConsumeInterface::class); + $consumeMiddleware->method('processConsume')->willThrowException($originalException); + + /** @var MiddlewareFactoryConsumeInterface&\PHPUnit\Framework\MockObject\MockObject $consumeMiddlewareFactory */ + $consumeMiddlewareFactory = $this->createMock(MiddlewareFactoryConsumeInterface::class); + $consumeMiddlewareFactory->method('createConsumeMiddleware')->willReturn($consumeMiddleware); + $consumeDispatcher = new ConsumeMiddlewareDispatcher($consumeMiddlewareFactory, 'simple'); + + $finalMessage = new Message('final', null); + /** @var MiddlewareFailureInterface&\PHPUnit\Framework\MockObject\MockObject $failureMiddleware */ + $failureMiddleware = $this->createMock(MiddlewareFailureInterface::class); + $failureMiddleware->method('processFailure')->willReturn(new FailureHandlingRequest($finalMessage, $originalException, $queue)); + + /** @var MiddlewareFactoryFailureInterface&\PHPUnit\Framework\MockObject\MockObject $failureMiddlewareFactory */ + $failureMiddlewareFactory = $this->createMock(MiddlewareFactoryFailureInterface::class); + $failureMiddlewareFactory->method('createFailureMiddleware')->willReturn($failureMiddleware); + $failureDispatcher = new FailureMiddlewareDispatcher($failureMiddlewareFactory, ['test-channel' => ['simple']]); + + $worker = new Worker( + ['simple' => fn () => null], + new NullLogger(), + new Injector(new SimpleContainer()), + new SimpleContainer(), + $consumeDispatcher, + $failureDispatcher + ); + + $result = $worker->process($message, $queue); + + self::assertSame($finalMessage, $result); + } + public function testStaticMethodHandler(): void { $message = new Message('static-handler', ['test-data']); - $logger = new SimpleLogger(); $container = new SimpleContainer(); $handlers = [ 'static-handler' => StaticMessageHandler::handle(...), ]; + /** @var \PHPUnit\Framework\MockObject\MockObject&QueueInterface $queue */ $queue = $this->createMock(QueueInterface::class); - $worker = $this->createWorkerByParams($handlers, $logger, $container); + $worker = $this->createWorkerByParams($handlers, $container); StaticMessageHandler::$wasHandled = false; $worker->process($message, $queue);