mirror of
https://github.com/shlinkio/shlink.git
synced 2026-03-11 09:43:13 +08:00
Merge pull request #2454 from acelaya-forks/feature/real-time-updates-options
Allow individual real-time updates topics to be enabled
This commit is contained in:
@@ -47,7 +47,7 @@
|
||||
"shlinkio/shlink-config": "^4.0",
|
||||
"shlinkio/shlink-event-dispatcher": "^4.2",
|
||||
"shlinkio/shlink-importer": "^5.6",
|
||||
"shlinkio/shlink-installer": "^9.5",
|
||||
"shlinkio/shlink-installer": "dev-develop#c5a8523 as 9.6",
|
||||
"shlinkio/shlink-ip-geolocation": "^4.3",
|
||||
"shlinkio/shlink-json": "^1.2",
|
||||
"spiral/roadrunner": "^2025.1",
|
||||
|
||||
@@ -76,6 +76,7 @@ return [
|
||||
Option\Matomo\MatomoBaseUrlConfigOption::class,
|
||||
Option\Matomo\MatomoSiteIdConfigOption::class,
|
||||
Option\Matomo\MatomoApiTokenConfigOption::class,
|
||||
Option\RealTimeUpdates\RealTimeUpdatesTopicsConfigOption::class,
|
||||
],
|
||||
|
||||
'installation_commands' => [
|
||||
|
||||
@@ -36,6 +36,7 @@ return [
|
||||
Config\Options\QrCodeOptions::class => [Config\Options\QrCodeOptions::class, 'fromEnv'],
|
||||
Config\Options\RabbitMqOptions::class => [Config\Options\RabbitMqOptions::class, 'fromEnv'],
|
||||
Config\Options\RobotsOptions::class => [Config\Options\RobotsOptions::class, 'fromEnv'],
|
||||
Config\Options\RealTimeUpdatesOptions::class => [Config\Options\RealTimeUpdatesOptions::class, 'fromEnv'],
|
||||
|
||||
RedirectRule\ShortUrlRedirectRuleService::class => ConfigAbstractFactory::class,
|
||||
RedirectRule\ShortUrlRedirectionResolver::class => ConfigAbstractFactory::class,
|
||||
|
||||
@@ -110,18 +110,21 @@ return (static function (): array {
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
],
|
||||
EventDispatcher\Mercure\NotifyNewShortUrlToMercure::class => [
|
||||
MercureHubPublishingHelper::class,
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
],
|
||||
EventDispatcher\RabbitMq\NotifyVisitToRabbitMq::class => [
|
||||
RabbitMqPublishingHelper::class,
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
Config\Options\RabbitMqOptions::class,
|
||||
],
|
||||
EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq::class => [
|
||||
@@ -129,6 +132,7 @@ return (static function (): array {
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
Config\Options\RabbitMqOptions::class,
|
||||
],
|
||||
EventDispatcher\RedisPubSub\NotifyVisitToRedis::class => [
|
||||
@@ -136,6 +140,7 @@ return (static function (): array {
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
'config.redis.pub_sub_enabled',
|
||||
],
|
||||
EventDispatcher\RedisPubSub\NotifyNewShortUrlToRedis::class => [
|
||||
@@ -143,6 +148,7 @@ return (static function (): array {
|
||||
EventDispatcher\PublishingUpdatesGenerator::class,
|
||||
'em',
|
||||
'Logger_Shlink',
|
||||
Config\Options\RealTimeUpdatesOptions::class,
|
||||
'config.redis.pub_sub_enabled',
|
||||
],
|
||||
|
||||
|
||||
@@ -255,14 +255,36 @@ function toProblemDetailsType(string $errorCode): string
|
||||
* @return string[]
|
||||
*/
|
||||
function enumValues(string $enum): array
|
||||
{
|
||||
return enumSide($enum, 'value');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<BackedEnum> $enum
|
||||
* @return string[]
|
||||
*/
|
||||
function enumNames(string $enum): array
|
||||
{
|
||||
return enumSide($enum, 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param class-string<BackedEnum> $enum
|
||||
* @param 'name'|'value' $type
|
||||
* @return string[]
|
||||
*/
|
||||
function enumSide(string $enum, string $type): array
|
||||
{
|
||||
static $cache;
|
||||
if ($cache === null) {
|
||||
$cache = [];
|
||||
}
|
||||
|
||||
return $cache[$enum] ?? (
|
||||
$cache[$enum] = array_map(static fn (BackedEnum $type) => (string) $type->value, $enum::cases())
|
||||
return $cache[$type][$enum] ?? (
|
||||
$cache[$type][$enum] = array_map(
|
||||
static fn (BackedEnum $entry) => (string) ($type === 'name' ? $entry->name : $entry->value),
|
||||
$enum::cases(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -85,6 +85,7 @@ enum EnvVars: string
|
||||
case MEMORY_LIMIT = 'MEMORY_LIMIT';
|
||||
case INITIAL_API_KEY = 'INITIAL_API_KEY';
|
||||
case SKIP_INITIAL_GEOLITE_DOWNLOAD = 'SKIP_INITIAL_GEOLITE_DOWNLOAD';
|
||||
case REAL_TIME_UPDATES_TOPICS = 'REAL_TIME_UPDATES_TOPICS';
|
||||
|
||||
/** @deprecated Use REDIRECT_EXTRA_PATH */
|
||||
case REDIRECT_APPEND_EXTRA_PATH = 'REDIRECT_APPEND_EXTRA_PATH';
|
||||
|
||||
63
module/Core/src/Config/Options/RealTimeUpdatesOptions.php
Normal file
63
module/Core/src/Config/Options/RealTimeUpdatesOptions.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\Config\Options;
|
||||
|
||||
use Shlinkio\Shlink\Core\Config\EnvVars;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Topic;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
|
||||
use function count;
|
||||
use function implode;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\contains;
|
||||
use function Shlinkio\Shlink\Core\ArrayUtils\map;
|
||||
use function Shlinkio\Shlink\Core\splitByComma;
|
||||
use function sprintf;
|
||||
|
||||
final readonly class RealTimeUpdatesOptions
|
||||
{
|
||||
/** @var string[] */
|
||||
public array $enabledTopics;
|
||||
|
||||
public function __construct(array|null $enabledTopics = null)
|
||||
{
|
||||
$validTopics = Topic::allTopicNames();
|
||||
$this->enabledTopics = $enabledTopics === null ? $validTopics : self::validateTopics(
|
||||
$enabledTopics,
|
||||
$validTopics,
|
||||
);
|
||||
}
|
||||
|
||||
public static function fromEnv(): self
|
||||
{
|
||||
$enabledTopics = splitByComma(EnvVars::REAL_TIME_UPDATES_TOPICS->loadFromEnv());
|
||||
return new self(enabledTopics: count($enabledTopics) === 0 ? null : $enabledTopics);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string[] $validTopics
|
||||
* @return string[]
|
||||
*/
|
||||
private static function validateTopics(array $providedTopics, array $validTopics): array
|
||||
{
|
||||
return map($providedTopics, function (string $topic) use ($validTopics): string {
|
||||
if (contains($topic, $validTopics)) {
|
||||
return $topic;
|
||||
}
|
||||
|
||||
throw ValidationException::fromArray([
|
||||
'topic' => sprintf(
|
||||
'Real-time updates topic "%s" is not valid. Expected one of ["%s"].',
|
||||
$topic,
|
||||
implode('", "', $validTopics),
|
||||
),
|
||||
]);
|
||||
});
|
||||
}
|
||||
|
||||
public function isTopicEnabled(Topic $topic): bool
|
||||
{
|
||||
return contains($topic->name, $this->enabledTopics);
|
||||
}
|
||||
}
|
||||
@@ -7,8 +7,10 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\Async;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Topic;
|
||||
use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
use Throwable;
|
||||
|
||||
@@ -19,6 +21,7 @@ abstract class AbstractNotifyNewShortUrlListener extends AbstractAsyncListener
|
||||
private readonly PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -40,6 +43,10 @@ abstract class AbstractNotifyNewShortUrlListener extends AbstractAsyncListener
|
||||
return;
|
||||
}
|
||||
|
||||
if (! $this->realTimeUpdatesOptions->isTopicEnabled(Topic::NEW_SHORT_URL)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$this->publishingHelper->publishUpdate($this->updatesGenerator->newShortUrlUpdate($shortUrl));
|
||||
} catch (Throwable $e) {
|
||||
|
||||
@@ -8,8 +8,10 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Topic;
|
||||
use Shlinkio\Shlink\Core\Visit\Entity\Visit;
|
||||
use Throwable;
|
||||
|
||||
@@ -22,6 +24,7 @@ abstract class AbstractNotifyVisitListener extends AbstractAsyncListener
|
||||
private readonly PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
private readonly EntityManagerInterface $em,
|
||||
private readonly LoggerInterface $logger,
|
||||
private readonly RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
) {
|
||||
}
|
||||
|
||||
@@ -61,12 +64,19 @@ abstract class AbstractNotifyVisitListener extends AbstractAsyncListener
|
||||
protected function determineUpdatesForVisit(Visit $visit): array
|
||||
{
|
||||
if ($visit->isOrphan()) {
|
||||
return [$this->updatesGenerator->newOrphanVisitUpdate($visit)];
|
||||
return $this->realTimeUpdatesOptions->isTopicEnabled(Topic::NEW_ORPHAN_VISIT)
|
||||
? [$this->updatesGenerator->newOrphanVisitUpdate($visit)]
|
||||
: [];
|
||||
}
|
||||
|
||||
return [
|
||||
$this->updatesGenerator->newShortUrlVisitUpdate($visit),
|
||||
$this->updatesGenerator->newVisitUpdate($visit),
|
||||
];
|
||||
$topics = [];
|
||||
if ($this->realTimeUpdatesOptions->isTopicEnabled(Topic::NEW_SHORT_URL_VISIT)) {
|
||||
$topics[] = $this->updatesGenerator->newShortUrlVisitUpdate($visit);
|
||||
}
|
||||
if ($this->realTimeUpdatesOptions->isTopicEnabled(Topic::NEW_VISIT)) {
|
||||
$topics[] = $this->updatesGenerator->newVisitUpdate($visit);
|
||||
}
|
||||
|
||||
return $topics;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RabbitMqOptions;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\AbstractNotifyNewShortUrlListener;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\RemoteSystem;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -19,9 +20,10 @@ class NotifyNewShortUrlToRabbitMq extends AbstractNotifyNewShortUrlListener
|
||||
PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
EntityManagerInterface $em,
|
||||
LoggerInterface $logger,
|
||||
RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
private readonly RabbitMqOptions $options,
|
||||
) {
|
||||
parent::__construct($rabbitMqHelper, $updatesGenerator, $em, $logger);
|
||||
parent::__construct($rabbitMqHelper, $updatesGenerator, $em, $logger, $realTimeUpdatesOptions);
|
||||
}
|
||||
|
||||
protected function isEnabled(): bool
|
||||
|
||||
@@ -8,6 +8,7 @@ use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RabbitMqOptions;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\AbstractNotifyVisitListener;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\RemoteSystem;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -19,9 +20,10 @@ class NotifyVisitToRabbitMq extends AbstractNotifyVisitListener
|
||||
PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
EntityManagerInterface $em,
|
||||
LoggerInterface $logger,
|
||||
RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
private readonly RabbitMqOptions $options,
|
||||
) {
|
||||
parent::__construct($rabbitMqHelper, $updatesGenerator, $em, $logger);
|
||||
parent::__construct($rabbitMqHelper, $updatesGenerator, $em, $logger, $realTimeUpdatesOptions);
|
||||
}
|
||||
|
||||
protected function isEnabled(): bool
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\AbstractNotifyNewShortUrlListener;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\RemoteSystem;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -18,9 +19,10 @@ class NotifyNewShortUrlToRedis extends AbstractNotifyNewShortUrlListener
|
||||
PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
EntityManagerInterface $em,
|
||||
LoggerInterface $logger,
|
||||
RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
private readonly bool $enabled,
|
||||
) {
|
||||
parent::__construct($redisHelper, $updatesGenerator, $em, $logger);
|
||||
parent::__construct($redisHelper, $updatesGenerator, $em, $logger, $realTimeUpdatesOptions);
|
||||
}
|
||||
|
||||
protected function isEnabled(): bool
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\AbstractNotifyVisitListener;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Async\RemoteSystem;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -18,9 +19,10 @@ class NotifyVisitToRedis extends AbstractNotifyVisitListener
|
||||
PublishingUpdatesGeneratorInterface $updatesGenerator,
|
||||
EntityManagerInterface $em,
|
||||
LoggerInterface $logger,
|
||||
RealTimeUpdatesOptions $realTimeUpdatesOptions,
|
||||
private readonly bool $enabled,
|
||||
) {
|
||||
parent::__construct($redisHelper, $updatesGenerator, $em, $logger);
|
||||
parent::__construct($redisHelper, $updatesGenerator, $em, $logger, $realTimeUpdatesOptions);
|
||||
}
|
||||
|
||||
protected function isEnabled(): bool
|
||||
|
||||
@@ -4,16 +4,23 @@ declare(strict_types=1);
|
||||
|
||||
namespace Shlinkio\Shlink\Core\EventDispatcher;
|
||||
|
||||
use function Shlinkio\Shlink\Core\enumNames;
|
||||
use function sprintf;
|
||||
|
||||
enum Topic: string
|
||||
{
|
||||
case NEW_VISIT = 'https://shlink.io/new-visit';
|
||||
case NEW_SHORT_URL_VISIT = 'https://shlink.io/new-visit/%s';
|
||||
case NEW_ORPHAN_VISIT = 'https://shlink.io/new-orphan-visit';
|
||||
case NEW_SHORT_URL = 'https://shlink.io/new-short-url';
|
||||
|
||||
public static function newShortUrlVisit(string|null $shortCode): string
|
||||
{
|
||||
return sprintf('%s/%s', self::NEW_VISIT->value, $shortCode ?? '');
|
||||
return sprintf(self::NEW_SHORT_URL_VISIT->value, $shortCode ?? '');
|
||||
}
|
||||
|
||||
public static function allTopicNames(): array
|
||||
{
|
||||
return enumNames(self::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
|
||||
namespace Config\Options;
|
||||
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Topic;
|
||||
use Shlinkio\Shlink\Core\Exception\ValidationException;
|
||||
|
||||
class RealTimeUpdatesOptionsTest extends TestCase
|
||||
{
|
||||
#[Test]
|
||||
#[TestWith([null, ['NEW_VISIT', 'NEW_SHORT_URL_VISIT', 'NEW_ORPHAN_VISIT', 'NEW_SHORT_URL']])]
|
||||
#[TestWith([['NEW_VISIT'], ['NEW_VISIT']])]
|
||||
#[TestWith([['NEW_SHORT_URL_VISIT', 'NEW_ORPHAN_VISIT'], ['NEW_SHORT_URL_VISIT', 'NEW_ORPHAN_VISIT']])]
|
||||
public function expectedTopicsAreResolved(array|null $providedTopics, array $expectedTopics): void
|
||||
{
|
||||
$options = new RealTimeUpdatesOptions($providedTopics);
|
||||
self::assertEquals($expectedTopics, $options->enabledTopics);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function exceptionIsThrownIfAnyProvidedTopicIsInvalid(): void
|
||||
{
|
||||
$this->expectException(ValidationException::class);
|
||||
new RealTimeUpdatesOptions(['NEW_SHORT_URL_VISIT', 'invalid']);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function checkingIfTopicIsEnabledWorks(): void
|
||||
{
|
||||
$options = new RealTimeUpdatesOptions(['NEW_ORPHAN_VISIT', 'NEW_SHORT_URL']);
|
||||
|
||||
self::assertTrue($options->isTopicEnabled(Topic::NEW_ORPHAN_VISIT));
|
||||
self::assertTrue($options->isTopicEnabled(Topic::NEW_SHORT_URL));
|
||||
self::assertFalse($options->isTopicEnabled(Topic::NEW_VISIT));
|
||||
self::assertFalse($options->isTopicEnabled(Topic::NEW_SHORT_URL_VISIT));
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Mercure\NotifyNewShortUrlToMercure;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -19,7 +20,6 @@ use Shlinkio\Shlink\Core\ShortUrl\Entity\ShortUrl;
|
||||
|
||||
class NotifyNewShortUrlToMercureTest extends TestCase
|
||||
{
|
||||
private NotifyNewShortUrlToMercure $listener;
|
||||
private MockObject & PublishingHelperInterface $helper;
|
||||
private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator;
|
||||
private MockObject & EntityManagerInterface $em;
|
||||
@@ -31,13 +31,6 @@ class NotifyNewShortUrlToMercureTest extends TestCase
|
||||
$this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class);
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->listener = new NotifyNewShortUrlToMercure(
|
||||
$this->helper,
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -52,7 +45,7 @@ class NotifyNewShortUrlToMercureTest extends TestCase
|
||||
);
|
||||
$this->logger->expects($this->never())->method('debug');
|
||||
|
||||
($this->listener)(new ShortUrlCreated('123'));
|
||||
$this->listener()(new ShortUrlCreated('123'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -69,7 +62,7 @@ class NotifyNewShortUrlToMercureTest extends TestCase
|
||||
$this->logger->expects($this->never())->method('warning');
|
||||
$this->logger->expects($this->never())->method('debug');
|
||||
|
||||
($this->listener)(new ShortUrlCreated('123'));
|
||||
$this->listener()(new ShortUrlCreated('123'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -93,6 +86,30 @@ class NotifyNewShortUrlToMercureTest extends TestCase
|
||||
['e' => $e, 'name' => 'Mercure'],
|
||||
);
|
||||
|
||||
($this->listener)(new ShortUrlCreated('123'));
|
||||
$this->listener()(new ShortUrlCreated('123'));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function publishingIsSkippedIfNewShortUrlTopicIsNotEnabled(): void
|
||||
{
|
||||
$shortUrl = ShortUrl::withLongUrl('https://longUrl');
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
|
||||
$this->em->expects($this->once())->method('find')->with(ShortUrl::class, '123')->willReturn($shortUrl);
|
||||
$this->updatesGenerator->expects($this->never())->method('newShortUrlUpdate');
|
||||
$this->helper->expects($this->never())->method('publishUpdate');
|
||||
|
||||
$this->listener(enableShortUrlTopic: false)(new ShortUrlCreated('123'));
|
||||
}
|
||||
|
||||
private function listener(bool $enableShortUrlTopic = true): NotifyNewShortUrlToMercure
|
||||
{
|
||||
return new NotifyNewShortUrlToMercure(
|
||||
$this->helper,
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(enabledTopics: $enableShortUrlTopic ? null : []),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,12 +7,14 @@ namespace ShlinkioTest\Shlink\Core\EventDispatcher\Mercure;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\Attributes\TestWith;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Mercure\NotifyVisitToMercure;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
@@ -23,7 +25,6 @@ use Shlinkio\Shlink\Core\Visit\Model\VisitType;
|
||||
|
||||
class NotifyVisitToMercureTest extends TestCase
|
||||
{
|
||||
private NotifyVisitToMercure $listener;
|
||||
private MockObject & PublishingHelperInterface $helper;
|
||||
private MockObject & PublishingUpdatesGeneratorInterface $updatesGenerator;
|
||||
private MockObject & EntityManagerInterface $em;
|
||||
@@ -35,8 +36,6 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
$this->updatesGenerator = $this->createMock(PublishingUpdatesGeneratorInterface::class);
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
|
||||
$this->listener = new NotifyVisitToMercure($this->helper, $this->updatesGenerator, $this->em, $this->logger);
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -54,11 +53,15 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
$this->updatesGenerator->expects($this->never())->method('newVisitUpdate');
|
||||
$this->helper->expects($this->never())->method('publishUpdate');
|
||||
|
||||
($this->listener)(new UrlVisited($visitId));
|
||||
$this->listener()(new UrlVisited($visitId));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function notificationsAreSentWhenVisitIsFound(): void
|
||||
#[TestWith([2, ['NEW_SHORT_URL_VISIT', 'NEW_VISIT']])]
|
||||
#[TestWith([1, ['NEW_VISIT']])]
|
||||
#[TestWith([1, ['NEW_SHORT_URL_VISIT']])]
|
||||
#[TestWith([0, []])]
|
||||
public function notificationsAreSentWhenVisitIsFound(int $publishUpdateCalls, array $enabledTopics): void
|
||||
{
|
||||
$visitId = '123';
|
||||
$visit = Visit::forValidShortUrl(ShortUrl::createFake(), Visitor::empty());
|
||||
@@ -67,14 +70,12 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
$this->em->expects($this->once())->method('find')->with(Visit::class, $visitId)->willReturn($visit);
|
||||
$this->logger->expects($this->never())->method('warning');
|
||||
$this->logger->expects($this->never())->method('debug');
|
||||
$this->updatesGenerator->expects($this->once())->method('newShortUrlVisitUpdate')->with($visit)->willReturn(
|
||||
$update,
|
||||
);
|
||||
$this->updatesGenerator->method('newShortUrlVisitUpdate')->willReturn($update);
|
||||
$this->updatesGenerator->expects($this->never())->method('newOrphanVisitUpdate');
|
||||
$this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with($visit)->willReturn($update);
|
||||
$this->helper->expects($this->exactly(2))->method('publishUpdate')->with($update);
|
||||
$this->updatesGenerator->method('newVisitUpdate')->willReturn($update);
|
||||
$this->helper->expects($this->exactly($publishUpdateCalls))->method('publishUpdate')->with($update);
|
||||
|
||||
($this->listener)(new UrlVisited($visitId));
|
||||
$this->listener(enabledTopics: $enabledTopics)(new UrlVisited($visitId));
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -98,12 +99,15 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
$this->updatesGenerator->expects($this->once())->method('newVisitUpdate')->with($visit)->willReturn($update);
|
||||
$this->helper->expects($this->once())->method('publishUpdate')->with($update)->willThrowException($e);
|
||||
|
||||
($this->listener)(new UrlVisited($visitId));
|
||||
$this->listener()(new UrlVisited($visitId));
|
||||
}
|
||||
|
||||
#[Test, DataProvider('provideOrphanVisits')]
|
||||
public function notificationsAreSentForOrphanVisits(Visit $visit): void
|
||||
{
|
||||
public function notificationsAreSentForOrphanVisits(
|
||||
Visit $visit,
|
||||
array $enabledTopics,
|
||||
int $publishUpdateCalls,
|
||||
): void {
|
||||
$visitId = '123';
|
||||
$update = Update::forTopicAndPayload('', []);
|
||||
|
||||
@@ -111,21 +115,31 @@ class NotifyVisitToMercureTest extends TestCase
|
||||
$this->logger->expects($this->never())->method('warning');
|
||||
$this->logger->expects($this->never())->method('debug');
|
||||
$this->updatesGenerator->expects($this->never())->method('newShortUrlVisitUpdate');
|
||||
$this->updatesGenerator->expects($this->once())->method('newOrphanVisitUpdate')->with($visit)->willReturn(
|
||||
$update,
|
||||
);
|
||||
$this->updatesGenerator->method('newOrphanVisitUpdate')->willReturn($update);
|
||||
$this->updatesGenerator->expects($this->never())->method('newVisitUpdate');
|
||||
$this->helper->expects($this->once())->method('publishUpdate')->with($update);
|
||||
$this->helper->expects($this->exactly($publishUpdateCalls))->method('publishUpdate')->with($update);
|
||||
|
||||
($this->listener)(new UrlVisited($visitId));
|
||||
$this->listener(enabledTopics: $enabledTopics)(new UrlVisited($visitId));
|
||||
}
|
||||
|
||||
public static function provideOrphanVisits(): iterable
|
||||
{
|
||||
$visitor = Visitor::empty();
|
||||
|
||||
yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor)];
|
||||
yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor)];
|
||||
yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor)];
|
||||
yield VisitType::REGULAR_404->value => [Visit::forRegularNotFound($visitor), ['NEW_ORPHAN_VISIT'], 1];
|
||||
yield VisitType::INVALID_SHORT_URL->value => [Visit::forInvalidShortUrl($visitor), ['NEW_ORPHAN_VISIT'], 1];
|
||||
yield VisitType::BASE_URL->value => [Visit::forBasePath($visitor), ['NEW_ORPHAN_VISIT'], 1];
|
||||
yield VisitType::BASE_URL->value . ' disabled' => [Visit::forBasePath($visitor), [], 0];
|
||||
}
|
||||
|
||||
private function listener(array|null $enabledTopics = null): NotifyVisitToMercure
|
||||
{
|
||||
return new NotifyVisitToMercure(
|
||||
$this->helper,
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(enabledTopics: $enabledTopics),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ use RuntimeException;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RabbitMqOptions;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyNewShortUrlToRabbitMq;
|
||||
@@ -115,6 +116,7 @@ class NotifyNewShortUrlToRabbitMqTest extends TestCase
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(),
|
||||
new RabbitMqOptions($enabled),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ use RuntimeException;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RabbitMqOptions;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\RabbitMq\NotifyVisitToRabbitMq;
|
||||
@@ -189,6 +190,7 @@ class NotifyVisitToRabbitMqTest extends TestCase
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(),
|
||||
$options ?? new RabbitMqOptions(enabled: true),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\ShortUrlCreated;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub\NotifyNewShortUrlToRedis;
|
||||
@@ -77,6 +78,13 @@ class NotifyNewShortUrlToRedisTest extends TestCase
|
||||
|
||||
private function createListener(bool $enabled = true): NotifyNewShortUrlToRedis
|
||||
{
|
||||
return new NotifyNewShortUrlToRedis($this->helper, $this->updatesGenerator, $this->em, $this->logger, $enabled);
|
||||
return new NotifyNewShortUrlToRedis(
|
||||
$this->helper,
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(),
|
||||
$enabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ use Psr\Log\LoggerInterface;
|
||||
use RuntimeException;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\PublishingHelperInterface;
|
||||
use Shlinkio\Shlink\Common\UpdatePublishing\Update;
|
||||
use Shlinkio\Shlink\Core\Config\Options\RealTimeUpdatesOptions;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\Event\UrlVisited;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\PublishingUpdatesGeneratorInterface;
|
||||
use Shlinkio\Shlink\Core\EventDispatcher\RedisPubSub\NotifyVisitToRedis;
|
||||
@@ -76,6 +77,13 @@ class NotifyVisitToRedisTest extends TestCase
|
||||
|
||||
private function createListener(bool $enabled = true): NotifyVisitToRedis
|
||||
{
|
||||
return new NotifyVisitToRedis($this->helper, $this->updatesGenerator, $this->em, $this->logger, $enabled);
|
||||
return new NotifyVisitToRedis(
|
||||
$this->helper,
|
||||
$this->updatesGenerator,
|
||||
$this->em,
|
||||
$this->logger,
|
||||
new RealTimeUpdatesOptions(),
|
||||
$enabled,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user