diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1c16ef..4e1e8ed 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========
+Next release
+------------
+
+* Add run checker (check before run consumer).
+
v2.0.3
------
diff --git a/src/Adapter/Amqp/Queue/AmqpQueue.php b/src/Adapter/Amqp/Queue/AmqpQueue.php
index b08b617..c08bc4a 100644
--- a/src/Adapter/Amqp/Queue/AmqpQueue.php
+++ b/src/Adapter/Amqp/Queue/AmqpQueue.php
@@ -89,7 +89,6 @@ public function get(): ?ReceivedMessage
$envelope = $this->queue->get();
if ($envelope) {
- // @phpstan-ignore-next-line
return new AmqpReceivedMessage($this->queue, $envelope);
}
diff --git a/src/Command/OutputEventHandler.php b/src/Command/OutputEventHandler.php
index 2940343..48b954e 100644
--- a/src/Command/OutputEventHandler.php
+++ b/src/Command/OutputEventHandler.php
@@ -42,7 +42,7 @@ public function __invoke(Event $event): void
'Receive consumer timeout exceed error.',
OutputInterface::VERBOSITY_VERBOSE
);
- } else if (Event::StopAfterNExecutes === $event) {
+ } elseif (Event::StopAfterNExecutes === $event) {
$this->output->writeln(
'Stop consumer after N executes.',
OutputInterface::VERBOSITY_VERBOSE
diff --git a/src/Command/RunConsumerCommand.php b/src/Command/RunConsumerCommand.php
index c496ea2..b98c7f8 100644
--- a/src/Command/RunConsumerCommand.php
+++ b/src/Command/RunConsumerCommand.php
@@ -13,12 +13,15 @@
namespace FiveLab\Component\Amqp\Command;
+use FiveLab\Component\Amqp\Consumer\Checker\RunConsumerCheckerRegistry;
+use FiveLab\Component\Amqp\Consumer\Checker\RunConsumerCheckerRegistryInterface;
use FiveLab\Component\Amqp\Consumer\ConsumerInterface;
use FiveLab\Component\Amqp\Consumer\EventableConsumerInterface;
use FiveLab\Component\Amqp\Consumer\Middleware\StopAfterNExecutesMiddleware;
use FiveLab\Component\Amqp\Consumer\MiddlewareAwareInterface;
use FiveLab\Component\Amqp\Consumer\Registry\ConsumerRegistryInterface;
use FiveLab\Component\Amqp\Exception\ConsumerTimeoutExceedException;
+use FiveLab\Component\Amqp\Exception\RunConsumerCheckerNotFoundException;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
@@ -42,14 +45,24 @@ class RunConsumerCommand extends Command
*/
protected static $defaultDescription = 'Run consumer.';
+ /**
+ * @var RunConsumerCheckerRegistryInterface
+ */
+ private readonly RunConsumerCheckerRegistryInterface $runCheckerRegistry;
+
/**
* Constructor.
*
- * @param ConsumerRegistryInterface $consumerRegistry
+ * @param ConsumerRegistryInterface $consumerRegistry
+ * @param RunConsumerCheckerRegistryInterface|null $runCheckerRegistry
*/
- public function __construct(private readonly ConsumerRegistryInterface $consumerRegistry)
- {
+ public function __construct(
+ private readonly ConsumerRegistryInterface $consumerRegistry,
+ RunConsumerCheckerRegistryInterface $runCheckerRegistry = null
+ ) {
parent::__construct();
+
+ $this->runCheckerRegistry = $runCheckerRegistry ?: new RunConsumerCheckerRegistry();
}
/**
@@ -62,7 +75,8 @@ protected function configure(): void
->addArgument('key', InputArgument::REQUIRED, 'The key of consumer.')
->addOption('read-timeout', null, InputOption::VALUE_REQUIRED, 'Set the read timeout for RabbitMQ.')
->addOption('loop', null, InputOption::VALUE_NONE, 'Loop consume (used only with read-timeout).')
- ->addOption('messages', null, InputOption::VALUE_REQUIRED, 'After process number of messages process be normal exits.');
+ ->addOption('messages', null, InputOption::VALUE_REQUIRED, 'After process number of messages process be normal exits.')
+ ->addOption('dry-run', null, InputOption::VALUE_NONE, 'Check if consumer can be run.');
}
/**
@@ -70,7 +84,23 @@ protected function configure(): void
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
- $consumer = $this->consumerRegistry->get((string) $input->getArgument('key'));
+ $consumerKey = (string) $input->getArgument('key');
+
+ if ($input->getOption('dry-run')) {
+ $checker = $this->runCheckerRegistry->get($consumerKey);
+ $checker->checkBeforeRun();
+
+ return 0;
+ }
+
+ try {
+ $checker = $this->runCheckerRegistry->get($consumerKey);
+ $checker->checkBeforeRun();
+ } catch (RunConsumerCheckerNotFoundException) {
+ // Normal flow. Checker not found.
+ }
+
+ $consumer = $this->consumerRegistry->get($consumerKey);
if ($consumer instanceof EventableConsumerInterface) {
$closure = (new OutputEventHandler($output))(...);
diff --git a/src/Consumer/Checker/ContainerRunConsumerCheckerRegistry.php b/src/Consumer/Checker/ContainerRunConsumerCheckerRegistry.php
new file mode 100644
index 0000000..3656a42
--- /dev/null
+++ b/src/Consumer/Checker/ContainerRunConsumerCheckerRegistry.php
@@ -0,0 +1,47 @@
+container->has($key)) {
+ return $this->container->get($key);
+ }
+
+ throw new RunConsumerCheckerNotFoundException(\sprintf(
+ 'The checker for consumer "%s" was not found.',
+ $key
+ ));
+ }
+}
diff --git a/src/Consumer/Checker/RunConsumerCheckerInterface.php b/src/Consumer/Checker/RunConsumerCheckerInterface.php
new file mode 100644
index 0000000..300865f
--- /dev/null
+++ b/src/Consumer/Checker/RunConsumerCheckerInterface.php
@@ -0,0 +1,29 @@
+
+ */
+ private array $checkers = [];
+
+ /**
+ * Add checker for consumer to registry
+ *
+ * @param string $key
+ * @param RunConsumerCheckerInterface $checker
+ */
+ public function add(string $key, RunConsumerCheckerInterface $checker): void
+ {
+ $this->checkers[$key] = $checker;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function get(string $key): RunConsumerCheckerInterface
+ {
+ return $this->checkers[$key] ?? throw new RunConsumerCheckerNotFoundException(\sprintf(
+ 'The checker for consumer "%s" was not found.',
+ $key
+ ));
+ }
+}
diff --git a/src/Consumer/Checker/RunConsumerCheckerRegistryInterface.php b/src/Consumer/Checker/RunConsumerCheckerRegistryInterface.php
new file mode 100644
index 0000000..f36f355
--- /dev/null
+++ b/src/Consumer/Checker/RunConsumerCheckerRegistryInterface.php
@@ -0,0 +1,33 @@
+registry = $this->createMock(ConsumerRegistryInterface::class);
+ $this->checkerRegistry = $this->createMock(RunConsumerCheckerRegistryInterface::class);
$this->connection = $this->createMock(ConnectionInterface::class);
$this->channel = $this->createMock(ChannelInterface::class);
$this->queue = $this->createMock(QueueInterface::class);
@@ -295,4 +305,105 @@ public function shouldSuccessExecuteInLoopWithReadTimeout(bool $verbose): void
throw $error;
}
}
+
+ #[Test]
+ public function shouldSuccessExecuteIfCheckerNotFoundInRegistry(): void
+ {
+ $command = new RunConsumerCommand($this->registry, $this->checkerRegistry);
+
+ $input = new ArrayInput([
+ 'key' => 'some',
+ ]);
+
+ $status = $command->run($input, new BufferedOutput());
+
+ self::assertEquals(0, $status);
+ }
+
+ #[Test]
+ public function shouldFailExecuteIfCheckerThrowError(): void
+ {
+ $this->configureChecker('some', new CannotRunConsumerException('foo bar'));
+
+ $this->registry->expects(self::never())
+ ->method('get');
+
+ $command = new RunConsumerCommand($this->registry, $this->checkerRegistry);
+
+ $input = new ArrayInput([
+ 'key' => 'some',
+ ]);
+
+ $this->expectException(CannotRunConsumerException::class);
+ $this->expectExceptionMessage('foo bar');
+
+ $command->run($input, new BufferedOutput());
+ }
+
+ #[Test]
+ public function shouldSuccessExecuteWithDryRun(): void
+ {
+ $this->configureChecker('some', null);
+
+ $command = new RunConsumerCommand($this->registry, $this->checkerRegistry);
+
+ $input = new ArrayInput([
+ 'key' => 'some',
+ '--dry-run' => true,
+ ]);
+
+ $status = $command->run($input, new BufferedOutput());
+
+ self::assertEquals(0, $status);
+ }
+
+ #[Test]
+ public function shouldFailExecuteWithDryRunIfCheckerNotFound(): void
+ {
+ $command = new RunConsumerCommand($this->registry);
+
+ $input = new ArrayInput([
+ 'key' => 'some',
+ '--dry-run' => true,
+ ]);
+
+ $this->expectException(RunConsumerCheckerNotFoundException::class);
+ $this->expectExceptionMessage('The checker for consumer "some" was not found.');
+
+ $command->run($input, new BufferedOutput());
+ }
+
+ #[Test]
+ public function shouldFailExecuteWithDryRunIfCheckerThrowError(): void
+ {
+ $this->configureChecker('some', new CannotRunConsumerException('bla bla'));
+
+ $command = new RunConsumerCommand($this->registry, $this->checkerRegistry);
+
+ $input = new ArrayInput([
+ 'key' => 'some',
+ '--dry-run' => true,
+ ]);
+
+ $this->expectException(CannotRunConsumerException::class);
+ $this->expectExceptionMessage('bla bla');
+
+ $command->run($input, new BufferedOutput());
+ }
+
+ private function configureChecker(string $key, ?\Throwable $error): void
+ {
+ $checker = $this->createMock(RunConsumerCheckerInterface::class);
+
+ $matcher = $checker->expects(self::once())->method('checkBeforeRun');
+
+ if ($error) {
+ $matcher->willThrowException($error);
+ }
+
+ $this->checkerRegistry->expects(self::once())
+ ->method('get')
+ ->with($key)
+ ->willReturn($checker);
+ }
}
diff --git a/tests/Unit/Consumer/Checker/ContainerRunConsumerCheckerRegistryTest.php b/tests/Unit/Consumer/Checker/ContainerRunConsumerCheckerRegistryTest.php
new file mode 100644
index 0000000..a56733f
--- /dev/null
+++ b/tests/Unit/Consumer/Checker/ContainerRunConsumerCheckerRegistryTest.php
@@ -0,0 +1,81 @@
+
+ */
+ private array $checkers;
+
+ /**
+ * @var ContainerRunConsumerCheckerRegistry
+ */
+ private ContainerRunConsumerCheckerRegistry $registry;
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp(): void
+ {
+ $this->checkers = [
+ $this->createMock(RunConsumerCheckerInterface::class),
+ $this->createMock(RunConsumerCheckerInterface::class),
+ ];
+
+ $container = $this->createMock(ContainerInterface::class);
+
+ $container->expects(self::any())
+ ->method('has')
+ ->willReturnCallback(static function (string $key) {
+ return \in_array($key, ['consumer_1', 'consumer_2'], true);
+ });
+
+ $container->expects(self::any())
+ ->method('get')
+ ->willReturnMap([
+ ['consumer_1', $this->checkers[0]],
+ ['consumer_2', $this->checkers[1]],
+ ]);
+
+ $this->registry = new ContainerRunConsumerCheckerRegistry($container);
+ }
+
+ #[Test]
+ public function shouldSuccessGet(): void
+ {
+ $consumer2 = $this->registry->get('consumer_2');
+ self::assertSame($this->checkers[1], $consumer2);
+
+ $consumer1 = $this->registry->get('consumer_1');
+ self::assertSame($this->checkers[0], $consumer1);
+ }
+
+ #[Test]
+ public function shouldFailGetIfNotFound(): void
+ {
+ $this->expectException(RunConsumerCheckerNotFoundException::class);
+ $this->expectExceptionMessage('The checker for consumer "foo" was not found.');
+
+ $this->registry->get('foo');
+ }
+}
diff --git a/tests/Unit/Consumer/Checker/RunConsumerCheckerRegistryTest.php b/tests/Unit/Consumer/Checker/RunConsumerCheckerRegistryTest.php
new file mode 100644
index 0000000..8dbea16
--- /dev/null
+++ b/tests/Unit/Consumer/Checker/RunConsumerCheckerRegistryTest.php
@@ -0,0 +1,70 @@
+createUniqueChecker();
+ $checker2 = $this->createUniqueChecker();
+ $checker3 = $this->createUniqueChecker();
+
+ $registry = new RunConsumerCheckerRegistry();
+
+ $registry->add('test_1', $checker1);
+ $registry->add('test_2', $checker2);
+ $registry->add('test_3', $checker3);
+
+ $result = $registry->get('test_2');
+
+ self::assertSame($checker2, $result);
+ }
+
+ #[Test]
+ public function shouldFailIfCheckerNotFound(): void
+ {
+ $registry = new RunConsumerCheckerRegistry();
+
+ $registry->add('foo', $this->createUniqueChecker());
+
+ $this->expectException(RunConsumerCheckerNotFoundException::class);
+ $this->expectExceptionMessage('The checker for consumer "bar" was not found.');
+
+ $registry->get('bar');
+ }
+
+ #[Test]
+ public function shouldFailIfRegistryIsEmpty(): void
+ {
+ $registry = new RunConsumerCheckerRegistry();
+
+ $this->expectException(RunConsumerCheckerNotFoundException::class);
+ $this->expectExceptionMessage('The checker for consumer "foo" was not found.');
+
+ $registry->get('foo');
+ }
+
+ private function createUniqueChecker(): RunConsumerCheckerInterface
+ {
+ return $this->createMock(RunConsumerCheckerInterface::class);
+ }
+}
diff --git a/tests/Unit/Consumer/Registry/ConsumerRegistryTest.php b/tests/Unit/Consumer/Registry/ConsumerRegistryTest.php
index 7816f06..a4d9908 100644
--- a/tests/Unit/Consumer/Registry/ConsumerRegistryTest.php
+++ b/tests/Unit/Consumer/Registry/ConsumerRegistryTest.php
@@ -36,7 +36,7 @@ public function shouldSuccessGet(): void
$result = $registry->get('test_2');
- self::assertEquals($consumer2, $result);
+ self::assertSame($consumer2, $result);
}
#[Test]
diff --git a/tests/Unit/Consumer/Registry/ContainerConsumerRegistryTest.php b/tests/Unit/Consumer/Registry/ContainerConsumerRegistryTest.php
index 4c7b4b4..5113a78 100644
--- a/tests/Unit/Consumer/Registry/ContainerConsumerRegistryTest.php
+++ b/tests/Unit/Consumer/Registry/ContainerConsumerRegistryTest.php
@@ -64,10 +64,10 @@ protected function setUp(): void
public function shouldSuccessGet(): void
{
$consumer2 = $this->registry->get('consumer_2');
- self::assertEquals($this->consumers[1], $consumer2);
+ self::assertSame($this->consumers[1], $consumer2);
$consumer1 = $this->registry->get('consumer_1');
- self::assertEquals($this->consumers[0], $consumer1);
+ self::assertSame($this->consumers[0], $consumer1);
}
#[Test]