From 700cd53c577594f971fd087080e2074312407e53 Mon Sep 17 00:00:00 2001 From: James Seconde Date: Mon, 19 Aug 2024 16:29:36 +0100 Subject: [PATCH] Feature/q3 messages additions (#497) * RCS objects and tests added * RCS update message added --- src/Messages/Channel/RCS/RcsCustom.php | 77 ++++++ src/Messages/Channel/RCS/RcsFile.php | 77 ++++++ src/Messages/Channel/RCS/RcsImage.php | 77 ++++++ .../Channel/RCS/RcsInvalidTtlException.php | 7 + src/Messages/Channel/RCS/RcsText.php | 65 +++++ src/Messages/Channel/RCS/RcsVideo.php | 77 ++++++ src/Messages/Client.php | 13 + test/Messages/ClientTest.php | 251 ++++++++++++++++++ .../Fixtures/Responses/rcs-success.json | 3 + .../Responses/rcs-update-success.json | 0 10 files changed, 647 insertions(+) create mode 100644 src/Messages/Channel/RCS/RcsCustom.php create mode 100644 src/Messages/Channel/RCS/RcsFile.php create mode 100644 src/Messages/Channel/RCS/RcsImage.php create mode 100644 src/Messages/Channel/RCS/RcsInvalidTtlException.php create mode 100644 src/Messages/Channel/RCS/RcsText.php create mode 100644 src/Messages/Channel/RCS/RcsVideo.php create mode 100644 test/Messages/Fixtures/Responses/rcs-success.json create mode 100644 test/Messages/Fixtures/Responses/rcs-update-success.json diff --git a/src/Messages/Channel/RCS/RcsCustom.php b/src/Messages/Channel/RCS/RcsCustom.php new file mode 100644 index 00000000..b94c733d --- /dev/null +++ b/src/Messages/Channel/RCS/RcsCustom.php @@ -0,0 +1,77 @@ +to = $to; + $this->from = $from; + $this->custom = $custom; + } + + public function getCustom(): array + { + return $this->custom; + } + + public function setCustom(array $custom): RcsCustom + { + $this->custom = $custom; + return $this; + } + + public function setTtl(?int $ttl): void + { + $range = [ + 'options' => [ + 'min_range' => self::RCS_TEXT_MIN_TTL, + 'max_range' => self::RCS_TEXT_MAX_TTL + ] + ]; + + if (!filter_var($ttl, FILTER_VALIDATE_INT, $range)) { + throw new RcsInvalidTtlException('Timeout ' . $ttl . ' is not valid'); + } + + $this->ttl = $ttl; + } + + public function toArray(): array + { + $returnArray = $this->getBaseMessageUniversalOutputArray(); + + $returnArray['custom'] = $this->getCustom(); + + if ($this->getClientRef()) { + $returnArray['client_ref'] = $this->getClientRef(); + } + + if ($this->getWebhookUrl()) { + $returnArray['webhook_url'] = $this->getWebhookUrl(); + } + + if ($this->getTtl()) { + $returnArray['ttl'] = $this->getTtl(); + } + + return $returnArray; + } +} diff --git a/src/Messages/Channel/RCS/RcsFile.php b/src/Messages/Channel/RCS/RcsFile.php new file mode 100644 index 00000000..1dc575cb --- /dev/null +++ b/src/Messages/Channel/RCS/RcsFile.php @@ -0,0 +1,77 @@ +to = $to; + $this->from = $from; + $this->file = $file; + } + + public function setTtl(?int $ttl): void + { + $range = [ + 'options' => [ + 'min_range' => self::RCS_TEXT_MIN_TTL, + 'max_range' => self::RCS_TEXT_MAX_TTL + ] + ]; + + if (!filter_var($ttl, FILTER_VALIDATE_INT, $range)) { + throw new RcsInvalidTtlException('Timeout ' . $ttl . ' is not valid'); + } + + $this->ttl = $ttl; + } + + public function getFile(): FileObject + { + return $this->file; + } + + public function setFile(FileObject $fileObject): RcsFile + { + $this->file = $fileObject; + return $this; + } + + public function toArray(): array + { + $returnArray = $this->getBaseMessageUniversalOutputArray(); + + $returnArray['file'] = $this->getFile()->toArray(); + + if ($this->getClientRef()) { + $returnArray['client_ref'] = $this->getClientRef(); + } + + if ($this->getWebhookUrl()) { + $returnArray['webhook_url'] = $this->getWebhookUrl(); + } + + if ($this->getTtl()) { + $returnArray['ttl'] = $this->getTtl(); + } + + return $returnArray; + } +} diff --git a/src/Messages/Channel/RCS/RcsImage.php b/src/Messages/Channel/RCS/RcsImage.php new file mode 100644 index 00000000..25031298 --- /dev/null +++ b/src/Messages/Channel/RCS/RcsImage.php @@ -0,0 +1,77 @@ +to = $to; + $this->from = $from; + $this->image = $image; + } + + public function setTtl(?int $ttl): void + { + $range = [ + 'options' => [ + 'min_range' => self::RCS_TEXT_MIN_TTL, + 'max_range' => self::RCS_TEXT_MAX_TTL + ] + ]; + + if (!filter_var($ttl, FILTER_VALIDATE_INT, $range)) { + throw new RcsInvalidTtlException('Timeout ' . $ttl . ' is not valid'); + } + + $this->ttl = $ttl; + } + + public function getImage(): ImageObject + { + return $this->image; + } + + public function setImage(ImageObject $image): RcsImage + { + $this->image = $image; + return $this; + } + + public function toArray(): array + { + $returnArray = $this->getBaseMessageUniversalOutputArray(); + + $returnArray['image'] = $this->getImage()->toArray(); + + if ($this->getClientRef()) { + $returnArray['client_ref'] = $this->getClientRef(); + } + + if ($this->getWebhookUrl()) { + $returnArray['webhook_url'] = $this->getWebhookUrl(); + } + + if ($this->getTtl()) { + $returnArray['ttl'] = $this->getTtl(); + } + + return $returnArray; + } +} diff --git a/src/Messages/Channel/RCS/RcsInvalidTtlException.php b/src/Messages/Channel/RCS/RcsInvalidTtlException.php new file mode 100644 index 00000000..b7866a99 --- /dev/null +++ b/src/Messages/Channel/RCS/RcsInvalidTtlException.php @@ -0,0 +1,7 @@ +to = $to; + $this->from = $from; + $this->text = $message; + } + + public function setTtl(?int $ttl): void + { + $range = [ + 'options' => [ + 'min_range' => self::RCS_TEXT_MIN_TTL, + 'max_range' => self::RCS_TEXT_MAX_TTL + ] + ]; + + if (!filter_var($ttl, FILTER_VALIDATE_INT, $range)) { + throw new RcsInvalidTtlException('Timeout ' . $ttl . ' is not valid'); + } + + $this->ttl = $ttl; + } + + public function toArray(): array + { + $returnArray = $this->getBaseMessageUniversalOutputArray(); + $returnArray['text'] = $this->getText(); + + if ($this->getClientRef()) { + $returnArray['client_ref'] = $this->getClientRef(); + } + + if ($this->getWebhookUrl()) { + $returnArray['webhook_url'] = $this->getWebhookUrl(); + } + + if ($this->getTtl()) { + $returnArray['ttl'] = $this->getTtl(); + } + + return $returnArray; + } +} diff --git a/src/Messages/Channel/RCS/RcsVideo.php b/src/Messages/Channel/RCS/RcsVideo.php new file mode 100644 index 00000000..254fba31 --- /dev/null +++ b/src/Messages/Channel/RCS/RcsVideo.php @@ -0,0 +1,77 @@ +to = $to; + $this->from = $from; + $this->video = $videoObject; + } + + public function setTtl(?int $ttl): void + { + $range = [ + 'options' => [ + 'min_range' => self::RCS_TEXT_MIN_TTL, + 'max_range' => self::RCS_TEXT_MAX_TTL + ] + ]; + + if (!filter_var($ttl, FILTER_VALIDATE_INT, $range)) { + throw new RcsInvalidTtlException('Timeout ' . $ttl . ' is not valid'); + } + + $this->ttl = $ttl; + } + + public function getVideo(): VideoObject + { + return $this->video; + } + + public function setVideo(VideoObject $videoObject): RcsVideo + { + $this->video = $videoObject; + return $this; + } + + public function toArray(): array + { + $returnArray = $this->getBaseMessageUniversalOutputArray(); + + $returnArray['video'] = $this->getVideo()->toArray(); + + if ($this->getClientRef()) { + $returnArray['client_ref'] = $this->getClientRef(); + } + + if ($this->getWebhookUrl()) { + $returnArray['webhook_url'] = $this->getWebhookUrl(); + } + + if ($this->getTtl()) { + $returnArray['ttl'] = $this->getTtl(); + } + + return $returnArray; + } +} diff --git a/src/Messages/Client.php b/src/Messages/Client.php index 3a0d6bac..cbdeef32 100644 --- a/src/Messages/Client.php +++ b/src/Messages/Client.php @@ -10,6 +10,8 @@ class Client implements APIClient { + public const RCS_STATUS_REVOKED = 'revoked'; + public function __construct(protected APIResource $api) { } @@ -23,4 +25,15 @@ public function send(BaseMessage $message): ?array { return $this->getAPIResource()->create($message->toArray()); } + + public function updateRcsStatus(string $messageUuid, string $status): bool + { + try { + $this->api->partiallyUpdate($messageUuid, ['status' => $status]); + return true; + } catch (\Exception $e) { + return false; + } + return false; + } } diff --git a/test/Messages/ClientTest.php b/test/Messages/ClientTest.php index f510b306..b95f61b5 100644 --- a/test/Messages/ClientTest.php +++ b/test/Messages/ClientTest.php @@ -19,6 +19,12 @@ use Vonage\Messages\Channel\MMS\MMSImage; use Vonage\Messages\Channel\MMS\MMSvCard; use Vonage\Messages\Channel\MMS\MMSVideo; +use Vonage\Messages\Channel\RCS\RcsCustom; +use Vonage\Messages\Channel\RCS\RcsFile; +use Vonage\Messages\Channel\RCS\RcsImage; +use Vonage\Messages\Channel\RCS\RcsInvalidTtlException; +use Vonage\Messages\Channel\RCS\RcsText; +use Vonage\Messages\Channel\RCS\RcsVideo; use Vonage\Messages\Channel\SMS\SMSText; use Vonage\Messages\Channel\Viber\ViberFile; use Vonage\Messages\Channel\Viber\ViberImage; @@ -960,6 +966,251 @@ public function testCanSendViberVideo(): void $this->assertArrayHasKey('message_uuid', $result); } + public function testCanSendRcsText(): void + { + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'client_ref' => 'RCS Message', + 'ttl' => 330, + 'webhook_url' => 'https://example.com/incoming' + ]; + + $message = new RcsText($payload['to'], $payload['from'], $payload['text']); + $message->setClientRef($payload['client_ref']); + $message->setTtl($payload['ttl']); + $message->setWebhookUrl($payload['webhook_url']); + + $this->vonageClient->send(Argument::that(function (Request $request) use ($payload) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $this->assertRequestJsonBodyContains('to', $payload['to'], $request); + $this->assertRequestJsonBodyContains('from', $payload['from'], $request); + $this->assertRequestJsonBodyContains('text', $payload['text'], $request); + $this->assertRequestJsonBodyContains('client_ref', $payload['client_ref'], $request); + $this->assertRequestJsonBodyContains('webhook_url', $payload['webhook_url'], $request); + $this->assertRequestJsonBodyContains('ttl', $payload['ttl'], $request); + $this->assertRequestJsonBodyContains('channel', 'rcs', $request); + $this->assertRequestJsonBodyContains('message_type', 'text', $request); + $this->assertEquals('POST', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-success', 202)); + + $result = $this->messageClient->send($message); + $this->assertIsArray($result); + $this->assertArrayHasKey('message_uuid', $result); + } + + public function testCannotSendRcsTtlOutOfRange() + { + $this->expectException(RcsInvalidTtlException::class); + + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'ttl' => 100, + ]; + + $message = new RcsText($payload['to'], $payload['from'], $payload['text']); + $message->setTtl($payload['ttl']); + } + + public function testCanSendRcsImage() + { + $image = new ImageObject('https://my-image.com'); + + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'client_ref' => 'RCS Message', + 'ttl' => 330, + 'webhook_url' => 'https://example.com/incoming', + 'image' => $image + ]; + + $message = new RcsImage($payload['to'], $payload['from'], $payload['image']); + $message->setClientRef($payload['client_ref']); + $message->setTtl($payload['ttl']); + $message->setWebhookUrl($payload['webhook_url']); + + $this->vonageClient->send(Argument::that(function (Request $request) use ($payload) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $this->assertRequestJsonBodyContains('to', $payload['to'], $request); + $this->assertRequestJsonBodyContains('from', $payload['from'], $request); + $this->assertRequestJsonBodyContains('client_ref', $payload['client_ref'], $request); + $this->assertRequestJsonBodyContains('webhook_url', $payload['webhook_url'], $request); + $this->assertRequestJsonBodyContains('ttl', $payload['ttl'], $request); + $this->assertRequestJsonBodyContains('channel', 'rcs', $request); + $this->assertRequestJsonBodyContains('message_type', 'image', $request); + $this->assertRequestJsonBodyContains('image', ['url' => 'https://my-image.com'], $request); + $this->assertEquals('POST', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-success', 202)); + + $result = $this->messageClient->send($message); + $this->assertIsArray($result); + $this->assertArrayHasKey('message_uuid', $result); + } + + public function testCanSendRcsVideo(): void + { + $videoObject = new VideoObject('https://my-image.com'); + + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'client_ref' => 'RCS Message', + 'ttl' => 330, + 'webhook_url' => 'https://example.com/incoming', + 'video' => $videoObject + ]; + + $message = new RcsVideo($payload['to'], $payload['from'], $payload['video']); + $message->setClientRef($payload + ['client_ref']); + $message->setTtl($payload['ttl']); + $message->setWebhookUrl($payload['webhook_url']); + + $this->vonageClient->send(Argument::that(function (Request $request) use ($payload) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $this->assertRequestJsonBodyContains('to', $payload['to'], $request); + $this->assertRequestJsonBodyContains('from', $payload['from'], $request); + $this->assertRequestJsonBodyContains('client_ref', $payload['client_ref'], $request); + $this->assertRequestJsonBodyContains('webhook_url', $payload['webhook_url'], $request); + $this->assertRequestJsonBodyContains('ttl', $payload['ttl'], $request); + $this->assertRequestJsonBodyContains('channel', 'rcs', $request); + $this->assertRequestJsonBodyContains('message_type', 'video', $request); + $this->assertRequestJsonBodyContains('video', ['url' => 'https://my-image.com'], $request); + $this->assertEquals('POST', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-success', 202)); + + $result = $this->messageClient->send($message); + $this->assertIsArray($result); + $this->assertArrayHasKey('message_uuid', $result); + } + + public function testCanSendRcsFile(): void + { + $fileObject = new FileObject('https://example.com/file.pdf'); + + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'client_ref' => 'RCS Message', + 'ttl' => 330, + 'webhook_url' => 'https://example.com/incoming', + 'file' => $fileObject + ]; + + $message = new RcsFile($payload['to'], $payload['from'], $payload['file']); + $message->setClientRef($payload['client_ref']); + $message->setTtl($payload['ttl']); + $message->setWebhookUrl($payload['webhook_url']); + + $this->vonageClient->send(Argument::that(function (Request $request) use ($payload) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $this->assertRequestJsonBodyContains('to', $payload['to'], $request); + $this->assertRequestJsonBodyContains('from', $payload['from'], $request); + $this->assertRequestJsonBodyContains('client_ref', $payload['client_ref'], $request); + $this->assertRequestJsonBodyContains('webhook_url', $payload['webhook_url'], $request); + $this->assertRequestJsonBodyContains('ttl', $payload['ttl'], $request); + $this->assertRequestJsonBodyContains('channel', 'rcs', $request); + $this->assertRequestJsonBodyContains('message_type', 'file', $request); + $this->assertRequestJsonBodyContains('file', ['url' => 'https://example.com/file.pdf'], $request); + $this->assertEquals('POST', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-success', 202)); + + $result = $this->messageClient->send($message); + $this->assertIsArray($result); + $this->assertArrayHasKey('message_uuid', $result); + } + + public function testCanSendRcsCustom() + { + $customObject = [ + 'custom_key' => 'custom_value', + ]; + + $payload = [ + 'to' => '447700900000', + 'from' => '16105551212', + 'text' => 'Reticulating Splines', + 'client_ref' => 'RCS Message', + 'ttl' => 330, + 'webhook_url' => 'https://example.com/incoming', + 'custom' => $customObject + ]; + + $message = new RcsCustom($payload['to'], $payload['from'], $payload['custom']); + $message->setClientRef($payload['client_ref']); + $message->setTtl($payload['ttl']); + $message->setWebhookUrl($payload['webhook_url']); + + $this->vonageClient->send(Argument::that(function (Request $request) use ($payload) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $this->assertRequestJsonBodyContains('to', $payload['to'], $request); + $this->assertRequestJsonBodyContains('from', $payload['from'], $request); + $this->assertRequestJsonBodyContains('client_ref', $payload['client_ref'], $request); + $this->assertRequestJsonBodyContains('webhook_url', $payload['webhook_url'], $request); + $this->assertRequestJsonBodyContains('ttl', $payload['ttl'], $request); + $this->assertRequestJsonBodyContains('channel', 'rcs', $request); + $this->assertRequestJsonBodyContains('message_type', 'file', $request); + $this->assertRequestJsonBodyContains('custom', ['custom_key' => 'custom_value'], $request); + $this->assertEquals('POST', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-success', 202)); + + $result = $this->messageClient->send($message); + $this->assertIsArray($result); + $this->assertArrayHasKey('message_uuid', $result); + } + + public function testCanUpdateRcsMessage(): void + { + $this->vonageClient->send(Argument::that(function (Request $request) { + $this->assertEquals( + 'Bearer ', + mb_substr($request->getHeaders()['Authorization'][0], 0, 7) + ); + $uri = $request->getUri(); + $uriString = $uri->__toString(); + $this->assertEquals('https://api.nexmo.com/v1/messages/6ce72c29-e454-442a-94f2-47a1cadba45f', $uriString); + + $this->assertRequestJsonBodyContains('status', 'revoked', $request); + $this->assertEquals('PATCH', $request->getMethod()); + + return true; + }))->willReturn($this->getResponse('rcs-update-success')); + + $this->messageClient->updateRcsStatus('6ce72c29-e454-442a-94f2-47a1cadba45f', MessagesClient::RCS_STATUS_REVOKED); + } + public function stickerTypeProvider(): array { return [ diff --git a/test/Messages/Fixtures/Responses/rcs-success.json b/test/Messages/Fixtures/Responses/rcs-success.json new file mode 100644 index 00000000..d9cb1168 --- /dev/null +++ b/test/Messages/Fixtures/Responses/rcs-success.json @@ -0,0 +1,3 @@ +{ + "message_uuid": "aaaaaaaa-bbbb-cccc-dddd-0123456789ab" +} \ No newline at end of file diff --git a/test/Messages/Fixtures/Responses/rcs-update-success.json b/test/Messages/Fixtures/Responses/rcs-update-success.json new file mode 100644 index 00000000..e69de29b