NotifyVisitToMercureTest.php 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. <?php
  2. declare(strict_types=1);
  3. namespace ShlinkioTest\Shlink\Core\EventDispatcher;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use PHPUnit\Framework\TestCase;
  6. use Prophecy\Argument;
  7. use Prophecy\PhpUnit\ProphecyTrait;
  8. use Prophecy\Prophecy\ObjectProphecy;
  9. use Psr\Log\LoggerInterface;
  10. use RuntimeException;
  11. use Shlinkio\Shlink\Core\Entity\ShortUrl;
  12. use Shlinkio\Shlink\Core\Entity\Visit;
  13. use Shlinkio\Shlink\Core\EventDispatcher\Event\VisitLocated;
  14. use Shlinkio\Shlink\Core\EventDispatcher\NotifyVisitToMercure;
  15. use Shlinkio\Shlink\Core\Mercure\MercureUpdatesGeneratorInterface;
  16. use Shlinkio\Shlink\Core\Model\Visitor;
  17. use Symfony\Component\Mercure\PublisherInterface;
  18. use Symfony\Component\Mercure\Update;
  19. class NotifyVisitToMercureTest extends TestCase
  20. {
  21. use ProphecyTrait;
  22. private NotifyVisitToMercure $listener;
  23. private ObjectProphecy $publisher;
  24. private ObjectProphecy $updatesGenerator;
  25. private ObjectProphecy $em;
  26. private ObjectProphecy $logger;
  27. public function setUp(): void
  28. {
  29. $this->publisher = $this->prophesize(PublisherInterface::class);
  30. $this->updatesGenerator = $this->prophesize(MercureUpdatesGeneratorInterface::class);
  31. $this->em = $this->prophesize(EntityManagerInterface::class);
  32. $this->logger = $this->prophesize(LoggerInterface::class);
  33. $this->listener = new NotifyVisitToMercure(
  34. $this->publisher->reveal(),
  35. $this->updatesGenerator->reveal(),
  36. $this->em->reveal(),
  37. $this->logger->reveal(),
  38. );
  39. }
  40. /** @test */
  41. public function notificationsAreNotSentWhenVisitCannotBeFound(): void
  42. {
  43. $visitId = '123';
  44. $findVisit = $this->em->find(Visit::class, $visitId)->willReturn(null);
  45. $logWarning = $this->logger->warning(
  46. 'Tried to notify mercure for visit with id "{visitId}", but it does not exist.',
  47. ['visitId' => $visitId],
  48. );
  49. $logDebug = $this->logger->debug(Argument::cetera());
  50. $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate(
  51. Argument::type(Visit::class),
  52. );
  53. $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate(Argument::type(Visit::class));
  54. $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate(Argument::type(Visit::class));
  55. $publish = $this->publisher->__invoke(Argument::type(Update::class));
  56. ($this->listener)(new VisitLocated($visitId));
  57. $findVisit->shouldHaveBeenCalledOnce();
  58. $logWarning->shouldHaveBeenCalledOnce();
  59. $logDebug->shouldNotHaveBeenCalled();
  60. $buildNewShortUrlVisitUpdate->shouldNotHaveBeenCalled();
  61. $buildNewVisitUpdate->shouldNotHaveBeenCalled();
  62. $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled();
  63. $publish->shouldNotHaveBeenCalled();
  64. }
  65. /** @test */
  66. public function notificationsAreSentWhenVisitIsFound(): void
  67. {
  68. $visitId = '123';
  69. $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance());
  70. $update = new Update('', '');
  71. $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit);
  72. $logWarning = $this->logger->warning(Argument::cetera());
  73. $logDebug = $this->logger->debug(Argument::cetera());
  74. $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update);
  75. $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update);
  76. $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update);
  77. $publish = $this->publisher->__invoke($update);
  78. ($this->listener)(new VisitLocated($visitId));
  79. $findVisit->shouldHaveBeenCalledOnce();
  80. $logWarning->shouldNotHaveBeenCalled();
  81. $logDebug->shouldNotHaveBeenCalled();
  82. $buildNewShortUrlVisitUpdate->shouldHaveBeenCalledOnce();
  83. $buildNewVisitUpdate->shouldHaveBeenCalledOnce();
  84. $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled();
  85. $publish->shouldHaveBeenCalledTimes(2);
  86. }
  87. /** @test */
  88. public function debugIsLoggedWhenExceptionIsThrown(): void
  89. {
  90. $visitId = '123';
  91. $visit = Visit::forValidShortUrl(ShortUrl::createEmpty(), Visitor::emptyInstance());
  92. $update = new Update('', '');
  93. $e = new RuntimeException('Error');
  94. $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit);
  95. $logWarning = $this->logger->warning(Argument::cetera());
  96. $logDebug = $this->logger->debug('Error while trying to notify mercure hub with new visit. {e}', [
  97. 'e' => $e,
  98. ]);
  99. $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update);
  100. $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update);
  101. $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update);
  102. $publish = $this->publisher->__invoke($update)->willThrow($e);
  103. ($this->listener)(new VisitLocated($visitId));
  104. $findVisit->shouldHaveBeenCalledOnce();
  105. $logWarning->shouldNotHaveBeenCalled();
  106. $logDebug->shouldHaveBeenCalledOnce();
  107. $buildNewShortUrlVisitUpdate->shouldHaveBeenCalledOnce();
  108. $buildNewVisitUpdate->shouldHaveBeenCalledOnce();
  109. $buildNewOrphanVisitUpdate->shouldNotHaveBeenCalled();
  110. $publish->shouldHaveBeenCalledOnce();
  111. }
  112. /**
  113. * @test
  114. * @dataProvider provideOrphanVisits
  115. */
  116. public function notificationsAreSentForOrphanVisits(Visit $visit): void
  117. {
  118. $visitId = '123';
  119. $update = new Update('', '');
  120. $findVisit = $this->em->find(Visit::class, $visitId)->willReturn($visit);
  121. $logWarning = $this->logger->warning(Argument::cetera());
  122. $logDebug = $this->logger->debug(Argument::cetera());
  123. $buildNewShortUrlVisitUpdate = $this->updatesGenerator->newShortUrlVisitUpdate($visit)->willReturn($update);
  124. $buildNewOrphanVisitUpdate = $this->updatesGenerator->newOrphanVisitUpdate($visit)->willReturn($update);
  125. $buildNewVisitUpdate = $this->updatesGenerator->newVisitUpdate($visit)->willReturn($update);
  126. $publish = $this->publisher->__invoke($update);
  127. ($this->listener)(new VisitLocated($visitId));
  128. $findVisit->shouldHaveBeenCalledOnce();
  129. $logWarning->shouldNotHaveBeenCalled();
  130. $logDebug->shouldNotHaveBeenCalled();
  131. $buildNewShortUrlVisitUpdate->shouldNotHaveBeenCalled();
  132. $buildNewVisitUpdate->shouldNotHaveBeenCalled();
  133. $buildNewOrphanVisitUpdate->shouldHaveBeenCalledOnce();
  134. $publish->shouldHaveBeenCalledOnce();
  135. }
  136. public function provideOrphanVisits(): iterable
  137. {
  138. $visitor = Visitor::emptyInstance();
  139. yield Visit::TYPE_REGULAR_404 => [Visit::forRegularNotFound($visitor)];
  140. yield Visit::TYPE_INVALID_SHORT_URL => [Visit::forInvalidShortUrl($visitor)];
  141. yield Visit::TYPE_BASE_URL => [Visit::forBasePath($visitor)];
  142. }
  143. }