diff --git a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php
index fb5453cf..bb332d82 100644
--- a/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php
+++ b/module/CLI/src/Command/ShortUrl/CreateShortUrlCommand.php
@@ -176,6 +176,11 @@ class CreateShortUrlCommand extends Command
ShortUrlInputFilter::FORWARD_QUERY => !$input->getOption('no-forward-query'),
], $this->options));
+ $result->onEventDispatchingError(static fn () => $io->isVerbose() && $io->warning(
+ 'Short URL properly created, but the real-time updates cannot be notified when generating the '
+ . 'short URL from the command line. Migrate to roadrunner in order to bypass this limitation.',
+ ));
+
$io->writeln([
sprintf('Processed long URL: %s', $longUrl),
sprintf('Generated short URL: %s', $this->stringifier->stringify($result->shortUrl)),
diff --git a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php
index f8b0e2d3..28e3ec21 100644
--- a/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php
+++ b/module/CLI/test/Command/ShortUrl/CreateShortUrlCommandTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace ShlinkioTest\Shlink\CLI\Command\ShortUrl;
+use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use PHPUnit\Framework\Assert;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
@@ -20,6 +21,7 @@ use Shlinkio\Shlink\Core\ShortUrl\Model\ShortUrlCreation;
use Shlinkio\Shlink\Core\ShortUrl\Model\UrlShorteningResult;
use Shlinkio\Shlink\Core\ShortUrl\UrlShortenerInterface;
use ShlinkioTest\Shlink\CLI\CliTestUtilsTrait;
+use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Tester\CommandTester;
class CreateShortUrlCommandTest extends TestCase
@@ -62,11 +64,12 @@ class CreateShortUrlCommandTest extends TestCase
$this->commandTester->execute([
'longUrl' => 'http://domain.com/foo/bar',
'--max-visits' => '3',
- ]);
+ ], ['verbosity' => OutputInterface::VERBOSITY_VERBOSE]);
$output = $this->commandTester->getDisplay();
self::assertEquals(ExitCodes::EXIT_SUCCESS, $this->commandTester->getStatusCode());
self::assertStringContainsString('stringified_short_url', $output);
+ self::assertStringNotContainsString('but the real-time updates cannot be notified', $output);
}
#[Test]
@@ -170,4 +173,40 @@ class CreateShortUrlCommandTest extends TestCase
yield 'no flags' => [[], null];
yield 'validate-url' => [['--validate-url' => true], true];
}
+
+ /**
+ * @param callable(string $output): void $assert
+ */
+ #[Test, DataProvider('provideDispatchBehavior')]
+ public function warningIsPrintedInVerboseModeWhenDispatchErrors(int $verbosity, callable $assert): void
+ {
+ $shortUrl = ShortUrl::createFake();
+ $this->urlShortener->expects($this->once())->method('shorten')->withAnyParameters()->willReturn(
+ UrlShorteningResult::withErrorOnEventDispatching($shortUrl, new ServiceNotFoundException()),
+ );
+ $this->stringifier->method('stringify')->willReturn('stringified_short_url');
+
+ $this->commandTester->execute(['longUrl' => 'http://domain.com/foo/bar'], ['verbosity' => $verbosity]);
+ $output = $this->commandTester->getDisplay();
+
+ $assert($output);
+ }
+
+ public static function provideDispatchBehavior(): iterable
+ {
+ $containsAssertion = static fn (string $output) => self::assertStringContainsString(
+ 'but the real-time updates cannot be notified',
+ $output,
+ );
+ $doesNotContainAssertion = static fn (string $output) => self::assertStringNotContainsString(
+ 'but the real-time updates cannot be notified',
+ $output,
+ );
+
+ yield 'quiet' => [OutputInterface::VERBOSITY_QUIET, $doesNotContainAssertion];
+ yield 'normal' => [OutputInterface::VERBOSITY_NORMAL, $doesNotContainAssertion];
+ yield 'verbose' => [OutputInterface::VERBOSITY_VERBOSE, $containsAssertion];
+ yield 'very verbose' => [OutputInterface::VERBOSITY_VERY_VERBOSE, $containsAssertion];
+ yield 'debug' => [OutputInterface::VERBOSITY_DEBUG, $containsAssertion];
+ }
}
diff --git a/module/Core/src/ShortUrl/Model/UrlShorteningResult.php b/module/Core/src/ShortUrl/Model/UrlShorteningResult.php
index c6a95739..b9d4f993 100644
--- a/module/Core/src/ShortUrl/Model/UrlShorteningResult.php
+++ b/module/Core/src/ShortUrl/Model/UrlShorteningResult.php
@@ -16,7 +16,7 @@ final class UrlShorteningResult
}
/**
- * @param callable(Throwable $errorOnEventDispatching): void $handler
+ * @param callable(Throwable $errorOnEventDispatching): mixed $handler
*/
public function onEventDispatchingError(callable $handler): void
{
diff --git a/module/Core/test/ShortUrl/UrlShortenerTest.php b/module/Core/test/ShortUrl/UrlShortenerTest.php
index 247e6185..cf1691ac 100644
--- a/module/Core/test/ShortUrl/UrlShortenerTest.php
+++ b/module/Core/test/ShortUrl/UrlShortenerTest.php
@@ -6,6 +6,7 @@ namespace ShlinkioTest\Shlink\Core\ShortUrl;
use Cake\Chronos\Chronos;
use Doctrine\ORM\EntityManager;
+use Laminas\ServiceManager\Exception\ServiceNotFoundException;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\MockObject\MockObject;
@@ -26,6 +27,7 @@ class UrlShortenerTest extends TestCase
private MockObject & EntityManager $em;
private MockObject & ShortUrlTitleResolutionHelperInterface $titleResolutionHelper;
private MockObject & ShortCodeUniquenessHelperInterface $shortCodeHelper;
+ private MockObject & EventDispatcherInterface $dispatcher;
protected function setUp(): void
{
@@ -39,17 +41,19 @@ class UrlShortenerTest extends TestCase
fn (callable $callback) => $callback(),
);
+ $this->dispatcher = $this->createMock(EventDispatcherInterface::class);
+
$this->urlShortener = new UrlShortener(
$this->titleResolutionHelper,
$this->em,
new SimpleShortUrlRelationResolver(),
$this->shortCodeHelper,
- $this->createMock(EventDispatcherInterface::class),
+ $this->dispatcher,
);
}
- #[Test]
- public function urlIsProperlyShortened(): void
+ #[Test, DataProvider('provideDispatchBehavior')]
+ public function urlIsProperlyShortened(bool $expectDispatchError, callable $dispatchBehavior): void
{
$longUrl = 'http://foobar.com/12345/hello?foo=bar';
$meta = ShortUrlCreation::fromRawData(['longUrl' => $longUrl]);
@@ -57,10 +61,24 @@ class UrlShortenerTest extends TestCase
$meta,
)->willReturnArgument(0);
$this->shortCodeHelper->method('ensureShortCodeUniqueness')->willReturn(true);
+ $this->dispatcher->expects($this->once())->method('dispatch')->willReturnCallback($dispatchBehavior);
$result = $this->urlShortener->shorten($meta);
+ $thereIsError = false;
+ $result->onEventDispatchingError(function () use (&$thereIsError) {
+ $thereIsError = true;
+ });
self::assertEquals($longUrl, $result->shortUrl->getLongUrl());
+ self::assertEquals($expectDispatchError, $thereIsError);
+ }
+
+ public static function provideDispatchBehavior(): iterable
+ {
+ yield 'no dispatch error' => [false, static function (): void {}];
+ yield 'dispatch error' => [true, static function (): void {
+ throw new ServiceNotFoundException();
+ }];
}
#[Test]