Skip to content

Commit 7e3d5e0

Browse files
authored
Merge pull request #18 from keboola/erik-PST-2469
Notifications Client
2 parents abdb109 + 80727d8 commit 7e3d5e0

10 files changed

Lines changed: 330 additions & 1 deletion

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ PHP client for the Notification API ([API docs](https://app.swaggerhub.com/apis/
44

55
## Usage
66
The client uses two kinds of authorizations - Storage API token for Subscription API (`SubscriptionClient` class) and
7-
Manage API Application token with scope `notifications:push-event` for the Events API (`EventsClient` class).
7+
Manage API Application token with scopes:
8+
- `notifications:push-event` for the Events API (`EventsClient` class)
9+
- `notifications:send-notification` for the Notifications API (`NotificationsClient` class)
810

911
```bash
1012
composer require keboola/notification-api-php-client

src/ClientFactory.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,17 @@ public function getSubscriptionClient(string $storageApiToken, array $options):
7171
{
7272
return new SubscriptionClient($this->getNotificationUrl(), $storageApiToken, $options);
7373
}
74+
75+
/**
76+
* @param array{
77+
* handler?: HandlerStack,
78+
* backoffMaxTries: int<0, 100>,
79+
* userAgent: string,
80+
* logger?: LoggerInterface
81+
* } $options
82+
*/
83+
public function getNotificationsClient(string $applicationToken, array $options): NotificationsClient
84+
{
85+
return new NotificationsClient($this->getNotificationUrl(), $applicationToken, $options);
86+
}
7487
}

src/NotificationsClient.php

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient;
6+
7+
use GuzzleHttp\Psr7\Request;
8+
use JsonException;
9+
use Keboola\NotificationClient\Exception\ClientException;
10+
use Keboola\NotificationClient\Requests\PostNotification\NotificationInterface;
11+
use Keboola\NotificationClient\Responses\Notification as ResponseNotification;
12+
13+
class NotificationsClient extends Client
14+
{
15+
protected string $tokenHeaderName = 'X-Kbc-ManageApiToken';
16+
17+
public function __construct(string $notificationApiUrl, string $applicationToken, array $options)
18+
{
19+
if (empty($applicationToken)) {
20+
throw new ClientException(sprintf(
21+
'Application token must be non-empty, %s provided.',
22+
json_encode($applicationToken),
23+
));
24+
}
25+
parent::__construct($notificationApiUrl, $applicationToken, $options);
26+
}
27+
28+
public function postNotification(NotificationInterface $notification): ResponseNotification
29+
{
30+
try {
31+
$notificationJson = json_encode($notification->jsonSerialize(), JSON_THROW_ON_ERROR);
32+
$request = new Request(
33+
'POST',
34+
'notifications',
35+
['Content-type' => 'application/json'],
36+
$notificationJson,
37+
);
38+
} catch (JsonException $e) {
39+
throw new ClientException('Invalid notification data: ' . $e->getMessage(), $e->getCode(), $e);
40+
}
41+
42+
return new ResponseNotification($this->sendRequest($request));
43+
}
44+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Requests\PostNotification;
6+
7+
use JsonSerializable;
8+
9+
interface NotificationInterface extends JsonSerializable
10+
{
11+
12+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Requests\PostNotification;
6+
7+
use Keboola\NotificationClient\Requests\PostSubscription\EmailRecipient;
8+
9+
class ProjectEmail implements NotificationInterface
10+
{
11+
private const TYPE = 'direct-project-email';
12+
13+
private string $projectId;
14+
private string $projectName;
15+
private string $title;
16+
private string $message;
17+
private EmailRecipient $recipient;
18+
19+
public function __construct(
20+
EmailRecipient $recipient,
21+
string $projectId,
22+
string $projectName,
23+
string $title,
24+
string $message
25+
) {
26+
$this->recipient = $recipient;
27+
$this->projectId = $projectId;
28+
$this->projectName = $projectName;
29+
$this->title = $title;
30+
$this->message = $message;
31+
}
32+
33+
public function jsonSerialize(): array
34+
{
35+
return [
36+
'type' => self::TYPE,
37+
'recipient' => $this->recipient->jsonSerialize(),
38+
'data' => [
39+
'project' => [
40+
'id' => $this->projectId,
41+
'name' => $this->projectName,
42+
],
43+
'title' => $this->title,
44+
'message' => $this->message,
45+
],
46+
];
47+
}
48+
}

src/Responses/Notification.php

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Responses;
6+
7+
use Keboola\NotificationClient\Exception\ClientException;
8+
9+
class Notification
10+
{
11+
private string $id;
12+
13+
public function __construct(array $data)
14+
{
15+
if (!array_key_exists('id', $data)) {
16+
throw new ClientException('Unrecognized response');
17+
}
18+
19+
$this->id = $data['id'];
20+
}
21+
22+
public function getId(): string
23+
{
24+
return $this->id;
25+
}
26+
}

tests/ClientFactoryTest.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use GuzzleHttp\Psr7\Response;
1212
use Keboola\NotificationClient\ClientFactory;
1313
use Keboola\NotificationClient\EventsClient;
14+
use Keboola\NotificationClient\NotificationsClient;
1415
use Keboola\NotificationClient\SubscriptionClient;
1516
use PHPUnit\Framework\TestCase;
1617
use Psr\Log\Test\TestLogger;
@@ -68,6 +69,10 @@ public function testGetClient(): void
6869
EventsClient::class,
6970
$clientFactory->getEventsClient('dummy', ['backoffMaxTries' => 3, 'userAgent' => 'Test']),
7071
);
72+
self::assertInstanceOf(
73+
NotificationsClient::class,
74+
$clientFactory->getNotificationsClient('dummy', ['backoffMaxTries' => 3, 'userAgent' => 'Test']),
75+
);
7176

7277
/** @var Request $request */
7378
$request = $requestHistory[0]['request'];
@@ -100,5 +105,7 @@ public function testGetClientLazy(): void
100105
self::assertCount(1, $requestHistory);
101106
$clientFactory->getEventsClient('dummy', ['backoffMaxTries' => 3, 'userAgent' => 'Test']);
102107
self::assertCount(1, $requestHistory);
108+
$clientFactory->getNotificationsClient('dummy', ['backoffMaxTries' => 3, 'userAgent' => 'Test']);
109+
self::assertCount(1, $requestHistory);
103110
}
104111
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Tests;
6+
7+
use GuzzleHttp\Handler\MockHandler;
8+
use GuzzleHttp\HandlerStack;
9+
use GuzzleHttp\Middleware;
10+
use GuzzleHttp\Psr7\Request;
11+
use GuzzleHttp\Psr7\Response;
12+
use Keboola\NotificationClient\Exception\ClientException;
13+
use Keboola\NotificationClient\NotificationsClient;
14+
use Keboola\NotificationClient\Requests\PostNotification\ProjectEmail;
15+
use Keboola\NotificationClient\Requests\PostSubscription\EmailRecipient;
16+
use PHPUnit\Framework\TestCase;
17+
18+
class NotificationsFunctionalClientTest extends TestCase
19+
{
20+
private function getClient(): NotificationsClient
21+
{
22+
return new NotificationsClient(
23+
(string) getenv('TEST_NOTIFICATION_API_URL'),
24+
(string) getenv('TEST_MANAGE_API_APPLICATION_TOKEN'),
25+
[
26+
'backoffMaxTries' => 3,
27+
'userAgent' => 'Test',
28+
],
29+
);
30+
}
31+
32+
33+
public function testCreateClientInvalidToken(): void
34+
{
35+
$this->expectException(ClientException::class);
36+
$this->expectExceptionMessage('Application token must be non-empty, "" provided.');
37+
new NotificationsClient(
38+
'https://example.com',
39+
'',
40+
[
41+
'backoffMaxTries' => 3,
42+
'userAgent' => 'Test',
43+
],
44+
);
45+
}
46+
47+
public function testPostNotification(): void
48+
{
49+
$client = $this->getClient();
50+
$response = $client->postNotification(new ProjectEmail(
51+
new EmailRecipient('devel+test-notification-api-client@keboola.com'),
52+
(string) getenv('TEST_STORAGE_API_PROJECT_ID'),
53+
'Test Project',
54+
'Notification API client test',
55+
'Test Notification Body',
56+
));
57+
58+
self::assertIsNumeric($response->getId());
59+
}
60+
61+
public function testPostNotificationHeaders(): void
62+
{
63+
$mock = new MockHandler([
64+
new Response(
65+
200,
66+
['Content-Type' => 'application/json'],
67+
'{"id": "1", "event": "2", "filters": [], "recipient": {"channel": "foo", "address": "bar"}}',
68+
),
69+
]);
70+
// Add the history middleware to the handler stack.
71+
$requestHistory = [];
72+
$history = Middleware::history($requestHistory);
73+
$stack = HandlerStack::create($mock);
74+
$stack->push($history);
75+
$client = new NotificationsClient(
76+
'https://example.com/',
77+
'testToken',
78+
['handler' => $stack, 'backoffMaxTries' => 3, 'userAgent' => 'Test'],
79+
);
80+
$client->postNotification(new ProjectEmail(
81+
new EmailRecipient('devel+test-notification-api-client@keboola.com'),
82+
(string) getenv('TEST_STORAGE_API_PROJECT_ID'),
83+
'Test Project',
84+
'Notification API client test',
85+
'Test Notification Body',
86+
));
87+
88+
/** @var Request $request */
89+
$request = $requestHistory[0]['request'];
90+
self::assertSame('POST', $request->getMethod());
91+
self::assertSame(
92+
['Content-Length', 'User-Agent', 'X-Kbc-ManageApiToken', 'Host', 'Content-type'],
93+
array_keys($request->getHeaders()),
94+
);
95+
self::assertSame(['application/json'], $request->getHeaders()['Content-type']);
96+
}
97+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Tests\Requests\PostNotification;
6+
7+
use Keboola\NotificationClient\Requests\PostNotification\ProjectEmail;
8+
use Keboola\NotificationClient\Requests\PostSubscription\EmailRecipient;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class ProjectEmailTest extends TestCase
12+
{
13+
public function testJsonSerialize(): void
14+
{
15+
$recipient = new EmailRecipient('john.doe@example.com');
16+
$projectId = '12345';
17+
$projectName = 'Test Project';
18+
$title = 'Test Notification';
19+
$message = 'This is a test notification message';
20+
21+
$projectEmail = new ProjectEmail(
22+
$recipient,
23+
$projectId,
24+
$projectName,
25+
$title,
26+
$message,
27+
);
28+
29+
self::assertSame(
30+
[
31+
'type' => 'direct-project-email',
32+
'recipient' => [
33+
'channel' => 'email',
34+
'address' => 'john.doe@example.com',
35+
],
36+
'data' => [
37+
'project' => [
38+
'id' => $projectId,
39+
'name' => $projectName,
40+
],
41+
'title' => $title,
42+
'message' => $message,
43+
],
44+
],
45+
$projectEmail->jsonSerialize(),
46+
);
47+
}
48+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Keboola\NotificationClient\Tests\Responses;
6+
7+
use Keboola\NotificationClient\Exception\ClientException;
8+
use Keboola\NotificationClient\Responses\Notification;
9+
use PHPUnit\Framework\TestCase;
10+
11+
class NotificationTest extends TestCase
12+
{
13+
public function testAccessors(): void
14+
{
15+
$data = [
16+
'id' => '123',
17+
];
18+
$notification = new Notification($data);
19+
self::assertSame('123', $notification->getId());
20+
}
21+
22+
public function testInvalidData(): void
23+
{
24+
$data = [
25+
'some' => 'value',
26+
];
27+
$this->expectException(ClientException::class);
28+
$this->expectExceptionMessage('Unrecognized response');
29+
$this->expectExceptionCode(0);
30+
new Notification($data);
31+
}
32+
}

0 commit comments

Comments
 (0)