-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from Jeroeny/init
create Mattermost bridge for symfony notifier
- Loading branch information
Showing
12 changed files
with
435 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
/Tests export-ignore | ||
/phpunit.xml.dist export-ignore |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
/.idea | ||
/vendor | ||
composer.lock | ||
|
||
.phpunit.result.cache |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
language: php | ||
|
||
php: | ||
- 7.2 | ||
- 7.3 | ||
- 7.4snapshot | ||
|
||
install: | ||
- composer install --no-progress | ||
|
||
script: | ||
- ./vendor/bin/phpunit --coverage-clover=coverage.xml | ||
|
||
after_success: | ||
- bash <(curl -s https://codecov.io/bash) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CHANGELOG | ||
========= | ||
|
||
1.0.0 | ||
----- | ||
|
||
* Created the bridge |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
<?php | ||
|
||
namespace Notifier\Bridge\Mattermost; | ||
|
||
use Symfony\Component\Notifier\Message\MessageOptionsInterface; | ||
|
||
final class MattermostMessageOptions implements MessageOptionsInterface | ||
{ | ||
/** @var string the channel to send the message to. in case of a person, prefixed with '@' */ | ||
private $channel; | ||
|
||
/** @var string the username to display above the sent message */ | ||
private $username; | ||
|
||
/** @var string the avatar to display with the sent message */ | ||
private $icon_url; | ||
|
||
public function __construct(string $channel, string $username, string $icon_url) | ||
{ | ||
$this->channel = $channel; | ||
$this->username = $username; | ||
$this->icon_url = $icon_url; | ||
} | ||
|
||
public function toArray(): array | ||
{ | ||
return [ | ||
'channel' => $this->channel, | ||
'username' => $this->username, | ||
'icon_url' => $this->icon_url, | ||
]; | ||
} | ||
|
||
public function getRecipientId(): ?string | ||
{ | ||
return $this->channel; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<?php | ||
|
||
namespace Notifier\Bridge\Mattermost; | ||
|
||
use Symfony\Component\Notifier\Exception\LogicException; | ||
use Symfony\Component\Notifier\Exception\TransportException; | ||
use Symfony\Component\Notifier\Message\ChatMessage; | ||
use Symfony\Component\Notifier\Message\MessageInterface; | ||
use Symfony\Component\Notifier\Transport\AbstractTransport; | ||
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
use function array_filter; | ||
use function get_class; | ||
use function sprintf; | ||
|
||
/** | ||
* MattermostTransport. | ||
* | ||
* @author Jeroen Spee <[email protected]> | ||
* | ||
* @internal | ||
*/ | ||
final class MattermostTransport extends AbstractTransport | ||
{ | ||
/** @var string webhook token */ | ||
private $token; | ||
|
||
/** @var string channel to send a message to by default. prefix with '@' for user */ | ||
private $chatChannel; | ||
|
||
public function __construct(string $token, string $chatChannel, HttpClientInterface $client = null, EventDispatcherInterface $dispatcher = null) | ||
{ | ||
$this->token = $token; | ||
$this->chatChannel = $chatChannel; | ||
$this->client = $client; | ||
|
||
parent::__construct($client, $dispatcher); | ||
} | ||
|
||
public function __toString(): string | ||
{ | ||
return sprintf('mattermost://%s@%s?channel=%s', $this->token, $this->getEndpoint(), $this->chatChannel); | ||
} | ||
|
||
public function supports(MessageInterface $message): bool | ||
{ | ||
return $message instanceof ChatMessage; | ||
} | ||
|
||
/** | ||
* @see https://docs.mattermost.com/developer/webhooks-incoming.html | ||
*/ | ||
protected function doSend(MessageInterface $message): void | ||
{ | ||
if (!$message instanceof ChatMessage) { | ||
throw new LogicException(sprintf('The "%s" transport only supports instances of "%s" (instance of "%s" given).', __CLASS__, ChatMessage::class, get_class($message))); | ||
} | ||
|
||
$endpoint = sprintf('https://%s/hooks/%s', $this->getEndpoint(), $this->token); | ||
$options = ($opts = $message->getOptions()) ? $opts->toArray() : []; | ||
if (!isset($options['channel'])) { | ||
$options['channel'] = $message->getRecipientId() ?: $this->chatChannel; | ||
} | ||
$options['text'] = $message->getSubject(); | ||
$response = $this->client->request('POST', $endpoint, [ | ||
'json' => array_filter($options), | ||
]); | ||
|
||
if (200 !== $response->getStatusCode()) { | ||
$result = $response->toArray(false); | ||
|
||
throw new TransportException(sprintf('Unable to post the Mattermost message: %s (%s: %s).', $result['message'], $result['status_code'], $result['id']), $response); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
<?php | ||
|
||
namespace Notifier\Bridge\Mattermost; | ||
|
||
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; | ||
use Symfony\Component\Notifier\Transport\AbstractTransportFactory; | ||
use Symfony\Component\Notifier\Transport\Dsn; | ||
use Symfony\Component\Notifier\Transport\TransportInterface; | ||
|
||
/** | ||
* @author Jeroen Spee <[email protected]> | ||
*/ | ||
final class MattermostTransportFactory extends AbstractTransportFactory | ||
{ | ||
public function create(Dsn $dsn): TransportInterface | ||
{ | ||
$scheme = $dsn->getScheme(); | ||
$token = $this->getUser($dsn); | ||
$channel = $dsn->getOption('channel'); | ||
$host = 'default' === $dsn->getHost() ? null : $dsn->getHost(); | ||
$port = $dsn->getPort(); | ||
|
||
if ('mattermost' === $scheme) { | ||
return (new MattermostTransport($token, $channel, $this->client, $this->dispatcher)) | ||
->setHost($host) | ||
->setPort($port); | ||
} | ||
|
||
throw new UnsupportedSchemeException($dsn, 'mattermost', $this->getSupportedSchemes()); | ||
} | ||
|
||
protected function getSupportedSchemes(): array | ||
{ | ||
return ['mattermost']; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,6 @@ | ||
# mattermost-bridge | ||
# Mattermost bridge | ||
|
||
[![build](https://travis-ci.org/Jeroeny/mattermost-bridge.svg?branch=master)](https://travis-ci.org/Jeroeny/mattermost-bridge) | ||
[![codecov](https://codecov.io/gh/Jeroeny/mattermost-bridge/branch/master/graph/badge.svg)](https://codecov.io/gh/Jeroeny/mattermost-bridge) | ||
|
||
Provides Mattermost integration for Symfony Notifier. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
<?php | ||
|
||
namespace Notifier\Bridge\Mattermost\Tests; | ||
|
||
use Notifier\Bridge\Mattermost\MattermostMessageOptions; | ||
use Notifier\Bridge\Mattermost\MattermostTransport; | ||
use Notifier\Bridge\Mattermost\MattermostTransportFactory; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Notifier\Exception\LogicException; | ||
use Symfony\Component\Notifier\Exception\TransportException; | ||
use Symfony\Component\Notifier\Exception\UnsupportedSchemeException; | ||
use Symfony\Component\Notifier\Message\ChatMessage; | ||
use Symfony\Component\Notifier\Message\MessageInterface; | ||
use Symfony\Component\Notifier\Transport\Dsn; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
use Symfony\Contracts\HttpClient\ResponseInterface; | ||
|
||
final class MattermostTransportFactoryTest extends TestCase | ||
{ | ||
/** @var string */ | ||
private $token; | ||
/** @var string */ | ||
private $channel; | ||
|
||
/** @var MockObject|HttpClientInterface */ | ||
private $httpClient; | ||
|
||
/** @var MattermostTransportFactory */ | ||
private $transportFactory; | ||
|
||
public function setUp(): void | ||
{ | ||
$this->token = 'testToken'; | ||
$this->channel = 'testChannel'; | ||
$this->httpClient = $this->createMock(HttpClientInterface::class); | ||
$this->transportFactory = new MattermostTransportFactory(null, $this->httpClient); | ||
} | ||
|
||
public function test_create_from_string(): void | ||
{ | ||
$dsn = 'mattermost://token@localhost?channel=test'; | ||
$transport = $this->transportFactory->create(Dsn::fromString($dsn)); | ||
$this->assertInstanceOf(MattermostTransport::class, $transport); | ||
$this->assertSame($dsn, $transport->__toString()); | ||
} | ||
|
||
public function test_create_unsupported(): void | ||
{ | ||
$this->expectException(UnsupportedSchemeException::class); | ||
$dsn = 'test://token@localhost?channel=test'; | ||
$this->transportFactory->create(Dsn::fromString($dsn)); | ||
} | ||
|
||
public function test_supports(): void | ||
{ | ||
$this->assertTrue($this->transportFactory->supports(Dsn::fromString('mattermost://token@localhost?channel=test'))); | ||
$this->assertFalse($this->transportFactory->supports(Dsn::fromString('test://user@localhost?channel=test'))); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
<?php | ||
|
||
namespace Notifier\Bridge\Mattermost\Tests; | ||
|
||
use Notifier\Bridge\Mattermost\MattermostMessageOptions; | ||
use Notifier\Bridge\Mattermost\MattermostTransport; | ||
use PHPUnit\Framework\MockObject\MockObject; | ||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Component\Notifier\Exception\LogicException; | ||
use Symfony\Component\Notifier\Exception\TransportException; | ||
use Symfony\Component\Notifier\Message\ChatMessage; | ||
use Symfony\Component\Notifier\Message\MessageInterface; | ||
use Symfony\Contracts\HttpClient\HttpClientInterface; | ||
use Symfony\Contracts\HttpClient\ResponseInterface; | ||
|
||
final class MattermostTransportTest extends TestCase | ||
{ | ||
/** @var string */ | ||
private $token; | ||
/** @var string */ | ||
private $channel; | ||
|
||
/** @var MockObject|HttpClientInterface */ | ||
private $httpClient; | ||
|
||
/** @var MattermostTransport */ | ||
private $transport; | ||
|
||
public function setUp(): void | ||
{ | ||
$this->token = 'testToken'; | ||
$this->channel = 'testChannel'; | ||
$this->httpClient = $this->createMock(HttpClientInterface::class); | ||
$this->transport = new MattermostTransport($this->token, $this->channel, $this->httpClient); | ||
} | ||
|
||
public function test_transport(): void | ||
{ | ||
$response = $this->createMock(ResponseInterface::class); | ||
$this->httpClient | ||
->expects($this->once()) | ||
->method('request') | ||
->willReturn($response); | ||
|
||
$response->expects($this->once())->method('getStatusCode')->willReturn(200); | ||
|
||
$channel = 'testChannel'; | ||
$messageOptions = new MattermostMessageOptions( | ||
$channel, 'webhookTest', 'iconUrl' | ||
); | ||
$this->assertSame($channel, $messageOptions->getRecipientId()); | ||
$this->transport->send(new ChatMessage('testMessage', $messageOptions)); | ||
} | ||
|
||
public function test_transport_fallback_channel(): void | ||
{ | ||
$response = $this->createMock(ResponseInterface::class); | ||
$this->httpClient | ||
->expects($this->once()) | ||
->method('request') | ||
->willReturn($response); | ||
|
||
$response->expects($this->once())->method('getStatusCode')->willReturn(200); | ||
|
||
$this->transport->send(new ChatMessage('testMessage')); | ||
} | ||
|
||
public function test_to_string(): void | ||
{ | ||
$this->assertSame('mattermost://' . $this->token . '@localhost?channel=' . $this->channel, $this->transport->__toString()); | ||
} | ||
|
||
public function test_supports(): void | ||
{ | ||
$this->assertTrue($this->transport->supports(new ChatMessage('test'))); | ||
$this->assertFalse($this->transport->supports($this->createMock(MessageInterface::class))); | ||
} | ||
|
||
public function test_send_fail(): void | ||
{ | ||
$this->expectException(LogicException::class); | ||
$this->transport->send($this->createMock(MessageInterface::class)); | ||
} | ||
|
||
public function test_transport_fail(): void | ||
{ | ||
$this->expectException(TransportException::class); | ||
$this->expectExceptionMessage('Unable to post the Mattermost message: Invalid webhook (400: web.incoming_webhook.invalid.app_error).'); | ||
$response = $this->createMock(ResponseInterface::class); | ||
$this->httpClient | ||
->expects($this->once()) | ||
->method('request') | ||
->willReturn($response); | ||
|
||
$response->expects($this->once())->method('getStatusCode')->willReturn(400); | ||
$response->expects($this->once())->method('toArray')->willReturn([ | ||
'id' => 'web.incoming_webhook.invalid.app_error', | ||
'message' => 'Invalid webhook', | ||
'detailed_error' => '', | ||
'request_id' => 'ging73md9qmadtest', | ||
'status_code' => 400 | ||
]); | ||
|
||
$this->transport->send(new ChatMessage( | ||
'testMessage', | ||
new MattermostMessageOptions( | ||
'testChannel', 'webhookTest', 'iconUrl' | ||
) | ||
) | ||
); | ||
} | ||
} |
Oops, something went wrong.