From 564898704f6cadf336e9a33676f6bf650357aee6 Mon Sep 17 00:00:00 2001 From: Nicolas PHILIPPE Date: Thu, 12 Mar 2020 17:20:44 +0100 Subject: [PATCH] Use miliseconds for statistics store waiting_time in seconds+microseconds store handling_time in seconds+microseconds store failing_time in seconds+microseconds Fixed statistics computing Handle micro/milli seconds in twig time filter Tested retry behavior Fixed waiting_time for messages with delay --- psalm.xml | 14 ---- src/Statistics/MetricsPerMessageType.php | 12 +-- src/Storage/Doctrine/Connection.php | 62 +++++++------- src/Storage/Doctrine/ConnectionFactory.php | 11 +-- src/Storage/Doctrine/Driver/MySQLDriver.php | 16 ---- .../Doctrine/Driver/PostgreSQLDriver.php | 16 ---- .../Doctrine/Driver/SQLDriverInterface.php | 13 --- .../SaveRetriedMessageListener.php | 3 +- .../UpdateStoredMessageListener.php | 10 ++- src/Storage/Doctrine/StoredMessage.php | 77 ++++++++++------- src/Twig/TimeDisplayExtension.php | 18 +++- .../AddStampOnMessageSentListenerTest.php | 2 +- .../SendEventOnRetriedMessageTest.php | 2 +- .../FailedMessageRejecterTest.php | 2 +- .../FailedMessageRepositoryTest.php | 2 +- tests/Fixtures/FailureMessage.php | 21 +++++ tests/Fixtures/Message.php | 10 +++ tests/Fixtures/RetryableMessage.php | 21 +++++ tests/Fixtures/TestableMessage.php | 13 +++ .../{ => Fixtures}/TestableMessageHandler.php | 7 +- .../AbstractFunctionalTests.php | 45 +++++----- .../DashboardControllerTest.php | 51 +++++++---- .../RejectFailedMessageControllerTest.php | 11 ++- .../RetryFailedMessageControllerTest.php | 11 ++- .../AbstractDoctrineIntegrationTests.php | 18 +--- .../Storage/DoctrineConnectionTest.php | 76 +++++++++-------- .../Doctrine/Driver/MySQLDriverTest.php | 20 ----- .../Doctrine/Driver/PostgreSQLDriverTest.php | 20 ----- .../SaveRetriedMessageListenerTest.php | 2 +- ...StoredMessageOnMessageSentListenerTest.php | 2 +- .../UpdateStoredMessageListenerTest.php | 84 +++++++++++++------ .../Doctrine/StoredMessageProviderTest.php | 2 +- tests/Storage/Doctrine/StoredMessageTest.php | 71 ++++++++++++++-- tests/TestKernel.php | 10 +++ tests/TestableMessage.php | 15 ---- tests/Twig/TimeDisplayExtensionTest.php | 11 ++- 36 files changed, 438 insertions(+), 343 deletions(-) delete mode 100644 src/Storage/Doctrine/Driver/MySQLDriver.php delete mode 100644 src/Storage/Doctrine/Driver/PostgreSQLDriver.php delete mode 100644 src/Storage/Doctrine/Driver/SQLDriverInterface.php create mode 100644 tests/Fixtures/FailureMessage.php create mode 100644 tests/Fixtures/Message.php create mode 100644 tests/Fixtures/RetryableMessage.php create mode 100644 tests/Fixtures/TestableMessage.php rename tests/{ => Fixtures}/TestableMessageHandler.php (55%) delete mode 100644 tests/Storage/Doctrine/Driver/MySQLDriverTest.php delete mode 100644 tests/Storage/Doctrine/Driver/PostgreSQLDriverTest.php delete mode 100644 tests/TestableMessage.php diff --git a/psalm.xml b/psalm.xml index f16268f..1ac23a6 100644 --- a/psalm.xml +++ b/psalm.xml @@ -12,18 +12,4 @@ - - - - - - - - - - - - - - diff --git a/src/Statistics/MetricsPerMessageType.php b/src/Statistics/MetricsPerMessageType.php index 7d92dfb..bb9d2a8 100644 --- a/src/Statistics/MetricsPerMessageType.php +++ b/src/Statistics/MetricsPerMessageType.php @@ -14,8 +14,8 @@ public function __construct( private \DateTimeImmutable $toDate, private string $class, private int $messagesCountOnPeriod, - private float $averageWaitingTime, - private float $averageHandlingTime + private ?float $averageWaitingTime, + private ?float $averageHandlingTime ) { } @@ -39,14 +39,14 @@ public function getMessagesHandledPerHour(): float return round($this->getMessagesCount() / $this->getNbHoursInPeriod(), 2); } - public function getAverageWaitingTime(): float + public function getAverageWaitingTime(): ?float { - return round($this->averageWaitingTime, 2); + return null !== $this->averageWaitingTime ? round($this->averageWaitingTime, 6) : null; } - public function getAverageHandlingTime(): float + public function getAverageHandlingTime(): ?float { - return round($this->averageHandlingTime, 2); + return null !== $this->averageHandlingTime ? round($this->averageHandlingTime, 6) : null; } private function getNbHoursInPeriod(): float diff --git a/src/Storage/Doctrine/Connection.php b/src/Storage/Doctrine/Connection.php index 4d28d69..ee01d0f 100644 --- a/src/Storage/Doctrine/Connection.php +++ b/src/Storage/Doctrine/Connection.php @@ -11,7 +11,6 @@ use Doctrine\DBAL\Types\Types; use SymfonyCasts\MessengerMonitorBundle\Statistics\MetricsPerMessageType; use SymfonyCasts\MessengerMonitorBundle\Statistics\Statistics; -use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Driver\SQLDriverInterface; /** * @internal @@ -20,7 +19,7 @@ */ class Connection { - public function __construct(private DBALConnection $driverConnection, private SQLDriverInterface $SQLDriver, private string $tableName) + public function __construct(private DBALConnection $driverConnection, private string $tableName) { } @@ -40,10 +39,7 @@ public function saveMessage(StoredMessage $storedMessage): void [ 'message_uid' => $storedMessage->getMessageUid(), 'class' => $storedMessage->getMessageClass(), - 'dispatched_at' => $storedMessage->getDispatchedAt(), - ], - [ - 'dispatched_at' => Types::DATETIME_IMMUTABLE, + 'dispatched_at' => (float) $storedMessage->getDispatchedAt()->format('U.u'), ] ); @@ -55,23 +51,18 @@ public function updateMessage(StoredMessage $storedMessage): void $this->executeQuery( $this->driverConnection->createQueryBuilder() ->update($this->tableName) - ->set('received_at', ':received_at') + ->set('waiting_time', ':waiting_time') ->set('receiver_name', ':receiver_name') - ->set('handled_at', ':handled_at') - ->set('failed_at', ':failed_at') + ->set('handling_time', ':handling_time') + ->set('failing_time', ':failing_time') ->where('id = :id') ->getSQL(), [ - 'received_at' => $storedMessage->getReceivedAt(), - 'handled_at' => $storedMessage->getHandledAt(), - 'failed_at' => $storedMessage->getFailedAt(), + 'waiting_time' => $storedMessage->getWaitingTime(), 'receiver_name' => $storedMessage->getReceiverName(), + 'handling_time' => $storedMessage->getHandlingTime(), + 'failing_time' => $storedMessage->getFailingTime(), 'id' => $storedMessage->getId(), - ], - [ - 'received_at' => Types::DATETIME_IMMUTABLE, - 'handled_at' => Types::DATETIME_IMMUTABLE, - 'failed_at' => Types::DATETIME_IMMUTABLE, ] ); } @@ -93,15 +84,16 @@ public function findMessage(string $messageUid): ?StoredMessage return null; } + /** @psalm-suppress PossiblyFalseArgument */ return new StoredMessage( $row['message_uid'], $row['class'], - new \DateTimeImmutable($row['dispatched_at']), + \DateTimeImmutable::createFromFormat('U.u', sprintf('%.6f', $row['dispatched_at'])), (int) $row['id'], - null !== $row['received_at'] ? new \DateTimeImmutable($row['received_at']) : null, - null !== $row['handled_at'] ? new \DateTimeImmutable($row['handled_at']) : null, - null !== $row['failed_at'] ? new \DateTimeImmutable($row['failed_at']) : null, - $row['receiver_name'] ?? null + null !== $row['waiting_time'] ? (float) $row['waiting_time'] : null, + $row['receiver_name'] ?? null, + null !== $row['handling_time'] ? (float) $row['handling_time'] : null, + null !== $row['failing_time'] ? (float) $row['failing_time'] : null ); } @@ -110,15 +102,17 @@ public function getStatistics(\DateTimeImmutable $fromDate, \DateTimeImmutable $ $statement = $this->executeQuery( $this->driverConnection->createQueryBuilder() ->select('count(id) as count_messages_on_period, class') - ->addSelect(sprintf('AVG(%s) AS average_waiting_time', $this->SQLDriver->getDateDiffInSecondsExpression('received_at', 'dispatched_at'))) - ->addSelect(sprintf('AVG(%s) AS average_handling_time', $this->SQLDriver->getDateDiffInSecondsExpression('handled_at', 'received_at'))) + ->addSelect('AVG(waiting_time) AS average_waiting_time') + ->addSelect('AVG(handling_time) AS average_handling_time') ->from($this->tableName) - ->where('handled_at >= :from_date') - ->andWhere('handled_at <= :to_date') + ->where('dispatched_at >= :from_date') + ->andWhere('dispatched_at <= :to_date') ->groupBy('class') ->getSQL(), - ['from_date' => $fromDate, 'to_date' => $toDate], - ['from_date' => Types::DATETIME_IMMUTABLE, 'to_date' => Types::DATETIME_IMMUTABLE] + [ + 'from_date' => (float) $fromDate->format('U'), + 'to_date' => (float) $toDate->format('U'), + ] ); $statistics = new Statistics($fromDate, $toDate); @@ -129,8 +123,8 @@ public function getStatistics(\DateTimeImmutable $fromDate, \DateTimeImmutable $ $toDate, $row['class'], (int) $row['count_messages_on_period'], - (float) $row['average_waiting_time'], - (float) $row['average_handling_time'] + $row['average_waiting_time'] ? (float) $row['average_waiting_time'] : null, + $row['average_handling_time'] ? (float) $row['average_handling_time'] : null ) ); } @@ -182,10 +176,10 @@ private function addTableToSchema(Schema $schema): void $table->addColumn('id', Types::INTEGER)->setNotnull(true)->setAutoincrement(true); $table->addColumn('message_uid', Types::GUID)->setNotnull(true); $table->addColumn('class', Types::STRING)->setLength(255)->setNotnull(true); - $table->addColumn('dispatched_at', Types::DATETIME_IMMUTABLE)->setNotnull(true); - $table->addColumn('received_at', Types::DATETIME_IMMUTABLE)->setNotnull(false); - $table->addColumn('handled_at', Types::DATETIME_IMMUTABLE)->setNotnull(false); - $table->addColumn('failed_at', Types::DATETIME_IMMUTABLE)->setNotnull(false); + $table->addColumn('dispatched_at', Types::FLOAT)->setNotnull(true); + $table->addColumn('waiting_time', Types::FLOAT)->setNotnull(false); + $table->addColumn('handling_time', Types::FLOAT)->setNotnull(false); + $table->addColumn('failing_time', Types::FLOAT)->setNotnull(false); $table->addColumn('receiver_name', Types::STRING)->setLength(255)->setNotnull(false); $table->addIndex(['dispatched_at']); $table->addIndex(['class']); diff --git a/src/Storage/Doctrine/ConnectionFactory.php b/src/Storage/Doctrine/ConnectionFactory.php index 90729cb..abde697 100644 --- a/src/Storage/Doctrine/ConnectionFactory.php +++ b/src/Storage/Doctrine/ConnectionFactory.php @@ -7,8 +7,6 @@ use Doctrine\DBAL\Connection as DBALConnection; use Doctrine\Persistence\ConnectionRegistry; use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; -use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Driver\MySQLDriver; -use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Driver\PostgreSQLDriver; /** * @internal @@ -24,15 +22,8 @@ public function __invoke(): Connection try { /** @var DBALConnection $driverConnection */ $driverConnection = $this->registry->getConnection($this->connectionName); - $databasePlatform = $driverConnection->getDatabasePlatform()->getName(); - $driver = match ($databasePlatform) { - 'mysql' => new MySQLDriver(), - 'postgresql' => new PostgreSQLDriver(), - default => throw new InvalidConfigurationException(sprintf('Doctrine platform "%s" is not supported', $databasePlatform)) - }; - - return new Connection($driverConnection, $driver, $this->tableName); + return new Connection($driverConnection, $this->tableName); } catch (\InvalidArgumentException) { throw new InvalidConfigurationException(sprintf('Doctrine connection with name "%s" does not exist', $this->connectionName)); } diff --git a/src/Storage/Doctrine/Driver/MySQLDriver.php b/src/Storage/Doctrine/Driver/MySQLDriver.php deleted file mode 100644 index 214125b..0000000 --- a/src/Storage/Doctrine/Driver/MySQLDriver.php +++ /dev/null @@ -1,16 +0,0 @@ -doctrineConnection->saveMessage( new StoredMessage( $retriedMessageEvent->getMessageUid(), $retriedMessageEvent->getMessageClass(), - \DateTimeImmutable::createFromFormat('U', (string) time()) + \DateTimeImmutable::createFromFormat('0.u00 U', microtime()) ) ); } diff --git a/src/Storage/Doctrine/EventListener/UpdateStoredMessageListener.php b/src/Storage/Doctrine/EventListener/UpdateStoredMessageListener.php index 3b566b0..f7f6712 100644 --- a/src/Storage/Doctrine/EventListener/UpdateStoredMessageListener.php +++ b/src/Storage/Doctrine/EventListener/UpdateStoredMessageListener.php @@ -8,6 +8,7 @@ use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; +use Symfony\Component\Messenger\Stamp\DelayStamp; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessageProvider; @@ -28,7 +29,10 @@ public function onMessageReceived(WorkerMessageReceivedEvent $event): void return; } - $storedMessage->setReceivedAt(\DateTimeImmutable::createFromFormat('U', (string) time())); + /** @var DelayStamp $delayStamp */ + $delayStamp = $event->getEnvelope()->last(DelayStamp::class) ?? new DelayStamp(0); + + $storedMessage->updateWaitingTime($delayStamp->getDelay() / 1000); $storedMessage->setReceiverName($event->getReceiverName()); $this->doctrineConnection->updateMessage($storedMessage); @@ -42,7 +46,7 @@ public function onMessageHandled(WorkerMessageHandledEvent $event): void return; } - $storedMessage->setHandledAt(\DateTimeImmutable::createFromFormat('U', (string) time())); + $storedMessage->updateHandlingTime(); $this->doctrineConnection->updateMessage($storedMessage); } @@ -54,7 +58,7 @@ public function onMessageFailed(WorkerMessageFailedEvent $event): void return; } - $storedMessage->setFailedAt(\DateTimeImmutable::createFromFormat('U', (string) time())); + $storedMessage->updateFailingTime(); $this->doctrineConnection->updateMessage($storedMessage); } diff --git a/src/Storage/Doctrine/StoredMessage.php b/src/Storage/Doctrine/StoredMessage.php index 8dde6b8..37f707c 100644 --- a/src/Storage/Doctrine/StoredMessage.php +++ b/src/Storage/Doctrine/StoredMessage.php @@ -13,19 +13,16 @@ */ final class StoredMessage { - private ?\DateTimeImmutable $receivedAt = null; - private ?\DateTimeImmutable $handledAt = null; - private ?\DateTimeImmutable $failedAt = null; - - public function __construct(private string $messageUid, private string $messageClass, private \DateTimeImmutable $dispatchedAt, private ?int $id = null, ?\DateTimeImmutable $receivedAt = null, ?\DateTimeImmutable $handledAt = null, ?\DateTimeImmutable $failedAt = null, private ?string $receiverName = null) - { - if (null !== $receivedAt) { - $this->receivedAt = $receivedAt; - $this->handledAt = $handledAt; - $this->failedAt = $failedAt; - } elseif (null !== $handledAt || null !== $failedAt) { - throw new \RuntimeException('"receivedAt" could not be null if "handledAt" or "failedAt" is not null'); - } + public function __construct( + private string $messageUid, + private string $messageClass, + private \DateTimeImmutable $dispatchedAt, + private ?int $id = null, + private ?float $waitingTime = null, + private ?string $receiverName = null, + private ?float $handlingTime = null, + private ?float $failingTime = null + ) { } public static function fromEnvelope(Envelope $envelope): self @@ -37,11 +34,11 @@ public static function fromEnvelope(Envelope $envelope): self throw new MessengerIdStampMissingException(); } + /** @psalm-suppress PossiblyFalseArgument */ return new self( $monitorIdStamp->getId(), $envelope->getMessage()::class, - \DateTimeImmutable::createFromFormat('U', (string) time()), - null + \DateTimeImmutable::createFromFormat('0.u00 U', microtime()) ); } @@ -73,43 +70,61 @@ public function getDispatchedAt(): \DateTimeImmutable return $this->dispatchedAt; } - public function getReceivedAt(): ?\DateTimeImmutable + public function getWaitingTime(): ?float { - return $this->receivedAt; + return $this->waitingTime; } - public function setReceivedAt(\DateTimeImmutable $receivedAt): void + /** + * @param float $delay The delay in seconds + */ + public function updateWaitingTime(float $delay = 0): void { - $this->receivedAt = $receivedAt; + $now = \DateTimeImmutable::createFromFormat('0.u00 U', microtime()); + /** @psalm-suppress PossiblyFalseReference */ + $this->waitingTime = round((float) $now->format('U.u') - (float) $this->dispatchedAt->format('U.u'), 6) - $delay; } - public function getHandledAt(): ?\DateTimeImmutable + public function setReceiverName(string $receiverName): void { - return $this->handledAt; + $this->receiverName = $receiverName; } - public function setHandledAt(\DateTimeImmutable $handledAt): void + public function getReceiverName(): ?string { - $this->handledAt = $handledAt; + return $this->receiverName; } - public function getFailedAt(): ?\DateTimeImmutable + public function getHandlingTime(): ?float { - return $this->failedAt; + return $this->handlingTime; } - public function setFailedAt(\DateTimeImmutable $failedAt): void + public function updateHandlingTime(): void { - $this->failedAt = $failedAt; + $this->handlingTime = $this->computePassedTimeSinceReceived(); } - public function setReceiverName(string $receiverName): void + public function getFailingTime(): ?float { - $this->receiverName = $receiverName; + return $this->failingTime; } - public function getReceiverName(): ?string + public function updateFailingTime(): void { - return $this->receiverName; + $this->failingTime = $this->computePassedTimeSinceReceived(); + } + + private function computePassedTimeSinceReceived(): float + { + $now = \DateTimeImmutable::createFromFormat('0.u00 U', microtime()); + + /** @psalm-suppress PossiblyFalseReference */ + return round( + (float) $now->format('U.u') + - (float) $this->dispatchedAt->format('U.u') + - $this->waitingTime, + 6 + ); } } diff --git a/src/Twig/TimeDisplayExtension.php b/src/Twig/TimeDisplayExtension.php index cf828d3..084a8a5 100644 --- a/src/Twig/TimeDisplayExtension.php +++ b/src/Twig/TimeDisplayExtension.php @@ -16,8 +16,24 @@ public function getFilters(): array ]; } - public function formatTime(float $seconds): string + public function formatTime(?float $seconds): string { + if (null === $seconds) { + return '-'; + } + + if ($seconds < 0.001) { + $time = (string) (round($seconds, 6) * 1000000); + + return sprintf('%d µs', $time); + } + + if ($seconds < 1) { + $time = (string) (round($seconds, 3) * 1000); + + return sprintf('%d ms', $time); + } + if ($seconds < 10) { $seconds = round($seconds, 2); diff --git a/tests/EventListener/AddStampOnMessageSentListenerTest.php b/tests/EventListener/AddStampOnMessageSentListenerTest.php index 98cf103..4777c5f 100644 --- a/tests/EventListener/AddStampOnMessageSentListenerTest.php +++ b/tests/EventListener/AddStampOnMessageSentListenerTest.php @@ -9,7 +9,7 @@ use Symfony\Component\Messenger\Event\SendMessageToTransportsEvent; use SymfonyCasts\MessengerMonitorBundle\EventListener\AddStampOnMessageSentListener; use SymfonyCasts\MessengerMonitorBundle\Stamp\MonitorIdStamp; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; final class AddStampOnMessageSentListenerTest extends TestCase { diff --git a/tests/EventListener/SendEventOnRetriedMessageTest.php b/tests/EventListener/SendEventOnRetriedMessageTest.php index 979e63b..c48337e 100644 --- a/tests/EventListener/SendEventOnRetriedMessageTest.php +++ b/tests/EventListener/SendEventOnRetriedMessageTest.php @@ -14,7 +14,7 @@ use SymfonyCasts\MessengerMonitorBundle\EventListener\SendEventOnRetriedMessageListener; use SymfonyCasts\MessengerMonitorBundle\FailedMessage\MessageRetriedByUserEvent; use SymfonyCasts\MessengerMonitorBundle\Stamp\MonitorIdStamp; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; final class SendEventOnRetriedMessageTest extends TestCase { diff --git a/tests/FailedMessage/FailedMessageRejecterTest.php b/tests/FailedMessage/FailedMessageRejecterTest.php index 46be860..b4d4e8a 100644 --- a/tests/FailedMessage/FailedMessageRejecterTest.php +++ b/tests/FailedMessage/FailedMessageRejecterTest.php @@ -9,7 +9,7 @@ use Symfony\Component\Messenger\Transport\Receiver\ListableReceiverInterface; use SymfonyCasts\MessengerMonitorBundle\FailedMessage\FailedMessageRejecter; use SymfonyCasts\MessengerMonitorBundle\FailureReceiver\FailureReceiverProvider; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; class FailedMessageRejecterTest extends TestCase { diff --git a/tests/FailedMessage/FailedMessageRepositoryTest.php b/tests/FailedMessage/FailedMessageRepositoryTest.php index 5cf9825..91a0e31 100644 --- a/tests/FailedMessage/FailedMessageRepositoryTest.php +++ b/tests/FailedMessage/FailedMessageRepositoryTest.php @@ -13,7 +13,7 @@ use SymfonyCasts\MessengerMonitorBundle\FailedMessage\FailedMessageDetails; use SymfonyCasts\MessengerMonitorBundle\FailedMessage\FailedMessageRepository; use SymfonyCasts\MessengerMonitorBundle\FailureReceiver\FailureReceiverProvider; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; class FailedMessageRepositoryTest extends TestCase { diff --git a/tests/Fixtures/FailureMessage.php b/tests/Fixtures/FailureMessage.php new file mode 100644 index 0000000..69dbf97 --- /dev/null +++ b/tests/Fixtures/FailureMessage.php @@ -0,0 +1,21 @@ +willFail) { + $this->willFail = false; + + return true; + } + + return $this->willFail; + } +} diff --git a/tests/Fixtures/Message.php b/tests/Fixtures/Message.php new file mode 100644 index 0000000..b513110 --- /dev/null +++ b/tests/Fixtures/Message.php @@ -0,0 +1,10 @@ +failures > 0) { + --$this->failures; + + return true; + } + + return false; + } +} diff --git a/tests/Fixtures/TestableMessage.php b/tests/Fixtures/TestableMessage.php new file mode 100644 index 0000000..537dd4e --- /dev/null +++ b/tests/Fixtures/TestableMessage.php @@ -0,0 +1,13 @@ +willFail) { - $message->willFail = false; + if (true === $message->shouldFail()) { throw new \Exception('oops!'); } } diff --git a/tests/FunctionalTests/AbstractFunctionalTests.php b/tests/FunctionalTests/AbstractFunctionalTests.php index 130e17b..a9e30e3 100644 --- a/tests/FunctionalTests/AbstractFunctionalTests.php +++ b/tests/FunctionalTests/AbstractFunctionalTests.php @@ -5,10 +5,10 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\FunctionalTests; use Doctrine\DBAL\Connection; +use Doctrine\DBAL\FetchMode; use Doctrine\DBAL\Schema\Schema; use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\DomCrawler\Crawler; use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\Messenger\Envelope; @@ -21,7 +21,7 @@ use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\Service\ServiceProviderInterface; use SymfonyCasts\MessengerMonitorBundle\Stamp\MonitorIdStamp; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\Message; use SymfonyCasts\MessengerMonitorBundle\Tests\TestKernel; abstract class AbstractFunctionalTests extends WebTestCase @@ -48,27 +48,16 @@ protected function setUp(): void } $connection->executeQuery('DROP TABLE IF EXISTS messenger_messages'); + $connection->executeQuery('DROP TABLE IF EXISTS messenger_monitor'); - $databasePlatform = $connection->getDatabasePlatform()->getName(); - - $truncateTable = match ($databasePlatform) { - 'mysql' => 'TRUNCATE TABLE messenger_monitor', - 'postgresql' => 'TRUNCATE TABLE messenger_monitor RESTART IDENTITY', - default => throw new InvalidConfigurationException(sprintf('Doctrine platform "%s" is not supported', $databasePlatform)) - }; - - try { - $connection->executeQuery($truncateTable); - } catch (\Throwable) { - self::getContainer()->get('test.symfonycasts.messenger_monitor.storage.doctrine_connection')->executeSchema(new Schema(), $connection); - } + self::getContainer()->get('test.symfonycasts.messenger_monitor.storage.doctrine_connection')->executeSchema(new Schema(), $connection); $this->messageBus = self::getContainer()->get('test.messenger.bus.default'); } - protected function dispatchMessage(bool $willFail = false): Envelope + protected function dispatchMessage(Message $message): Envelope { - return $this->messageBus->dispatch(new Envelope(new TestableMessage($willFail))); + return $this->messageBus->dispatch(new Envelope($message)); } protected function assertQueuesCounts(array $expectedQueues, Crawler $crawler): void @@ -103,19 +92,24 @@ protected function assertFailedMessagesCount(int $count, Crawler $crawler): void ); } - protected function assertStoredMessageIsInDB(Envelope $envelope): void + protected function assertStoredMessageIsInDB(Envelope $envelope, int $count = 1): void { /** @var Connection $connection */ $connection = self::getContainer()->get('doctrine.dbal.default_connection'); /** @var MonitorIdStamp $monitorIdStamp */ $monitorIdStamp = $envelope->last(MonitorIdStamp::class); - $this->assertNotFalse( - $connection->executeQuery('SELECT id FROM messenger_monitor WHERE message_uid = :id', ['id' => $monitorIdStamp->getId()]) + + $this->assertSame( + $count, + (int) $connection->executeQuery( + 'SELECT count(id) FROM messenger_monitor WHERE message_uid = :message_uid', + ['message_uid' => $monitorIdStamp->getId()] + )->fetch(FetchMode::COLUMN) ); } - protected function handleMessage(Envelope $envelope, string $queueName): void + protected function handleLastMessageInQueue(string $queueName): void { /** @var EventDispatcherInterface $eventDispatcher */ $eventDispatcher = self::getContainer()->get('event_dispatcher'); @@ -124,7 +118,7 @@ protected function handleMessage(Envelope $envelope, string $queueName): void $receiver = $this->getReceiver($queueName); $worker = new Worker( - [$queueName => new SingleMessageReceiver($receiver, $receiver->find($this->getMessageId($envelope)))], + [$queueName => new SingleMessageReceiver($receiver, $receiver->find($this->getLastMessageId($queueName)))], $this->messageBus, $eventDispatcher ); @@ -141,6 +135,13 @@ protected function getMessageId(Envelope $envelope): mixed return $transportMessageIdStamp->getId(); } + protected function getLastMessageId(string $queueName): mixed + { + $receiver = $this->getReceiver($queueName); + + return $this->getMessageId(current($receiver->get())); + } + protected function getLastFailedMessageId(): mixed { $receiver = $this->getReceiver('failed'); diff --git a/tests/FunctionalTests/DashboardControllerTest.php b/tests/FunctionalTests/DashboardControllerTest.php index d3cbd6d..c25a41d 100644 --- a/tests/FunctionalTests/DashboardControllerTest.php +++ b/tests/FunctionalTests/DashboardControllerTest.php @@ -5,16 +5,16 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\FunctionalTests; use Symfony\Component\HttpFoundation\Response; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\FailureMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\RetryableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; +/** @group functional */ final class DashboardControllerTest extends AbstractFunctionalTests { public function testDashboardEmpty(): void { - $crawler = $this->client->request('GET', '/'); - self::assertResponseIsSuccessful(); - - $this->assertQueuesCounts(['queue' => 0, 'failed' => 0], $crawler); - $this->assertFailedMessagesCount(0, $crawler); + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0]); } public function testDashboardWithForbiddenUser(): void @@ -25,26 +25,45 @@ public function testDashboardWithForbiddenUser(): void public function testDashboardWithOneQueuedMessage(): void { - $envelope = $this->dispatchMessage(); + $envelope = $this->dispatchMessage(new TestableMessage()); - $crawler = $this->client->request('GET', '/'); - self::assertResponseIsSuccessful(); - - $this->assertQueuesCounts(['queue' => 1, 'failed' => 0], $crawler); - $this->assertFailedMessagesCount(1, $crawler); + $this->assertDisplayedQueuesOnDashboard(['queue' => 1, 'queue_with_retry' => 0, 'failed' => 0]); $this->assertStoredMessageIsInDB($envelope); - $this->handleMessage($envelope, 'queue'); - $this->testDashboardEmpty(); + $this->handleLastMessageInQueue('queue'); + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0]); } public function testDashboardWithOneFailedMessage(): void { - $envelope = $this->dispatchMessage(true); - $this->handleMessage($envelope, 'queue'); + $envelope = $this->dispatchMessage(new FailureMessage()); + $this->handleLastMessageInQueue('queue'); + + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 1]); + $this->assertStoredMessageIsInDB($envelope); + } + public function testDashboardWithOneRetryableMessage(): void + { + $envelope = $this->dispatchMessage(new RetryableMessage()); + + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 1, 'failed' => 0]); + $this->assertStoredMessageIsInDB($envelope); + + $this->handleLastMessageInQueue('queue_with_retry'); + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 1, 'failed' => 0]); + $this->assertStoredMessageIsInDB($envelope, 2); + + $this->handleLastMessageInQueue('queue_with_retry'); + $this->assertDisplayedQueuesOnDashboard(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 1]); + $this->assertStoredMessageIsInDB($envelope, 2); + } + + private function assertDisplayedQueuesOnDashboard(array $queues): void + { $crawler = $this->client->request('GET', '/'); self::assertResponseIsSuccessful(); - $this->assertQueuesCounts(['queue' => 0, 'failed' => 1], $crawler); + $this->assertQueuesCounts($queues, $crawler); + $this->assertFailedMessagesCount($queues['failed'], $crawler); } } diff --git a/tests/FunctionalTests/RejectFailedMessageControllerTest.php b/tests/FunctionalTests/RejectFailedMessageControllerTest.php index 6bfb408..b8086d7 100644 --- a/tests/FunctionalTests/RejectFailedMessageControllerTest.php +++ b/tests/FunctionalTests/RejectFailedMessageControllerTest.php @@ -4,18 +4,21 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\FunctionalTests; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\FailureMessage; + +/** @group functional */ final class RejectFailedMessageControllerTest extends AbstractFunctionalTests { public function testRejectFailedMessageSuccess(): void { - $envelope = $this->dispatchMessage(true); - $this->handleMessage($envelope, 'queue'); + $envelope = $this->dispatchMessage(new FailureMessage()); + $this->handleLastMessageInQueue('queue'); $this->client->followRedirects(); $crawler = $this->client->request('GET', sprintf('/failed-message/reject/%s', $id = $this->getLastFailedMessageId())); self::assertResponseIsSuccessful(); - $this->assertQueuesCounts(['queue' => 0, 'failed' => 0], $crawler); + $this->assertQueuesCounts(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0], $crawler); $this->assertAlertIsPresent($crawler, '.alert-success', sprintf('Message with id "%s" correctly rejected.', $id)); } @@ -25,7 +28,7 @@ public function testRejectFailedMessageFails(): void $crawler = $this->client->request('GET', '/failed-message/reject/123'); self::assertResponseIsSuccessful(); - $this->assertQueuesCounts(['queue' => 0, 'failed' => 0], $crawler); + $this->assertQueuesCounts(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0], $crawler); $this->assertAlertIsPresent( $crawler, '.alert-danger', diff --git a/tests/FunctionalTests/RetryFailedMessageControllerTest.php b/tests/FunctionalTests/RetryFailedMessageControllerTest.php index 56151e1..f7c67d5 100644 --- a/tests/FunctionalTests/RetryFailedMessageControllerTest.php +++ b/tests/FunctionalTests/RetryFailedMessageControllerTest.php @@ -4,18 +4,21 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\FunctionalTests; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\FailureMessage; + +/** @group functional */ final class RetryFailedMessageControllerTest extends AbstractFunctionalTests { public function testRetryFailedMessage(): void { - $envelope = $this->dispatchMessage(true); - $this->handleMessage($envelope, 'queue'); + $this->dispatchMessage(new FailureMessage()); + $this->handleLastMessageInQueue('queue'); $this->client->followRedirects(); $crawler = $this->client->request('GET', sprintf('/failed-message/retry/%s', $id = $this->getLastFailedMessageId())); self::assertResponseIsSuccessful(); - $this->assertQueuesCounts(['queue' => 0, 'failed' => 0], $crawler); + $this->assertQueuesCounts(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0], $crawler); $this->assertAlertIsPresent($crawler, '.alert-success', sprintf('Message with id "%s" correctly retried.', $id)); } @@ -25,7 +28,7 @@ public function testRetryFailedMessageFails(): void $crawler = $this->client->request('GET', '/failed-message/retry/123'); self::assertResponseIsSuccessful(); - $this->assertQueuesCounts(['queue' => 0, 'failed' => 0], $crawler); + $this->assertQueuesCounts(['queue' => 0, 'queue_with_retry' => 0, 'failed' => 0], $crawler); $this->assertAlertIsPresent( $crawler, '.alert-danger', diff --git a/tests/IntegrationTests/AbstractDoctrineIntegrationTests.php b/tests/IntegrationTests/AbstractDoctrineIntegrationTests.php index 786d400..2c7eb2f 100644 --- a/tests/IntegrationTests/AbstractDoctrineIntegrationTests.php +++ b/tests/IntegrationTests/AbstractDoctrineIntegrationTests.php @@ -7,7 +7,6 @@ use Doctrine\DBAL\Connection; use Doctrine\DBAL\Schema\Schema; use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase; -use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException; use Symfony\Component\HttpKernel\KernelInterface; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection as DoctrineConnection; use SymfonyCasts\MessengerMonitorBundle\Tests\TestKernel; @@ -36,20 +35,9 @@ protected function setUp(): void $this->markTestSkipped(sprintf('Can\'t connect to connection: %s', $exception->getMessage())); } - $this->doctrineConnection = self::getContainer()->get('test.symfonycasts.messenger_monitor.storage.doctrine_connection'); - - $databasePlatform = $connection->getDatabasePlatform()->getName(); - - $truncateTable = match ($databasePlatform) { - 'mysql' => 'TRUNCATE TABLE messenger_monitor', - 'postgresql' => 'TRUNCATE TABLE messenger_monitor RESTART IDENTITY', - default => throw new InvalidConfigurationException(sprintf('Doctrine platform "%s" is not supported', $databasePlatform)) - }; + $connection->executeQuery('DROP TABLE IF EXISTS messenger_monitor'); - try { - $connection->executeQuery($truncateTable); - } catch (\Throwable) { - $this->doctrineConnection->executeSchema(new Schema(), $connection); - } + $this->doctrineConnection = self::getContainer()->get('test.symfonycasts.messenger_monitor.storage.doctrine_connection'); + $this->doctrineConnection->executeSchema(new Schema(), $connection); } } diff --git a/tests/IntegrationTests/Storage/DoctrineConnectionTest.php b/tests/IntegrationTests/Storage/DoctrineConnectionTest.php index d35d679..265d9a4 100644 --- a/tests/IntegrationTests/Storage/DoctrineConnectionTest.php +++ b/tests/IntegrationTests/Storage/DoctrineConnectionTest.php @@ -5,11 +5,12 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\IntegrationTests\Storage; use Doctrine\DBAL\Connection; +use Symfony\Bridge\PhpUnit\ClockMock; use SymfonyCasts\MessengerMonitorBundle\Statistics\MetricsPerMessageType; use SymfonyCasts\MessengerMonitorBundle\Statistics\Statistics; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; use SymfonyCasts\MessengerMonitorBundle\Tests\IntegrationTests\AbstractDoctrineIntegrationTests; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; final class DoctrineConnectionTest extends AbstractDoctrineIntegrationTests { @@ -18,7 +19,7 @@ public function testSaveAndLoadMessage(): void $uuid = uuid_create(UUID_TYPE_RANDOM); $this->doctrineConnection->saveMessage( - new StoredMessage($uuid, TestableMessage::class, $dispatchedAt = (new \DateTimeImmutable())->setTime(0, 0, 0)) + new StoredMessage($uuid, TestableMessage::class, $dispatchedAt = (new \DateTimeImmutable())->setTime(0, 0, 0, 1000)) ); $storedMessage = $this->doctrineConnection->findMessage($uuid); @@ -42,31 +43,22 @@ public function testUpdateMessage(): void { $uuid = uuid_create(UUID_TYPE_RANDOM); - $this->doctrineConnection->saveMessage($storedMessage = new StoredMessage($uuid, TestableMessage::class, new \DateTimeImmutable())); - $storedMessage->setReceivedAt(\DateTimeImmutable::createFromFormat('U', (string) time())); - $storedMessage->setHandledAt(\DateTimeImmutable::createFromFormat('U', (string) time())); - $storedMessage->setFailedAt(\DateTimeImmutable::createFromFormat('U', (string) time())); + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:01.123'))->format('U.u')); + + $this->doctrineConnection->saveMessage($storedMessage = new StoredMessage('message_uid', TestableMessage::class, new \DateTimeImmutable('2020-01-01 00:00:00.123'))); + $storedMessage->updateWaitingTime(); $storedMessage->setReceiverName('receiver_name'); + $storedMessage->updateHandlingTime(); + $storedMessage->updateFailingTime(); $this->doctrineConnection->updateMessage($storedMessage); $storedMessageLoadedFromDatabase = $this->doctrineConnection->findMessage($uuid); - $this->assertSame( - $storedMessage->getReceivedAt()->format('Y-m-d H:i:s'), - $storedMessageLoadedFromDatabase->getReceivedAt()->format('Y-m-d H:i:s') - ); - - $this->assertSame( - $storedMessage->getHandledAt()->format('Y-m-d H:i:s'), - $storedMessageLoadedFromDatabase->getHandledAt()->format('Y-m-d H:i:s') - ); - - $this->assertSame( - $storedMessage->getFailedAt()->format('Y-m-d H:i:s'), - $storedMessageLoadedFromDatabase->getFailedAt()->format('Y-m-d H:i:s') - ); - + $this->assertSame($storedMessage->getWaitingTime(), $storedMessageLoadedFromDatabase->getWaitingTime()); $this->assertSame($storedMessage->getReceiverName(), $storedMessageLoadedFromDatabase->getReceiverName()); + $this->assertSame($storedMessage->getHandlingTime(), $storedMessageLoadedFromDatabase->getHandlingTime()); + $this->assertSame($storedMessage->getFailingTime(), $storedMessageLoadedFromDatabase->getFailingTime()); } public function testGetStatistics(): void @@ -79,8 +71,8 @@ public function testGetStatistics(): void $statistics = $this->doctrineConnection->getStatistics($fromDate = new \DateTimeImmutable('1 hour ago'), $toDate = new \DateTimeImmutable()); $expectedStatistics = new Statistics($fromDate, $toDate); - $expectedStatistics->add(new MetricsPerMessageType($fromDate, $toDate, TestableMessage::class, 2, 120.0, 120.0)); - $expectedStatistics->add(new MetricsPerMessageType($fromDate, $toDate, 'Another'.TestableMessage::class, 1, 60.0, 60.0)); + $expectedStatistics->add(new MetricsPerMessageType($fromDate, $toDate, TestableMessage::class, 2, 0.2, 0.3)); + $expectedStatistics->add(new MetricsPerMessageType($fromDate, $toDate, 'Another'.TestableMessage::class, 2, 0.1, 0.2)); $this->assertEquals($expectedStatistics, $statistics); } @@ -99,9 +91,9 @@ private function storeMessages(): void [ 'message_uid' => $uuid, 'class' => TestableMessage::class, - 'dispatched_at' => (new \DateTimeImmutable('3 minutes ago'))->format('Y-m-d H:i:s'), - 'received_at' => (new \DateTimeImmutable('2 minutes ago'))->format('Y-m-d H:i:s'), - 'handled_at' => (new \DateTimeImmutable('1 minute ago'))->format('Y-m-d H:i:s'), + 'dispatched_at' => (new \DateTimeImmutable('3 minutes ago'))->format('U.u'), + 'waiting_time' => 0.1, + 'handling_time' => 0.2, ] ); @@ -110,32 +102,44 @@ private function storeMessages(): void [ 'message_uid' => $uuid2, 'class' => TestableMessage::class, - 'dispatched_at' => (new \DateTimeImmutable('10 minutes ago'))->format('Y-m-d H:i:s'), - 'received_at' => (new \DateTimeImmutable('7 minutes ago'))->format('Y-m-d H:i:s'), - 'handled_at' => (new \DateTimeImmutable('4 minute ago'))->format('Y-m-d H:i:s'), + 'dispatched_at' => (new \DateTimeImmutable('10 minutes ago'))->format('U.u'), + 'waiting_time' => 0.3, + 'handling_time' => 0.4, + ] + ); + + $connection->insert( + 'messenger_monitor', + [ + 'message_uid' => $uuid3, + 'class' => 'Another'.TestableMessage::class, + 'dispatched_at' => (new \DateTimeImmutable('3 minutes ago'))->format('U.u'), + 'waiting_time' => 0.1, + 'handling_time' => 0.2, ] ); + // this one should only affect waiting_time metric + // proves that "null" values are not assumed as "0" in AVG() sql function $connection->insert( 'messenger_monitor', [ 'message_uid' => $uuid3, 'class' => 'Another'.TestableMessage::class, - 'dispatched_at' => (new \DateTimeImmutable('3 minutes ago'))->format('Y-m-d H:i:s'), - 'received_at' => (new \DateTimeImmutable('2 minutes ago'))->format('Y-m-d H:i:s'), - 'handled_at' => (new \DateTimeImmutable('1 minute ago'))->format('Y-m-d H:i:s'), + 'dispatched_at' => (new \DateTimeImmutable('3 minutes ago'))->format('U.u'), + 'waiting_time' => 0.1, ] ); - // should not be part of statistics + // should not be part of statistics because it is too old $connection->insert( 'messenger_monitor', [ 'message_uid' => $uuid2, 'class' => TestableMessage::class, - 'dispatched_at' => (new \DateTimeImmutable('6 hours ago'))->format('Y-m-d H:i:s'), - 'received_at' => (new \DateTimeImmutable('6 hours ago'))->format('Y-m-d H:i:s'), - 'handled_at' => (new \DateTimeImmutable('6 hours ago'))->format('Y-m-d H:i:s'), + 'dispatched_at' => (new \DateTimeImmutable('6 hours ago'))->format('U.u'), + 'waiting_time' => 1, + 'handling_time' => 2, ] ); } diff --git a/tests/Storage/Doctrine/Driver/MySQLDriverTest.php b/tests/Storage/Doctrine/Driver/MySQLDriverTest.php deleted file mode 100644 index 5ef4fd9..0000000 --- a/tests/Storage/Doctrine/Driver/MySQLDriverTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertSame( - 'TIME_TO_SEC(TIMEDIFF(field_from, field_to))', - $mysqlDriver->getDateDiffInSecondsExpression('field_from', 'field_to') - ); - } -} diff --git a/tests/Storage/Doctrine/Driver/PostgreSQLDriverTest.php b/tests/Storage/Doctrine/Driver/PostgreSQLDriverTest.php deleted file mode 100644 index 53fa125..0000000 --- a/tests/Storage/Doctrine/Driver/PostgreSQLDriverTest.php +++ /dev/null @@ -1,20 +0,0 @@ -assertSame( - 'extract(epoch from (field_from - field_to))', - $mysqlDriver->getDateDiffInSecondsExpression('field_from', 'field_to') - ); - } -} diff --git a/tests/Storage/Doctrine/EventListener/SaveRetriedMessageListenerTest.php b/tests/Storage/Doctrine/EventListener/SaveRetriedMessageListenerTest.php index 0a86035..066768c 100644 --- a/tests/Storage/Doctrine/EventListener/SaveRetriedMessageListenerTest.php +++ b/tests/Storage/Doctrine/EventListener/SaveRetriedMessageListenerTest.php @@ -9,7 +9,7 @@ use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\EventListener\SaveRetriedMessageListener; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; final class SaveRetriedMessageListenerTest extends TestCase { diff --git a/tests/Storage/Doctrine/EventListener/SaveStoredMessageOnMessageSentListenerTest.php b/tests/Storage/Doctrine/EventListener/SaveStoredMessageOnMessageSentListenerTest.php index b995b7d..4e2e750 100644 --- a/tests/Storage/Doctrine/EventListener/SaveStoredMessageOnMessageSentListenerTest.php +++ b/tests/Storage/Doctrine/EventListener/SaveStoredMessageOnMessageSentListenerTest.php @@ -11,7 +11,7 @@ use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\EventListener\SaveStoredMessageOnMessageSentListener; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; final class SaveStoredMessageOnMessageSentListenerTest extends TestCase { diff --git a/tests/Storage/Doctrine/EventListener/UpdateStoredMessageListenerTest.php b/tests/Storage/Doctrine/EventListener/UpdateStoredMessageListenerTest.php index b2600b6..287043f 100644 --- a/tests/Storage/Doctrine/EventListener/UpdateStoredMessageListenerTest.php +++ b/tests/Storage/Doctrine/EventListener/UpdateStoredMessageListenerTest.php @@ -5,21 +5,27 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\Storage\Doctrine\EventListener; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\Messenger\Envelope; use Symfony\Component\Messenger\Event\WorkerMessageFailedEvent; use Symfony\Component\Messenger\Event\WorkerMessageHandledEvent; use Symfony\Component\Messenger\Event\WorkerMessageReceivedEvent; +use Symfony\Component\Messenger\Stamp\DelayStamp; use SymfonyCasts\MessengerMonitorBundle\Stamp\MonitorIdStamp; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\EventListener\UpdateStoredMessageListener; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessageProvider; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; +/** @group time-sensitive */ final class UpdateStoredMessageListenerTest extends TestCase { public function testUpdateOnMessageReceived(): void { + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:02'))->format('U')); + $listener = new UpdateStoredMessageListener( $doctrineConnection = $this->createMock(Connection::class), $storedMessageProvider = $this->createMock(StoredMessageProvider::class) @@ -30,29 +36,59 @@ public function testUpdateOnMessageReceived(): void $storedMessageProvider->expects($this->once()) ->method('getStoredMessage') ->with($envelope) - ->willReturn($storedMessage = new StoredMessage($stamp->getId(), TestableMessage::class, new \DateTimeImmutable())); + ->willReturn( + $storedMessage = new StoredMessage( + $stamp->getId(), + TestableMessage::class, + new \DateTimeImmutable('2020-01-01 00:00:00') + ) + ); - $doctrineConnection->expects($this->once()) - ->method('updateMessage') - ->with($storedMessage); + $doctrineConnection->expects($this->once())->method('updateMessage')->with($storedMessage); $listener->onMessageReceived(new WorkerMessageReceivedEvent($envelope, 'receiver-name')); - $this->assertNotNull($storedMessage->getReceivedAt()); + $this->assertSame(2.0, $storedMessage->getWaitingTime()); } - public function testUpdateOnMessageReceivedDoesNotUpdateIfNoMessageFound(): void + public function testUpdateOnMessageReceivedWithDelayStamp(): void { + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:02'))->format('U')); + $listener = new UpdateStoredMessageListener( $doctrineConnection = $this->createMock(Connection::class), $storedMessageProvider = $this->createMock(StoredMessageProvider::class) ); - $envelope = new Envelope(new TestableMessage()); + $envelope = new Envelope(new TestableMessage(), [$stamp = new MonitorIdStamp(), new DelayStamp(1000)]); $storedMessageProvider->expects($this->once()) ->method('getStoredMessage') ->with($envelope) - ->willReturn(null); + ->willReturn( + $storedMessage = new StoredMessage( + $stamp->getId(), + TestableMessage::class, + new \DateTimeImmutable('2020-01-01 00:00:00') + ) + ); + + $doctrineConnection->expects($this->once())->method('updateMessage')->with($storedMessage); + + $listener->onMessageReceived(new WorkerMessageReceivedEvent($envelope, 'receiver-name')); + $this->assertSame(1.0, $storedMessage->getWaitingTime()); + } + + public function testUpdateOnMessageReceivedDoesNotUpdateIfNoMessageFound(): void + { + $listener = new UpdateStoredMessageListener( + $doctrineConnection = $this->createMock(Connection::class), + $storedMessageProvider = $this->createMock(StoredMessageProvider::class) + ); + + $envelope = new Envelope(new TestableMessage()); + + $storedMessageProvider->expects($this->once())->method('getStoredMessage')->with($envelope)->willReturn(null); $doctrineConnection->expects($this->never())->method('updateMessage'); @@ -71,14 +107,14 @@ public function testUpdateOnMessageHandled(): void $storedMessageProvider->expects($this->once()) ->method('getStoredMessage') ->with($envelope) - ->willReturn($storedMessage = new StoredMessage($stamp->getId(), TestableMessage::class, new \DateTimeImmutable())); + ->willReturn( + $storedMessage = new StoredMessage($stamp->getId(), TestableMessage::class, new \DateTimeImmutable()) + ); - $doctrineConnection->expects($this->once()) - ->method('updateMessage') - ->with($storedMessage); + $doctrineConnection->expects($this->once())->method('updateMessage')->with($storedMessage); $listener->onMessageHandled(new WorkerMessageHandledEvent($envelope, 'receiver-name')); - $this->assertNotNull($storedMessage->getHandledAt()); + $this->assertNotNull($storedMessage->getHandlingTime()); } public function testUpdateOnMessageHandledDoesNotUpdateIfNoMessageFound(): void @@ -90,10 +126,7 @@ public function testUpdateOnMessageHandledDoesNotUpdateIfNoMessageFound(): void $envelope = new Envelope(new TestableMessage()); - $storedMessageProvider->expects($this->once()) - ->method('getStoredMessage') - ->with($envelope) - ->willReturn(null); + $storedMessageProvider->expects($this->once())->method('getStoredMessage')->with($envelope)->willReturn(null); $doctrineConnection->expects($this->never())->method('updateMessage'); @@ -112,14 +145,14 @@ public function testUpdateOnMessageFailed(): void $storedMessageProvider->expects($this->once()) ->method('getStoredMessage') ->with($envelope) - ->willReturn($storedMessage = new StoredMessage($stamp->getId(), TestableMessage::class, new \DateTimeImmutable())); + ->willReturn( + $storedMessage = new StoredMessage($stamp->getId(), TestableMessage::class, new \DateTimeImmutable()) + ); - $doctrineConnection->expects($this->once()) - ->method('updateMessage') - ->with($storedMessage); + $doctrineConnection->expects($this->once())->method('updateMessage')->with($storedMessage); $listener->onMessageFailed(new WorkerMessageFailedEvent($envelope, 'receiver-name', new \Exception())); - $this->assertNotNull($storedMessage->getFailedAt()); + $this->assertNotNull($storedMessage->getFailingTime()); } public function testUpdateOnMessageFailedDoesNotUpdateIfNoMessageFound(): void @@ -131,10 +164,7 @@ public function testUpdateOnMessageFailedDoesNotUpdateIfNoMessageFound(): void $envelope = new Envelope(new TestableMessage()); - $storedMessageProvider->expects($this->once()) - ->method('getStoredMessage') - ->with($envelope) - ->willReturn(null); + $storedMessageProvider->expects($this->once())->method('getStoredMessage')->with($envelope)->willReturn(null); $doctrineConnection->expects($this->never())->method('updateMessage'); diff --git a/tests/Storage/Doctrine/StoredMessageProviderTest.php b/tests/Storage/Doctrine/StoredMessageProviderTest.php index 946d363..b0f6c58 100644 --- a/tests/Storage/Doctrine/StoredMessageProviderTest.php +++ b/tests/Storage/Doctrine/StoredMessageProviderTest.php @@ -11,7 +11,7 @@ use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Connection; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessageProvider; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; final class StoredMessageProviderTest extends TestCase { diff --git a/tests/Storage/Doctrine/StoredMessageTest.php b/tests/Storage/Doctrine/StoredMessageTest.php index 218120e..92a8e3c 100644 --- a/tests/Storage/Doctrine/StoredMessageTest.php +++ b/tests/Storage/Doctrine/StoredMessageTest.php @@ -5,12 +5,14 @@ namespace SymfonyCasts\MessengerMonitorBundle\Tests\Storage\Doctrine; use PHPUnit\Framework\TestCase; +use Symfony\Bridge\PhpUnit\ClockMock; use Symfony\Component\Messenger\Envelope; use SymfonyCasts\MessengerMonitorBundle\Stamp\MonitorIdStamp; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\Exception\MessengerIdStampMissingException; use SymfonyCasts\MessengerMonitorBundle\Storage\Doctrine\StoredMessage; -use SymfonyCasts\MessengerMonitorBundle\Tests\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; +/** @group time-sensitive */ final class StoredMessageTest extends TestCase { public function testStoredMessage(): void @@ -18,17 +20,24 @@ public function testStoredMessage(): void $uuid = uuid_create(UUID_TYPE_RANDOM); $storedMessage = new StoredMessage( - $uuid, TestableMessage::class, $dispatchedAt = new \DateTimeImmutable(), 1, $receivedAt = new \DateTimeImmutable(), $handledAt = new \DateTimeImmutable(), $failedAt = new \DateTimeImmutable(), $receiverName = 'receiver_name' + $uuid, + TestableMessage::class, + $dispatchedAt = new \DateTimeImmutable(), + 1, + $waitingTime = 0.1, + $receiverName = 'receiver_name', + $handlingTime = 0.2, + $failingTime = 0.3 ); $this->assertSame(1, $storedMessage->getId()); $this->assertSame($uuid, $storedMessage->getMessageUid()); $this->assertSame(TestableMessage::class, $storedMessage->getMessageClass()); $this->assertSame($dispatchedAt, $storedMessage->getDispatchedAt()); - $this->assertSame($receivedAt, $storedMessage->getReceivedAt()); - $this->assertSame($handledAt, $storedMessage->getHandledAt()); - $this->assertSame($failedAt, $storedMessage->getFailedAt()); + $this->assertSame($waitingTime, $storedMessage->getWaitingTime()); $this->assertSame($receiverName, $storedMessage->getReceiverName()); + $this->assertSame($handlingTime, $storedMessage->getHandlingTime()); + $this->assertSame($failingTime, $storedMessage->getFailingTime()); } public function testCreateFromEnvelope(): void @@ -48,4 +57,56 @@ public function testExceptionWhenCreateFromEnvelopeWithoutStamp(): void StoredMessage::fromEnvelope(new Envelope(new TestableMessage())); } + + public function testUpdateWaitingTime(): void + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:01.123456'))->format('U.u')); + + $storedMessage = new StoredMessage($uuid, TestableMessage::class, new \DateTimeImmutable('2020-01-01 00:00:00.000')); + + $storedMessage->updateWaitingTime(); + $this->assertSame(1.123456, $storedMessage->getWaitingTime()); + } + + public function testUpdateWaitingTimeWithOffset(): void + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:03.123456'))->format('U.u')); + + $storedMessage = new StoredMessage($uuid, TestableMessage::class, new \DateTimeImmutable('2020-01-01 00:00:00.000')); + + $storedMessage->updateWaitingTime(2); + $this->assertSame(1.123456, $storedMessage->getWaitingTime()); + } + + public function testUpdateHandlingTime(): void + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:02.123456'))->format('U.u')); + + $storedMessage = new StoredMessage($uuid, TestableMessage::class, new \DateTimeImmutable('2020-01-01 00:00:00.000'), 1, 1.0); + + $storedMessage->updateHandlingTime(); + $this->assertSame(1.123456, $storedMessage->getHandlingTime()); + } + + public function testUpdateFailingTime(): void + { + $uuid = uuid_create(UUID_TYPE_RANDOM); + + ClockMock::register(StoredMessage::class); + ClockMock::withClockMock((new \DateTimeImmutable('2020-01-01 00:00:02.123456'))->format('U.u')); + + $storedMessage = new StoredMessage($uuid, TestableMessage::class, new \DateTimeImmutable('2020-01-01 00:00:00.000'), 1, 1.0); + + $storedMessage->updateFailingTime(); + $this->assertSame(1.123456, $storedMessage->getFailingTime()); + } } diff --git a/tests/TestKernel.php b/tests/TestKernel.php index b1ad176..3f6f764 100644 --- a/tests/TestKernel.php +++ b/tests/TestKernel.php @@ -16,6 +16,10 @@ use Symfony\Component\Routing\Loader\Configurator\RoutingConfigurator; use Symfony\Component\Security\Core\User\InMemoryUser; use SymfonyCasts\MessengerMonitorBundle\SymfonyCastsMessengerMonitorBundle; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\FailureMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\RetryableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessage; +use SymfonyCasts\MessengerMonitorBundle\Tests\Fixtures\TestableMessageHandler; final class TestKernel extends Kernel { @@ -98,10 +102,16 @@ protected function configureContainer(ContainerBuilder $container) 'dsn' => 'doctrine://default?queue_name=queue', 'retry_strategy' => ['max_retries' => 0], ], + 'queue_with_retry' => [ + 'dsn' => 'doctrine://default?queue_name=queue_with_retry', + 'retry_strategy' => ['max_retries' => 1, 'delay' => 0, 'multiplier' => 1], + ], 'failed' => 'doctrine://default?queue_name=failed', ], 'routing' => [ TestableMessage::class => 'queue', + FailureMessage::class => 'queue', + RetryableMessage::class => 'queue_with_retry', ], ], 'test' => true, diff --git a/tests/TestableMessage.php b/tests/TestableMessage.php deleted file mode 100644 index 509fe8b..0000000 --- a/tests/TestableMessage.php +++ /dev/null @@ -1,15 +0,0 @@ -willFail = $willFail; - } -} diff --git a/tests/Twig/TimeDisplayExtensionTest.php b/tests/Twig/TimeDisplayExtensionTest.php index bbe008f..f0be133 100644 --- a/tests/Twig/TimeDisplayExtensionTest.php +++ b/tests/Twig/TimeDisplayExtensionTest.php @@ -13,12 +13,17 @@ public function testFormatTime(): void { $timeDisplayExtension = new TimeDisplayExtension(); - $this->assertSame('10 seconds', $timeDisplayExtension->formatTime(10)); + $this->assertSame('-', $timeDisplayExtension->formatTime(null)); - $this->assertSame('1 second', $timeDisplayExtension->formatTime(1)); + $this->assertSame('12 µs', $timeDisplayExtension->formatTime(0.000012)); + + $this->assertSame('12 ms', $timeDisplayExtension->formatTime(0.012)); + + $this->assertSame('10 seconds', $timeDisplayExtension->formatTime(10.0)); + + $this->assertSame('1 second', $timeDisplayExtension->formatTime(1.0)); $this->assertSame('1 second', $timeDisplayExtension->formatTime(1.004)); - $this->assertSame('0.12 seconds', $timeDisplayExtension->formatTime(0.123)); $this->assertSame('11 seconds', $timeDisplayExtension->formatTime(11.123)); $this->assertSame('1 minute', $timeDisplayExtension->formatTime(60.123));