Skip to content

Commit

Permalink
Adds unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
butschster committed May 22, 2022
1 parent ae7b244 commit 3306cb7
Show file tree
Hide file tree
Showing 11 changed files with 517 additions and 35 deletions.
5 changes: 2 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
"symfony/notifier": "^6.0"
},
"require-dev": {
"mockery/mockery": "^1.5",
"phpunit/phpunit": "^9.5",
"spiral/testing": "^1.0",
"spiral/testing": "^1.2",
"symfony/firebase-notifier": "^6.0",
"vimeo/psalm": "^4.9"
},
"autoload": {
Expand Down
14 changes: 7 additions & 7 deletions src/ChannelManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Spiral\Notifications;

use Spiral\Core\Container;
use Spiral\Core\FactoryInterface;
use Spiral\Notifications\Config\NotificationsConfig;
use Spiral\SendIt\Config\MailerConfig;
use Symfony\Component\Mailer\Transport\RoundRobinTransport as MailerRoundRobinTransport;
Expand All @@ -19,7 +19,7 @@ final class ChannelManager
private array $channels = [];

public function __construct(
private Container $container,
private FactoryInterface $factory,
private NotificationsConfig $config,
private MailerConfig $mailerConfig,
) {
Expand All @@ -36,16 +36,16 @@ public function getChannel(string $name): ?ChannelInterface

if ($channel['type'] === EmailChannel::class) {
if (\count($dsns) === 1) {
$transport = $this->resolveMailerTransport(new Transport\Dsn($dsns[0]));
$transport = $this->resolveMailerTransport($dsns[0]);
} else {
$transport = new MailerRoundRobinTransport(
\array_map(function (string $dsn): MailerTransportInterface {
return $this->resolveMailerTransport(new Transport\Dsn($dsn));
\array_map(function (Transport\Dsn $dsn): MailerTransportInterface {
return $this->resolveMailerTransport($dsn);
}, $dsns)
);
}

return $this->container->make($channel['type'], [
return $this->factory->make($channel['type'], [
'transport' => $transport,
'from' => $this->mailerConfig->getFromAddress(),
]);
Expand All @@ -61,7 +61,7 @@ public function getChannel(string $name): ?ChannelInterface
);
}

return $this->container->make($channel['type'], [
return $this->factory->make($channel['type'], [
'transport' => $transport,
]);
}
Expand Down
19 changes: 13 additions & 6 deletions src/Config/NotificationsConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@
use Spiral\Core\InjectableConfig;
use Spiral\Notifications\Exceptions\InvalidArgumentException;
use Spiral\Notifications\Exceptions\TransportException;
use Symfony\Component\Notifier\Channel\ChannelInterface;
use Symfony\Component\Notifier\Transport\Dsn;

final class NotificationsConfig extends InjectableConfig
{
public const CONFIG = 'notifications';

protected $config = [
'queueConnection' => null,
'channels' => [],
Expand All @@ -31,33 +33,36 @@ public function getChannelPolicies(): array

/**
* @param string $name
* @return array{type: class-string, transport: array<Dsn>}
* @return array{
* type: class-string<ChannelInterface>,
* transport: array<Dsn>
* }
* @throws InvalidArgumentException
* @throws TransportException
*/
public function getChannel(string $name): array
{
if (! isset($this->config['channels'][$name])) {
throw new TransportException(sprintf('Transport with given name `%s` is not found.', $name));
throw new TransportException(sprintf('Channel with given name `%s` is not found.', $name));
}

$channel = $this->config['channels'][$name];

if (! \is_array($channel)) {
throw new InvalidArgumentException(
sprintf('Config for channel `%s` must be an array', $name)
sprintf('Config for channel `%s` must be an array.', $name)
);
}

if (! isset($channel['type'])) {
throw new InvalidArgumentException(
sprintf('Config for channel `%s` should contain `type` key', $name)
sprintf('Config for channel `%s` should contain `type` key.', $name)
);
}

if (! isset($channel['transport'])) {
throw new InvalidArgumentException(
sprintf('Config for channel `%s` should contain `transport` key', $name)
sprintf('Config for channel `%s` should contain `transport` key.', $name)
);
}

Expand All @@ -84,13 +89,15 @@ public function getTransport(array $names): array
throw new TransportException(sprintf('Transport with given name `%s` is not found.', $name));
}

$transport = $dsns[] = $this->config['transports'][$name];
$transport = $this->config['transports'][$name];

if (! \is_string($transport)) {
throw new InvalidArgumentException(
sprintf('Config for transport `%s` must be a DSN string', $name)
);
}

$dsns[] = new Dsn($transport);
}


Expand Down
1 change: 1 addition & 0 deletions src/Notifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Spiral\Notifications;

use Spiral\Notifications\Config\NotificationsConfig;
use Spiral\Queue\QueueableInterface;
use Spiral\Queue\QueueConnectionProviderInterface;
use Symfony\Component\Notifier\Channel\ChannelInterface;
use Symfony\Component\Notifier\Channel\ChannelPolicy;
Expand Down
10 changes: 0 additions & 10 deletions src/QueueableInterface.php

This file was deleted.

20 changes: 13 additions & 7 deletions src/SendNotificationJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,18 @@

final class SendNotificationJob implements HandlerInterface
{
public function __construct(private NotifierInterface $notifier)
{
public function __construct(
private NotifierInterface $notifier
) {
}

/**
* @param array{
* notification: Notification,
* recipient: RecipientInterface
* } $payload
* @throws InvalidArgumentException
*/
public function handle(string $name, string $id, array $payload): void
{
if (! isset($payload['notification'])) {
Expand All @@ -25,7 +33,7 @@ public function handle(string $name, string $id, array $payload): void
if (! $payload['notification'] instanceof Notification) {
throw new InvalidArgumentException(
sprintf(
'Payload `notification` key value type should be instance of %s',
'Payload `notification` key value type should be instance of `%s`',
Notification::class
)
);
Expand All @@ -35,19 +43,17 @@ public function handle(string $name, string $id, array $payload): void
throw new InvalidArgumentException('Payload `recipient` key is required.');
}


if (! $payload['recipient'] instanceof RecipientInterface) {
throw new InvalidArgumentException(
sprintf(
'Payload `recipient` key value type should be instance of %s',
'Payload `recipient` key value type should be instance of `%s`',
RecipientInterface::class
)
);
}

/** @var Notification $notification */
$notification = $payload['notification'];

/** @var RecipientInterface $notification */
$recipient = $payload['recipient'];

$this->notifier->sendNow($notification, $recipient);
Expand Down
24 changes: 24 additions & 0 deletions tests/app/Notifications/UserNotification.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace Spiral\Notifications\Tests\App\Notifications;

use Spiral\Queue\QueueableInterface;
use Symfony\Component\Notifier\Message\EmailMessage;
use Symfony\Component\Notifier\Notification\Notification;
use Symfony\Component\Notifier\Recipient\EmailRecipientInterface;
use Symfony\Component\Notifier\Recipient\RecipientInterface;

class UserNotification extends Notification implements QueueableInterface
{
public function getChannels(RecipientInterface $recipient): array
{
return ['email'];
}

public function asEmailMessage(EmailRecipientInterface $recipient, string $transport = null): ?EmailMessage
{
return EmailMessage::fromNotification($this, $recipient);
}
}
159 changes: 159 additions & 0 deletions tests/src/ChannelManagerTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
<?php

declare(strict_types=1);

namespace Spiral\Notifications\Tests;

use Mockery as m;
use Spiral\Core\FactoryInterface;
use Spiral\Notifications\ChannelManager;
use Spiral\Notifications\Config\NotificationsConfig;
use Spiral\SendIt\Config\MailerConfig;
use Symfony\Component\Mailer\Transport\RoundRobinTransport as MailerRoundRobinTransport;
use Symfony\Component\Mailer\Transport\Smtp\EsmtpTransport;
use Symfony\Component\Notifier\Bridge\Firebase\FirebaseTransport;
use Symfony\Component\Notifier\Channel\ChannelInterface;
use Symfony\Component\Notifier\Channel\EmailChannel;
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException;
use Symfony\Component\Notifier\Transport\RoundRobinTransport;

final class ChannelManagerTest extends TestCase
{
private ChannelManager $manager;
private m\LegacyMockInterface|m\MockInterface|FactoryInterface $factory;

protected function setUp(): void
{
parent::setUp();

$config = new NotificationsConfig([
'channels' => [
'email_single' => [
'type' => EmailChannel::class,
'transport' => 'gmail',
],
'email_multiple' => [
'type' => EmailChannel::class,
'transport' => ['gmail', 'yahoo'],
],
'firebase' => [
'type' => 'firebase',
'transport' => 'firebase',
],
'firebase_multiple' => [
'type' => 'firebase_multiple',
'transport' => ['firebase1', 'firebase'],
],
'unknown' => [
'type' => 'unknown',
'transport' => 'unknown',
],
'unsupported' => [
'type' => 'unsupported',
'transport' => 'unsupported',
],
],
'transports' => [
'gmail' => 'smtp://gmail:[email protected]:25',
'yahoo' => 'smtp://yahoo:[email protected]:25',
'firebase' => 'firebase://USERNAME:PASSWORD@default',
'firebase1' => 'firebase://USERNAME1:PASSWORD@default',
'unknown' => 'foo://USERNAME:PASSWORD@default',
'unsupported' => 'discord://TOKEN@default?webhook_id=ID',
],
]);

$this->manager = new ChannelManager(
$this->factory = m::mock(FactoryInterface::class),
$config,
new MailerConfig([
'dsn' => 'smtp://user:[email protected]:25',
'from' => '[email protected]',
'queue' => null,
'pipeline' => null,
'queueConnection' => null,
])
);
}

public function testGetsEmailChannelWithSingleTransport(): void
{
$this->factory->shouldReceive('make')->once()
->withArgs(static function (string $type, array $args): bool {
return $type === EmailChannel::class
&& $args['transport'] instanceof EsmtpTransport
&& (string)$args['transport'] === 'smtp://smtp.gmail.com'
&& $args['from'] === '[email protected]';
})
->andReturn($channel = m::mock(ChannelInterface::class));

$this->assertSame(
$channel,
$this->manager->getChannel('email_single')
);
}

public function testGetsEmailChannelWithMultipleTransport(): void
{
$this->factory->shouldReceive('make')->once()
->withArgs(static function (string $type, array $args): bool {
return $type === EmailChannel::class
&& $args['transport'] instanceof MailerRoundRobinTransport
&& (string)$args['transport'] === 'roundrobin(smtp://smtp.gmail.com smtp://smtp.yahoo.com)'
&& $args['from'] === '[email protected]';
})
->andReturn($channel = m::mock(ChannelInterface::class));

$this->assertSame(
$channel,
$this->manager->getChannel('email_multiple')
);
}

public function testGetsNotificationChannelWithSingleTransport(): void
{
$this->factory->shouldReceive('make')->once()
->withArgs(static function (string $type, array $args): bool {
return $type === 'firebase'
&& $args['transport'] instanceof FirebaseTransport
&& (string)$args['transport'] === 'firebase://fcm.googleapis.com/fcm/send';
})
->andReturn($channel = m::mock(ChannelInterface::class));

$this->assertSame(
$channel,
$this->manager->getChannel('firebase')
);
}

public function testGetsNotificationChannelWithMultipleTransport(): void
{
$this->factory->shouldReceive('make')->once()
->withArgs(static function (string $type, array $args): bool {
return $type === 'firebase_multiple'
&& $args['transport'] instanceof RoundRobinTransport;
})
->andReturn($channel = m::mock(ChannelInterface::class));

$this->assertSame(
$channel,
$this->manager->getChannel('firebase_multiple')
);
}

public function testUnknownTransportTypeShouldThrowAnException(): void
{
$this->expectException(UnsupportedSchemeException::class);
$this->expectErrorMessage('The "foo" scheme is not supported.');

$this->manager->getChannel('unknown');
}

public function testUnsupportedTransportTypeShouldThrowAnException(): void
{
$this->expectException(UnsupportedSchemeException::class);
$this->expectErrorMessage('Unable to send notification via "discord" as the bridge is not installed; try running "composer require symfony/discord-notifier".');

$this->manager->getChannel('unsupported');
}
}
Loading

0 comments on commit 3306cb7

Please sign in to comment.