minor #40671 [Notifier] Mercure bridge: bump mercure dependency to 0.5 (azjezz, mtarld)

This PR was merged into the 5.3-dev branch.

Discussion
----------

[Notifier] Mercure bridge: bump mercure dependency to 0.5

| Q             | A
| ------------- | ---
| Branch?       | 5.x
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| License       | MIT
| Doc PR        | https://github.com/symfony/symfony-docs/pull/15179

Bump Mercure bridge's `symfony/mercure` dependency to 0.5 to deal with hubs instead of publishers.

---
To be able to use `HandlerRegistry::all` method, this PR needs https://github.com/symfony/mercure/pull/50 to be merged and released.

Commits
-------

498f96f1a8 Drop support of mercure:^0.4
d3306fdc92 add support for symfony/mercure:^0.5
This commit is contained in:
Robin Chalas 2021-04-06 12:24:06 +02:00
commit de143497ff
8 changed files with 108 additions and 152 deletions

View File

@ -142,7 +142,7 @@
"psr/http-client": "^1.0", "psr/http-client": "^1.0",
"psr/simple-cache": "^1.0", "psr/simple-cache": "^1.0",
"egulias/email-validator": "^2.1.10|^3.1", "egulias/email-validator": "^2.1.10|^3.1",
"symfony/mercure-bundle": "^0.2", "symfony/mercure-bundle": "^0.3",
"symfony/phpunit-bridge": "^5.2", "symfony/phpunit-bridge": "^5.2",
"symfony/security-acl": "~2.8|~3.0", "symfony/security-acl": "~2.8|~3.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",

View File

@ -26,6 +26,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader; use Symfony\Bundle\FrameworkBundle\Routing\AnnotatedRouteControllerLoader;
use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface; use Symfony\Bundle\FrameworkBundle\Routing\RouteLoaderInterface;
use Symfony\Bundle\FullStack; use Symfony\Bundle\FullStack;
use Symfony\Bundle\MercureBundle\MercureBundle;
use Symfony\Component\Asset\PackageInterface; use Symfony\Component\Asset\PackageInterface;
use Symfony\Component\BrowserKit\AbstractBrowser; use Symfony\Component\BrowserKit\AbstractBrowser;
use Symfony\Component\Cache\Adapter\AdapterInterface; use Symfony\Component\Cache\Adapter\AdapterInterface;
@ -44,8 +45,6 @@ use Symfony\Component\Console\Application;
use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Command\Command;
use Symfony\Component\DependencyInjection\Alias; use Symfony\Component\DependencyInjection\Alias;
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument;
use Symfony\Component\DependencyInjection\Argument\TaggedIteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass; use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerBuilder;
@ -95,6 +94,7 @@ use Symfony\Component\Mailer\Bridge\Postmark\Transport\PostmarkTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory; use Symfony\Component\Mailer\Bridge\Sendgrid\Transport\SendgridTransportFactory;
use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory; use Symfony\Component\Mailer\Bridge\Sendinblue\Transport\SendinblueTransportFactory;
use Symfony\Component\Mailer\Mailer; use Symfony\Component\Mailer\Mailer;
use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory; use Symfony\Component\Messenger\Bridge\AmazonSqs\Transport\AmazonSqsTransportFactory;
use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory; use Symfony\Component\Messenger\Bridge\Amqp\Transport\AmqpTransportFactory;
use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory; use Symfony\Component\Messenger\Bridge\Beanstalkd\Transport\BeanstalkdTransportFactory;
@ -2408,7 +2408,7 @@ class FrameworkExtension extends Extension
if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) { if (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages) && ContainerBuilder::willBeAvailable('symfony/mercure-bundle', MercureBundle::class, $parentPackages)) {
$container->getDefinition($classToServices[MercureTransportFactory::class]) $container->getDefinition($classToServices[MercureTransportFactory::class])
->replaceArgument('$publisherLocator', new ServiceLocatorArgument(new TaggedIteratorArgument('mercure.publisher', null, null, true))); ->replaceArgument('$registry', new Reference(HubRegistry::class));
} elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) { } elseif (ContainerBuilder::willBeAvailable('symfony/mercure-notifier', MercureTransportFactory::class, $parentPackages)) {
$container->removeDefinition($classToServices[MercureTransportFactory::class]); $container->removeDefinition($classToServices[MercureTransportFactory::class]);
} }

View File

@ -11,20 +11,18 @@
namespace Symfony\Component\Notifier\Bridge\Mercure; namespace Symfony\Component\Notifier\Bridge\Mercure;
use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Exception\InvalidArgumentException;
use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Update; use Symfony\Component\Mercure\Update;
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\LogicException;
use Symfony\Component\Notifier\Exception\RuntimeException; use Symfony\Component\Notifier\Exception\RuntimeException;
use Symfony\Component\Notifier\Exception\TransportException;
use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException; use Symfony\Component\Notifier\Exception\UnsupportedMessageTypeException;
use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\SentMessage; use Symfony\Component\Notifier\Message\SentMessage;
use Symfony\Component\Notifier\Transport\AbstractTransport; use Symfony\Component\Notifier\Transport\AbstractTransport;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use Symfony\Contracts\HttpClient\Exception\ExceptionInterface;
use Symfony\Contracts\HttpClient\Exception\HttpExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
/** /**
@ -32,21 +30,21 @@ use Symfony\Contracts\HttpClient\HttpClientInterface;
*/ */
final class MercureTransport extends AbstractTransport final class MercureTransport extends AbstractTransport
{ {
private $publisher; private $hub;
private $publisherId; private $hubId;
private $topics; private $topics;
/** /**
* @param string|string[]|null $topics * @param string|string[]|null $topics
*/ */
public function __construct(PublisherInterface $publisher, string $publisherId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null) public function __construct(HubInterface $hub, string $hubId, $topics = null, ?HttpClientInterface $client = null, ?EventDispatcherInterface $dispatcher = null)
{ {
if (null !== $topics && !\is_array($topics) && !\is_string($topics)) { if (null !== $topics && !\is_array($topics) && !\is_string($topics)) {
throw new \TypeError(sprintf('"%s()" expects parameter 3 to be an array of strings, a string or null, "%s" given.', __METHOD__, get_debug_type($topics))); throw new \TypeError(sprintf('"%s()" expects parameter 3 to be an array of strings, a string or null, "%s" given.', __METHOD__, get_debug_type($topics)));
} }
$this->publisher = $publisher; $this->hub = $hub;
$this->publisherId = $publisherId; $this->hubId = $hubId;
$this->topics = $topics ?? 'https://symfony.com/notifier'; $this->topics = $topics ?? 'https://symfony.com/notifier';
parent::__construct($client, $dispatcher); parent::__construct($client, $dispatcher);
@ -54,7 +52,7 @@ final class MercureTransport extends AbstractTransport
public function __toString(): string public function __toString(): string
{ {
return sprintf('mercure://%s?%s', $this->publisherId, http_build_query(['topic' => $this->topics])); return sprintf('mercure://%s?%s', $this->hubId, http_build_query(['topic' => $this->topics]));
} }
public function supports(MessageInterface $message): bool public function supports(MessageInterface $message): bool
@ -87,18 +85,14 @@ final class MercureTransport extends AbstractTransport
]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry()); ]), $options->isPrivate(), $options->getId(), $options->getType(), $options->getRetry());
try { try {
$messageId = ($this->publisher)($update); $messageId = $this->hub->publish($update);
$sentMessage = new SentMessage($message, (string) $this); $sentMessage = new SentMessage($message, (string) $this);
$sentMessage->setMessageId($messageId); $sentMessage->setMessageId($messageId);
return $sentMessage; return $sentMessage;
} catch (HttpExceptionInterface $e) { } catch (MercureRuntimeException | InvalidArgumentException $e) {
throw new TransportException('Unable to post the Mercure message: '.$e->getResponse()->getContent(false), $e->getResponse(), $e->getCode(), $e);
} catch (ExceptionInterface $e) {
throw new RuntimeException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e); throw new RuntimeException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e);
} catch (\InvalidArgumentException $e) {
throw new InvalidArgumentException('Unable to post the Mercure message: '.$e->getMessage(), $e->getCode(), $e);
} }
} }
} }

View File

@ -11,30 +11,26 @@
namespace Symfony\Component\Notifier\Bridge\Mercure; namespace Symfony\Component\Notifier\Bridge\Mercure;
use Symfony\Bundle\MercureBundle\MercureBundle; use Symfony\Component\Mercure\Exception\InvalidArgumentException;
use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\HubRegistry;
use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\AbstractTransportFactory; use Symfony\Component\Notifier\Transport\AbstractTransportFactory;
use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportInterface; use Symfony\Component\Notifier\Transport\TransportInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
/** /**
* @author Mathias Arlaud <mathias.arlaud@gmail.com> * @author Mathias Arlaud <mathias.arlaud@gmail.com>
*/ */
final class MercureTransportFactory extends AbstractTransportFactory final class MercureTransportFactory extends AbstractTransportFactory
{ {
private $publisherLocator; private $registry;
/** public function __construct(HubRegistry $registry)
* @param ServiceProviderInterface $publisherLocator A container that holds {@see PublisherInterface} instances
*/
public function __construct(ServiceProviderInterface $publisherLocator)
{ {
parent::__construct(); parent::__construct();
$this->publisherLocator = $publisherLocator; $this->registry = $registry;
} }
/** /**
@ -46,18 +42,16 @@ final class MercureTransportFactory extends AbstractTransportFactory
throw new UnsupportedSchemeException($dsn, 'mercure', $this->getSupportedSchemes()); throw new UnsupportedSchemeException($dsn, 'mercure', $this->getSupportedSchemes());
} }
$publisherId = $dsn->getHost(); $hubId = $dsn->getHost();
if (!$this->publisherLocator->has($publisherId)) {
if (!class_exists(MercureBundle::class) && !$this->publisherLocator->getProvidedServices()) {
throw new LogicException('No publishers found. Did you forget to install the MercureBundle? Try running "composer require symfony/mercure-bundle".');
}
throw new LogicException(sprintf('"%s" not found. Did you mean one of: %s?', $publisherId, implode(', ', array_keys($this->publisherLocator->getProvidedServices()))));
}
$topic = $dsn->getOption('topic'); $topic = $dsn->getOption('topic');
return new MercureTransport($this->publisherLocator->get($publisherId), $publisherId, $topic); try {
$hub = $this->registry->getHub($hubId);
} catch (InvalidArgumentException $exception) {
throw new IncompleteDsnException(sprintf('Hub "%s" not found. Did you mean one of: "%s"?', $hubId, implode('", "', array_keys($this->registry->all()))));
}
return new MercureTransport($hub, $hubId, $topic);
} }
protected function getSupportedSchemes(): array protected function getSupportedSchemes(): array

View File

@ -7,11 +7,11 @@ DSN example
----------- -----------
``` ```
MERCURE_DSN=mercure://PUBLISHER_SERVICE_ID?topic=TOPIC MERCURE_DSN=mercure://HUB_ID?topic=TOPIC
``` ```
where: where:
- `PUBLISHER_SERVICE_ID` is the Mercure publisher service id - `HUB_ID` is the Mercure hub id
- `TOPIC` is the topic IRI (optional, default: `https://symfony.com/notifier`. Could be either a single topic: `topic=https://foo` or multiple topics: `topic[]=/foo/1&topic[]=https://bar`) - `TOPIC` is the topic IRI (optional, default: `https://symfony.com/notifier`. Could be either a single topic: `topic=https://foo` or multiple topics: `topic[]=/foo/1&topic[]=https://bar`)
Resources Resources

View File

@ -11,15 +11,13 @@
namespace Symfony\Component\Notifier\Bridge\Mercure\Tests; namespace Symfony\Component\Notifier\Bridge\Mercure\Tests;
use LogicException; use Symfony\Component\Mercure\HubInterface;
use Symfony\Bridge\PhpUnit\ClassExistsMock; use Symfony\Component\Mercure\HubRegistry;
use Symfony\Bundle\MercureBundle\MercureBundle;
use Symfony\Component\Mercure\PublisherInterface;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransportFactory;
use Symfony\Component\Notifier\Exception\IncompleteDsnException;
use Symfony\Component\Notifier\Test\TransportFactoryTestCase; use Symfony\Component\Notifier\Test\TransportFactoryTestCase;
use Symfony\Component\Notifier\Transport\Dsn; use Symfony\Component\Notifier\Transport\Dsn;
use Symfony\Component\Notifier\Transport\TransportFactoryInterface; use Symfony\Component\Notifier\Transport\TransportFactoryInterface;
use Symfony\Contracts\Service\ServiceProviderInterface;
/** /**
* @author Mathias Arlaud <mathias.arlaud@gmail.com> * @author Mathias Arlaud <mathias.arlaud@gmail.com>
@ -28,73 +26,49 @@ final class MercureTransportFactoryTest extends TransportFactoryTestCase
{ {
public function createFactory(): TransportFactoryInterface public function createFactory(): TransportFactoryInterface
{ {
$publisherLocator = $this->createMock(ServiceProviderInterface::class); $hub = $this->createMock(HubInterface::class);
$publisherLocator->method('has')->willReturn(true); $hubRegistry = new HubRegistry($hub, ['hubId' => $hub]);
$publisherLocator->method('get')->willReturn($this->createMock(PublisherInterface::class));
return new MercureTransportFactory($publisherLocator); return new MercureTransportFactory($hubRegistry);
} }
public function supportsProvider(): iterable public function supportsProvider(): iterable
{ {
yield [true, 'mercure://publisherId?topic=topic']; yield [true, 'mercure://hubId?topic=topic'];
yield [false, 'somethingElse://publisherId?topic=topic']; yield [false, 'somethingElse://hubId?topic=topic'];
} }
public function createProvider(): iterable public function createProvider(): iterable
{ {
yield [ yield [
'mercure://publisherId?topic=%2Ftopic%2F1', 'mercure://hubId?topic=%2Ftopic%2F1',
'mercure://publisherId?topic=/topic/1', 'mercure://hubId?topic=/topic/1',
]; ];
yield [ yield [
'mercure://publisherId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D=%2Ftopic%2F2', 'mercure://hubId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D=%2Ftopic%2F2',
'mercure://publisherId?topic[]=/topic/1&topic[]=/topic/2', 'mercure://hubId?topic[]=/topic/1&topic[]=/topic/2',
]; ];
yield [ yield [
'mercure://publisherId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', 'mercure://hubId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier',
'mercure://publisherId', 'mercure://hubId',
]; ];
} }
public function unsupportedSchemeProvider(): iterable public function unsupportedSchemeProvider(): iterable
{ {
yield ['somethingElse://publisherId?topic=topic']; yield ['somethingElse://hubId?topic=topic'];
} }
public function testCreateWithEmptyServiceProviderAndWithoutMercureBundleThrows() public function testNotFoundHubThrows()
{ {
ClassExistsMock::register(MercureTransportFactory::class); $hub = $this->createMock(HubInterface::class);
ClassExistsMock::withMockedClasses([MercureBundle::class => false]); $hubRegistry = new HubRegistry($hub, ['hubId' => $hub, 'anotherHubId' => $hub]);
$factory = new MercureTransportFactory($hubRegistry);
$publisherLocator = $this->createMock(ServiceProviderInterface::class); $this->expectException(IncompleteDsnException::class);
$publisherLocator->method('has')->willReturn(false); $this->expectExceptionMessage('Hub "wrongHubId" not found. Did you mean one of: "hubId", "anotherHubId"?');
$publisherLocator->method('getProvidedServices')->willReturn([]); $factory->create(new Dsn('mercure://wrongHubId'));
$factory = new MercureTransportFactory($publisherLocator);
$this->expectException(LogicException::class);
$this->expectExceptionMessage('No publishers found. Did you forget to install the MercureBundle? Try running "composer require symfony/mercure-bundle".');
try {
$factory->create(new Dsn('mercure://publisherId'));
} finally {
ClassExistsMock::withMockedClasses([MercureBundle::class => true]);
}
}
public function testNotFoundPublisherThrows()
{
$publisherLocator = $this->createMock(ServiceProviderInterface::class);
$publisherLocator->method('has')->willReturn(false);
$publisherLocator->method('getProvidedServices')->willReturn(['fooPublisher' => 'fooFqcn', 'barPublisher' => 'barFqcn']);
$factory = new MercureTransportFactory($publisherLocator);
$this->expectException(LogicException::class);
$this->expectExceptionMessage('"publisherId" not found. Did you mean one of: fooPublisher, barPublisher?');
$factory->create(new Dsn('mercure://publisherId'));
} }
} }

View File

@ -11,24 +11,23 @@
namespace Symfony\Component\Notifier\Bridge\Mercure\Tests; namespace Symfony\Component\Notifier\Bridge\Mercure\Tests;
use Symfony\Component\HttpClient\Exception\TransportException as HttpClientTransportException; use Symfony\Component\Mercure\Exception\InvalidArgumentException;
use Symfony\Component\Mercure\PublisherInterface; use Symfony\Component\Mercure\Exception\RuntimeException as MercureRuntimeException;
use Symfony\Component\Mercure\HubInterface;
use Symfony\Component\Mercure\Jwt\StaticTokenProvider;
use Symfony\Component\Mercure\MockHub;
use Symfony\Component\Mercure\Update; use Symfony\Component\Mercure\Update;
use Symfony\Component\Notifier\Bridge\Mercure\MercureOptions; use Symfony\Component\Notifier\Bridge\Mercure\MercureOptions;
use Symfony\Component\Notifier\Bridge\Mercure\MercureTransport; use Symfony\Component\Notifier\Bridge\Mercure\MercureTransport;
use Symfony\Component\Notifier\Exception\InvalidArgumentException;
use Symfony\Component\Notifier\Exception\LogicException; use Symfony\Component\Notifier\Exception\LogicException;
use Symfony\Component\Notifier\Exception\RuntimeException; use Symfony\Component\Notifier\Exception\RuntimeException;
use Symfony\Component\Notifier\Exception\TransportException;
use Symfony\Component\Notifier\Message\ChatMessage; use Symfony\Component\Notifier\Message\ChatMessage;
use Symfony\Component\Notifier\Message\MessageInterface; use Symfony\Component\Notifier\Message\MessageInterface;
use Symfony\Component\Notifier\Message\MessageOptionsInterface; use Symfony\Component\Notifier\Message\MessageOptionsInterface;
use Symfony\Component\Notifier\Message\SmsMessage; use Symfony\Component\Notifier\Message\SmsMessage;
use Symfony\Component\Notifier\Test\TransportTestCase; use Symfony\Component\Notifier\Test\TransportTestCase;
use Symfony\Component\Notifier\Transport\TransportInterface; use Symfony\Component\Notifier\Transport\TransportInterface;
use Symfony\Contracts\HttpClient\Exception\ServerExceptionInterface;
use Symfony\Contracts\HttpClient\HttpClientInterface; use Symfony\Contracts\HttpClient\HttpClientInterface;
use Symfony\Contracts\HttpClient\ResponseInterface;
use TypeError; use TypeError;
/** /**
@ -36,18 +35,18 @@ use TypeError;
*/ */
final class MercureTransportTest extends TransportTestCase final class MercureTransportTest extends TransportTestCase
{ {
public function createTransport(?HttpClientInterface $client = null, ?PublisherInterface $publisher = null, string $publisherId = 'publisherId', $topics = null): TransportInterface public function createTransport(?HttpClientInterface $client = null, ?HubInterface $hub = null, string $hubId = 'hubId', $topics = null): TransportInterface
{ {
$publisher = $publisher ?? $this->createMock(PublisherInterface::class); $hub = $hub ?? $this->createMock(HubInterface::class);
return new MercureTransport($publisher, $publisherId, $topics); return new MercureTransport($hub, $hubId, $topics);
} }
public function toStringProvider(): iterable public function toStringProvider(): iterable
{ {
yield ['mercure://publisherId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', $this->createTransport()]; yield ['mercure://hubId?topic=https%3A%2F%2Fsymfony.com%2Fnotifier', $this->createTransport()];
yield ['mercure://customPublisherId?topic=%2Ftopic', $this->createTransport(null, null, 'customPublisherId', '/topic')]; yield ['mercure://customHubId?topic=%2Ftopic', $this->createTransport(null, null, 'customHubId', '/topic')];
yield ['mercure://customPublisherId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D%5B0%5D=%2Ftopic%2F2', $this->createTransport(null, null, 'customPublisherId', ['/topic/1', ['/topic/2']])]; yield ['mercure://customHubId?topic%5B0%5D=%2Ftopic%2F1&topic%5B1%5D%5B0%5D=%2Ftopic%2F2', $this->createTransport(null, null, 'customHubId', ['/topic/1', ['/topic/2']])];
} }
public function supportedMessagesProvider(): iterable public function supportedMessagesProvider(): iterable
@ -90,87 +89,82 @@ final class MercureTransportTest extends TransportTestCase
public function testSendWithTransportFailureThrows() public function testSendWithTransportFailureThrows()
{ {
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), static function (): void {
$publisher->method('__invoke')->willThrowException(new HttpClientTransportException('Cannot connect to mercure')); throw new MercureRuntimeException('Cannot connect to mercure');
});
$this->expectException(RuntimeException::class); $this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Unable to post the Mercure message: Cannot connect to mercure'); $this->expectExceptionMessage('Unable to post the Mercure message: Cannot connect to mercure');
$this->createTransport(null, $publisher)->send(new ChatMessage('subject')); $this->createTransport(null, $hub)->send(new ChatMessage('subject'));
}
public function testSendWithWrongResponseThrows()
{
$response = $this->createMock(ResponseInterface::class);
$response->method('getContent')->willReturn('Service Unavailable');
$httpException = $this->createMock(ServerExceptionInterface::class);
$httpException->method('getResponse')->willReturn($response);
$publisher = $this->createMock(PublisherInterface::class);
$publisher->method('__invoke')->willThrowException($httpException);
$this->expectException(TransportException::class);
$this->expectExceptionMessage('Unable to post the Mercure message: Service Unavailable');
$this->createTransport(null, $publisher)->send(new ChatMessage('subject'));
} }
public function testSendWithWrongTokenThrows() public function testSendWithWrongTokenThrows()
{ {
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), static function (): void {
$publisher->method('__invoke')->willThrowException(new \InvalidArgumentException('The provided JWT is not valid')); throw new InvalidArgumentException('The provided JWT is not valid');
});
$this->expectException(InvalidArgumentException::class); $this->expectException(RuntimeException::class);
$this->expectExceptionMessage('Unable to post the Mercure message: The provided JWT is not valid'); $this->expectExceptionMessage('Unable to post the Mercure message: The provided JWT is not valid');
$this->createTransport(null, $publisher)->send(new ChatMessage('subject')); $this->createTransport(null, $hub)->send(new ChatMessage('subject'));
} }
public function testSendWithMercureOptions() public function testSendWithMercureOptions()
{ {
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string {
$publisher $this->assertSame(['/topic/1', '/topic/2'], $update->getTopics());
->expects($this->once()) $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData());
->method('__invoke') $this->assertSame('id', $update->getId());
->with(new Update(['/topic/1', '/topic/2'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', true, 'id', 'type', 1)) $this->assertSame('type', $update->getType());
; $this->assertSame(1, $update->getRetry());
$this->assertTrue($update->isPrivate());
$this->createTransport(null, $publisher)->send(new ChatMessage('subject', new MercureOptions(['/topic/1', '/topic/2'], true, 'id', 'type', 1))); return 'id';
});
$this->createTransport(null, $hub)->send(new ChatMessage('subject', new MercureOptions(['/topic/1', '/topic/2'], true, 'id', 'type', 1)));
} }
public function testSendWithMercureOptionsButWithoutOptionTopic() public function testSendWithMercureOptionsButWithoutOptionTopic()
{ {
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string {
$publisher $this->assertSame(['https://symfony.com/notifier'], $update->getTopics());
->expects($this->once()) $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData());
->method('__invoke') $this->assertSame('id', $update->getId());
->with(new Update(['https://symfony.com/notifier'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', true, 'id', 'type', 1)) $this->assertSame('type', $update->getType());
; $this->assertSame(1, $update->getRetry());
$this->assertTrue($update->isPrivate());
$this->createTransport(null, $publisher)->send(new ChatMessage('subject', new MercureOptions(null, true, 'id', 'type', 1))); return 'id';
});
$this->createTransport(null, $hub)->send(new ChatMessage('subject', new MercureOptions(null, true, 'id', 'type', 1)));
} }
public function testSendWithoutMercureOptions() public function testSendWithoutMercureOptions()
{ {
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update): string {
$publisher $this->assertSame(['https://symfony.com/notifier'], $update->getTopics());
->expects($this->once()) $this->assertSame('{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}', $update->getData());
->method('__invoke') $this->assertFalse($update->isPrivate());
->with(new Update(['https://symfony.com/notifier'], '{"@context":"https:\/\/www.w3.org\/ns\/activitystreams","type":"Announce","summary":"subject"}'))
;
$this->createTransport(null, $publisher)->send(new ChatMessage('subject')); return 'id';
});
$this->createTransport(null, $hub)->send(new ChatMessage('subject'));
} }
public function testSendSuccessfully() public function testSendSuccessfully()
{ {
$messageId = 'urn:uuid:a7045be0-a75d-4d40-8bd2-29fa4e5dd10b'; $messageId = 'urn:uuid:a7045be0-a75d-4d40-8bd2-29fa4e5dd10b';
$publisher = $this->createMock(PublisherInterface::class); $hub = new MockHub('https://foo.com/.well-known/mercure', new StaticTokenProvider('foo'), function (Update $update) use ($messageId): string {
$publisher->method('__invoke')->willReturn($messageId); return $messageId;
});
$sentMessage = $this->createTransport(null, $publisher)->send(new ChatMessage('subject')); $sentMessage = $this->createTransport(null, $hub)->send(new ChatMessage('subject'));
$this->assertSame($messageId, $sentMessage->getMessageId()); $this->assertSame($messageId, $sentMessage->getMessageId());
} }
} }

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": ">=7.2.5", "php": ">=7.2.5",
"ext-json": "*", "ext-json": "*",
"symfony/mercure": "^0.4", "symfony/mercure": "^0.5.2",
"symfony/notifier": "^5.3", "symfony/notifier": "^5.3",
"symfony/service-contracts": "^1.10|^2" "symfony/service-contracts": "^1.10|^2"
}, },