From 83028217d84d6b808723896edfa5bb7cc0b9906d Mon Sep 17 00:00:00 2001 From: Dmitrii Derepko Date: Sun, 14 Jan 2024 18:05:30 +0700 Subject: [PATCH] Revert some consume middlewares --- src/Debug/QueueCollector.php | 4 +- src/Worker/Worker.php | 14 +- .../Integration/Support/ConsumeMiddleware.php | 27 +++ .../Middleware/Consume/ConsumeRequestTest.php | 24 ++ ...validMiddlewareDefinitionExceptionTest.php | 73 ++++++ .../Consume/MiddlewareDispatcherTest.php | 186 ++++++++++++++++ .../Consume/MiddlewareFactoryTest.php | 208 ++++++++++++++++++ 7 files changed, 522 insertions(+), 14 deletions(-) create mode 100644 tests/Integration/Support/ConsumeMiddleware.php create mode 100644 tests/Unit/Middleware/Consume/ConsumeRequestTest.php create mode 100644 tests/Unit/Middleware/Consume/InvalidMiddlewareDefinitionExceptionTest.php create mode 100644 tests/Unit/Middleware/Consume/MiddlewareDispatcherTest.php create mode 100644 tests/Unit/Middleware/Consume/MiddlewareFactoryTest.php diff --git a/src/Debug/QueueCollector.php b/src/Debug/QueueCollector.php index 1a8c9d76..d0bc7171 100644 --- a/src/Debug/QueueCollector.php +++ b/src/Debug/QueueCollector.php @@ -4,11 +4,11 @@ namespace Yiisoft\Queue\Debug; +use Yiisoft\Queue\Middleware\MiddlewareInterface; use Yiisoft\Yii\Debug\Collector\CollectorTrait; use Yiisoft\Yii\Debug\Collector\SummaryCollectorInterface; use Yiisoft\Queue\Enum\JobStatus; use Yiisoft\Queue\Message\MessageInterface; -use Yiisoft\Queue\Middleware\Push\MiddlewarePushInterface; use Yiisoft\Queue\QueueInterface; final class QueueCollector implements SummaryCollectorInterface @@ -53,7 +53,7 @@ public function collectStatus(string $id, JobStatus $status): void public function collectPush( string $channel, MessageInterface $message, - string|array|callable|MiddlewarePushInterface ...$middlewareDefinitions, + string|array|callable|MiddlewareInterface ...$middlewareDefinitions, ): void { if (!$this->isActive()) { return; diff --git a/src/Worker/Worker.php b/src/Worker/Worker.php index 46bf645d..13471fb7 100644 --- a/src/Worker/Worker.php +++ b/src/Worker/Worker.php @@ -57,12 +57,12 @@ public function process(MessageInterface $message, QueueInterface $queue): Messa $request = new Request($message, $queue->getAdapter()); $closure = fn (MessageInterface $message): mixed => $this->injector->invoke($handler, [$message]); try { - return $this->consumeMiddlewareDispatcher->dispatch($request, $this->createConsumeHandler($closure))->getMessage(); + return $this->consumeMiddlewareDispatcher->dispatch($request, new ConsumeFinalHandler($closure))->getMessage(); } catch (Throwable $exception) { $request = new FailureHandlingRequest($request->getMessage(), $exception, $queue); try { - $result = $this->failureMiddlewareDispatcher->dispatch($request, $this->createFailureHandler()); + $result = $this->failureMiddlewareDispatcher->dispatch($request, new FailureFinalHandler()); $this->logger->info($exception->getMessage()); return $result->getMessage(); @@ -140,14 +140,4 @@ private function prepare(callable|object|array|string|null $definition): callabl return $definition; } - - private function createConsumeHandler(Closure $handler): MessageHandlerInterface - { - return new ConsumeFinalHandler($handler); - } - - private function createFailureHandler(): MessageFailureHandlerInterface - { - return new FailureFinalHandler(); - } } diff --git a/tests/Integration/Support/ConsumeMiddleware.php b/tests/Integration/Support/ConsumeMiddleware.php new file mode 100644 index 00000000..347920f4 --- /dev/null +++ b/tests/Integration/Support/ConsumeMiddleware.php @@ -0,0 +1,27 @@ +getMessage(); + $stack = $message->getData(); + $stack[] = $this->stage; + $messageNew = new Message($message->getHandlerName(), $stack); + + return $handler->handle($request->withMessage($messageNew)); + } +} diff --git a/tests/Unit/Middleware/Consume/ConsumeRequestTest.php b/tests/Unit/Middleware/Consume/ConsumeRequestTest.php new file mode 100644 index 00000000..f2942570 --- /dev/null +++ b/tests/Unit/Middleware/Consume/ConsumeRequestTest.php @@ -0,0 +1,24 @@ +createMock(AdapterInterface::class); + $consumeRequest = new Request($message, $adapter); + + $this->assertNotSame($consumeRequest, $consumeRequest->withMessage($message)); + $this->assertNotSame($consumeRequest, $consumeRequest->withAdapter($adapter)); + } +} diff --git a/tests/Unit/Middleware/Consume/InvalidMiddlewareDefinitionExceptionTest.php b/tests/Unit/Middleware/Consume/InvalidMiddlewareDefinitionExceptionTest.php new file mode 100644 index 00000000..26efb343 --- /dev/null +++ b/tests/Unit/Middleware/Consume/InvalidMiddlewareDefinitionExceptionTest.php @@ -0,0 +1,73 @@ + TestCallableMiddleware::class, 'index'], + sprintf( + '["class" => "%s", "index"]', + TestCallableMiddleware::class, + ), + ], + ]; + } + + /** + * @dataProvider dataBase + */ + public function testBase(mixed $definition, string $expected): void + { + $exception = new InvalidMiddlewareDefinitionException($definition); + self::assertStringEndsWith('. Got ' . $expected . '.', $exception->getMessage()); + } + + public function dataUnknownDefinition(): array + { + return [ + [42], + [[new stdClass()]], + ]; + } + + /** + * @dataProvider dataUnknownDefinition + */ + public function testUnknownDefinition(mixed $definition): void + { + $exception = new InvalidMiddlewareDefinitionException($definition); + self::assertSame( + 'Parameter should be either middleware class name or a callable.', + $exception->getMessage() + ); + } +} diff --git a/tests/Unit/Middleware/Consume/MiddlewareDispatcherTest.php b/tests/Unit/Middleware/Consume/MiddlewareDispatcherTest.php new file mode 100644 index 00000000..5f016bc1 --- /dev/null +++ b/tests/Unit/Middleware/Consume/MiddlewareDispatcherTest.php @@ -0,0 +1,186 @@ +getRequest(); + $queue = $this->createMock(QueueInterface::class); + $adapter = $this->createMock(AdapterInterface::class); + + $dispatcher = $this->createDispatcher()->withMiddlewares( + [ + static function (Request $request) use ($adapter): Request { + return $request->withMessage(new Message('test', 'New closure test data'))->withAdapter($adapter); + }, + ] + ); + + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + $this->assertSame('New closure test data', $request->getMessage()->getData()); + } + + public function testArrayMiddlewareCallableDefinition(): void + { + $request = $this->getRequest(); + $container = $this->createContainer( + [ + TestCallableMiddleware::class => new TestCallableMiddleware(), + ] + ); + $dispatcher = $this->createDispatcher($container)->withMiddlewares([[TestCallableMiddleware::class, 'index']]); + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + $this->assertSame('New test data', $request->getMessage()->getData()); + } + + public function testFactoryArrayDefinition(): void + { + $request = $this->getRequest(); + $container = $this->createContainer(); + $definition = [ + 'class' => TestMiddleware::class, + '__construct()' => ['message' => 'New test data from the definition'], + ]; + $dispatcher = $this->createDispatcher($container)->withMiddlewares([$definition]); + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + $this->assertSame('New test data from the definition', $request->getMessage()->getData()); + } + + public function testMiddlewareFullStackCalled(): void + { + $request = $this->getRequest(); + + $middleware1 = static function (Request $request, MessageHandlerInterface $handler): Request { + $request = $request->withMessage(new Message($request->getMessage()->getHandlerName(), 'new test data')); + + return $handler->handle($request); + }; + $middleware2 = static function (Request $request, MessageHandlerInterface $handler): Request { + $request = $request->withMessage(new Message('new handler', $request->getMessage()->getData())); + + return $handler->handle($request); + }; + + $dispatcher = $this->createDispatcher()->withMiddlewares([$middleware1, $middleware2]); + + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + $this->assertSame('new test data', $request->getMessage()->getData()); + $this->assertSame('new handler', $request->getMessage()->getHandlerName()); + } + + public function testMiddlewareStackInterrupted(): void + { + $request = $this->getRequest(); + + $middleware1 = static function (Request $request, MessageHandlerInterface $handler): Request { + return $request->withMessage(new Message($request->getMessage()->getHandlerName(), 'first')); + }; + $middleware2 = static function (Request $request, MessageHandlerInterface $handler): Request { + return $request->withMessage(new Message($request->getMessage()->getHandlerName(), 'second')); + }; + + $dispatcher = $this->createDispatcher()->withMiddlewares([$middleware1, $middleware2]); + + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + $this->assertSame('first', $request->getMessage()->getData()); + } + + public function dataHasMiddlewares(): array + { + return [ + [[], false], + [[[TestCallableMiddleware::class, 'index']], true], + ]; + } + + /** + * @dataProvider dataHasMiddlewares + */ + public function testHasMiddlewares(array $definitions, bool $expected): void + { + self::assertSame( + $expected, + $this->createDispatcher()->withMiddlewares($definitions)->hasMiddlewares() + ); + } + + public function testImmutability(): void + { + $dispatcher = $this->createDispatcher(); + self::assertNotSame($dispatcher, $dispatcher->withMiddlewares([])); + } + + public function testResetStackOnWithMiddlewares(): void + { + $request = $this->getRequest(); + $container = $this->createContainer( + [ + TestCallableMiddleware::class => new TestCallableMiddleware(), + TestMiddleware::class => new TestMiddleware(), + ] + ); + + $dispatcher = $this + ->createDispatcher($container) + ->withMiddlewares([[TestCallableMiddleware::class, 'index']]); + $dispatcher->dispatch($request, $this->getRequestHandler()); + + $dispatcher = $dispatcher->withMiddlewares([TestMiddleware::class]); + $request = $dispatcher->dispatch($request, $this->getRequestHandler()); + + self::assertSame('New middleware test data', $request->getMessage()->getData()); + } + + private function getRequestHandler(): MessageHandlerInterface + { + return new class () implements MessageHandlerInterface { + public function handle(Request $request): Request + { + return $request; + } + }; + } + + private function createDispatcher( + ContainerInterface $container = null, + ): MiddlewareDispatcher { + $container ??= $this->createContainer([AdapterInterface::class => new FakeAdapter()]); + $callableFactory = new CallableFactory($container); + + return new MiddlewareDispatcher( + new MiddlewareFactory($container, $callableFactory), + ); + } + + private function createContainer(array $instances = []): ContainerInterface + { + return new SimpleContainer($instances); + } + + private function getRequest(): Request + { + return new Request( + new Message('handler', 'data'), + $this->createMock(AdapterInterface::class) + ); + } +} diff --git a/tests/Unit/Middleware/Consume/MiddlewareFactoryTest.php b/tests/Unit/Middleware/Consume/MiddlewareFactoryTest.php new file mode 100644 index 00000000..cbdd763c --- /dev/null +++ b/tests/Unit/Middleware/Consume/MiddlewareFactoryTest.php @@ -0,0 +1,208 @@ +getContainer([ConsumeMiddleware::class => new ConsumeMiddleware('stage1')]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware(ConsumeMiddleware::class); + self::assertInstanceOf(ConsumeMiddleware::class, $middleware); + } + + public function testCreateFromAliasString(): void + { + $container = $this->getContainer(['test' => new ConsumeMiddleware('stage1')]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware('test'); + self::assertInstanceOf(ConsumeMiddleware::class, $middleware); + } + + public function testCreateFromArray(): void + { + $container = $this->getContainer([TestCallableMiddleware::class => new TestCallableMiddleware()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + [TestCallableMiddleware::class, 'index'] + ); + self::assertSame( + 'New test data', + $middleware->process( + $this->getRequest(), + $this->createMock(MessageHandlerInterface::class) + )->getMessage()->getData(), + ); + } + + public function testCreateFromClosureResponse(): void + { + $container = $this->getContainer([TestCallableMiddleware::class => new TestCallableMiddleware()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + fn (): Request => new Request( + new Message('test', 'test data'), + $this->createMock(AdapterInterface::class), + ) + ); + self::assertSame( + 'test data', + $middleware->process( + $this->getRequest(), + $this->createMock(MessageHandlerInterface::class) + )->getMessage()->getData() + ); + } + + public function testCreateFromClosureMiddleware(): void + { + $container = $this->getContainer([TestCallableMiddleware::class => new TestCallableMiddleware()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + static fn (): MiddlewareInterface => new ConsumeMiddleware('stage1') + ); + + $handler = $this->createMock(MessageHandlerInterface::class); + $handler->expects($this->once())->method('handle')->willReturnCallback( + static fn (Request $request): Request => $request->withMessage( + new Message('test', 'New middleware test data') + ) + ); + + self::assertSame( + 'New middleware test data', + $middleware->process( + $this->getRequest(), + $handler + )->getMessage()->getData() + ); + } + + public function testCreateWithUseParamsMiddleware(): void + { + $container = $this->getContainer([ConsumeMiddleware::class => new ConsumeMiddleware('stage1')]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware(ConsumeMiddleware::class); + + self::assertSame( + ['data', 'stage1'], + $middleware->process( + $this->getRequest(), + $this->getRequestHandler() + )->getMessage()->getData() + ); + } + + public function testCreateWithTestCallableMiddleware(): void + { + $container = $this->getContainer([TestCallableMiddleware::class => new TestCallableMiddleware()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + [TestCallableMiddleware::class, 'index'] + ); + $request = $this->getRequest(); + + self::assertSame( + 'New test data', + $middleware->process( + $request, + $this->getRequestHandler() + )->getMessage()->getData() + ); + } + + public function testInvalidMiddlewareWithWrongCallable(): void + { + $container = $this->getContainer([TestCallableMiddleware::class => new TestCallableMiddleware()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + static fn () => 42 + ); + + $this->expectException(InvalidMiddlewareDefinitionException::class); + $middleware->process( + $this->getRequest(), + $this->createMock(MessageHandlerInterface::class) + ); + } + + public function invalidMiddlewareDefinitionProvider(): array + { + return [ + 'wrong string' => ['test'], + 'wrong class' => [TestCallableMiddleware::class], + 'wrong array size' => [['test']], + 'array not a class' => [['class', 'test']], + 'wrong array type' => [['class' => TestCallableMiddleware::class, 'index']], + 'wrong array with int items' => [[7, 42]], + 'array with wrong method name' => [[TestCallableMiddleware::class, 'notExists']], + 'array wrong class' => [['class' => InvalidController::class]], + ]; + } + + /** + * @dataProvider invalidMiddlewareDefinitionProvider + */ + public function testInvalidMiddleware(mixed $definition): void + { + $this->expectException(InvalidMiddlewareDefinitionException::class); + $this->getMiddlewareFactory()->createMiddleware($definition); + } + + public function testInvalidMiddlewareWithWrongController(): void + { + $container = $this->getContainer([InvalidController::class => new InvalidController()]); + $middleware = $this->getMiddlewareFactory($container)->createMiddleware( + [InvalidController::class, 'index'] + ); + + $this->expectException(InvalidMiddlewareDefinitionException::class); + $middleware->process( + $this->getRequest(), + $this->createMock(MessageHandlerInterface::class) + ); + } + + private function getMiddlewareFactory(ContainerInterface $container = null): MiddlewareFactoryInterface + { + $container ??= $this->getContainer([AdapterInterface::class => new FakeAdapter()]); + + return new MiddlewareFactory($container, new CallableFactory($container)); + } + + private function getContainer(array $instances = []): ContainerInterface + { + return new SimpleContainer($instances); + } + + private function getRequestHandler(): MessageHandlerInterface + { + return new class () implements MessageHandlerInterface { + public function handle(Request $request): Request + { + return $request; + } + }; + } + + private function getRequest(): Request + { + return new Request( + new Message('handler', ['data']), + $this->createMock(AdapterInterface::class) + ); + } +}