-
Notifications
You must be signed in to change notification settings - Fork 68
feat(breaking): add Vonage Messages API adapter #143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
34654af
5f92f64
3b8e2c4
c1ef16e
6fa3699
449c941
4bcbabd
a1d89ce
31fa7f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\Messaging\Adapter\SMS; | ||
|
|
||
| use Utopia\Messaging\Adapter\SMS as SMSAdapter; | ||
| use Utopia\Messaging\Messages\SMS as SMSMessage; | ||
| use Utopia\Messaging\Response; | ||
|
|
||
| // Vonage Messages API SMS Adapter. | ||
| // This adapter uses the modern Vonage Messages API (V1) which is more cost-effective | ||
| // and versatile than the legacy SMS API. | ||
| // https://developer.vonage.com/en/api/messages | ||
| class VonageMessages extends SMSAdapter | ||
| { | ||
| protected const NAME = 'Vonage Messages'; | ||
|
|
||
| public function __construct( | ||
| private string $apiKey, | ||
| private string $apiSecret, | ||
| private ?string $from = null | ||
| ) { | ||
| } | ||
|
|
||
| // Vonage Messages API endpoint. | ||
| protected function getApiEndpoint(): string | ||
| { | ||
| return 'https://api.vonage.com/v1/messages'; | ||
| } | ||
|
|
||
| // Generates the Basic Authorization header. | ||
| protected function getAuthorizationHeader(): string | ||
| { | ||
| return 'Basic ' . \base64_encode("{$this->apiKey}:{$this->apiSecret}"); | ||
| } | ||
|
|
||
| // Sets common headers for the API request. | ||
| protected function getRequestHeaders(): array | ||
| { | ||
| return [ | ||
| "Authorization: {$this->getAuthorizationHeader()}", | ||
| 'Content-Type: application/json', | ||
| 'Accept: application/json', | ||
| 'User-Agent: Utopia Messaging', | ||
| ]; | ||
| } | ||
|
|
||
| // Get adapter name. | ||
| public function getName(): string | ||
| { | ||
| return static::NAME; | ||
| } | ||
|
|
||
| // Get max messages per request. | ||
| public function getMaxMessagesPerRequest(): int | ||
| { | ||
| return 1; | ||
| } | ||
|
|
||
| protected function process(SMSMessage $message): array | ||
| { | ||
| $to = \ltrim($message->getTo()[0], '+'); | ||
| $from = $this->from ?? $message->getFrom(); | ||
| $from = $from !== null ? \ltrim($from, '+') : null; | ||
|
|
||
| $response = new Response($this->getType()); | ||
|
|
||
| if (empty($from)) { | ||
| $response->addResult($message->getTo()[0], 'The "from" field is required for the Vonage Messages API.'); | ||
| return $response->toArray(); | ||
| } | ||
|
|
||
| $result = $this->request( | ||
| method: 'POST', | ||
| url: $this->getApiEndpoint(), | ||
| headers: $this->getRequestHeaders(), | ||
| body: [ | ||
| 'message_type' => 'text', | ||
| 'to' => $to, | ||
| 'from' => $from, | ||
| 'text' => $message->getContent(), | ||
| 'channel' => 'sms', | ||
| ], | ||
| ); | ||
|
|
||
| if ($result['statusCode'] === 202) { | ||
| $response->setDeliveredTo(1); | ||
| $response->addResult($message->getTo()[0]); | ||
| } else { | ||
| $errorMessage = "Error {$result['statusCode']}"; | ||
|
|
||
| if (\is_array($result['response'])) { | ||
| if (isset($result['response']['detail'])) { | ||
| $errorMessage = $result['response']['detail']; | ||
| } elseif (isset($result['response']['title'])) { | ||
| $errorMessage = $result['response']['title']; | ||
| } | ||
| } elseif (!empty($result['error'])) { | ||
| $errorMessage = $result['error']; | ||
| } elseif (\is_string($result['response']) && !empty($result['response'])) { | ||
| $errorMessage = "Error {$result['statusCode']}: " . \mb_strimwidth(\strip_tags($result['response']), 0, 100, '...'); | ||
| } | ||
|
|
||
| $response->addResult($message->getTo()[0], $errorMessage); | ||
| } | ||
|
|
||
| return $response->toArray(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| <?php | ||
|
|
||
| namespace Utopia\Tests\Adapter\SMS; | ||
|
|
||
| use Utopia\Messaging\Adapter\SMS\VonageMessages; | ||
| use Utopia\Messaging\Messages\SMS; | ||
| use Utopia\Tests\Adapter\Base; | ||
|
|
||
| class VonageMessagesTest extends Base | ||
| { | ||
| // Tests sending an SMS with the 'from' number set directly in the adapter. | ||
| public function testSendSMS(): void | ||
| { | ||
| $apiKey = \getenv('VONAGE_API_KEY'); | ||
| $apiSecret = \getenv('VONAGE_API_SECRET'); | ||
| $to = \getenv('VONAGE_TO'); | ||
|
|
||
| if (!$apiKey || !$apiSecret || !$to) { | ||
| $this->markTestSkipped('Vonage Messages credentials or recipient are not available.'); | ||
| } | ||
|
Comment on lines
+18
to
+20
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These tests skip without live credentials and exercise only successful deliveries, leaving the new request construction and HTTP, transport, and malformed-response handling untested in normal CI. Add deterministic client or local-server tests that assert the outgoing request and representative error responses. Prompt To Fix With AIThis is a comment left during a code review.
Path: tests/Messaging/Adapter/SMS/VonageMessagesTest.php
Line: 18-20
Comment:
**Adapter behavior lacks deterministic coverage**
These tests skip without live credentials and exercise only successful deliveries, leaving the new request construction and HTTP, transport, and malformed-response handling untested in normal CI. Add deterministic client or local-server tests that assert the outgoing request and representative error responses.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||
|
|
||
| $sender = new VonageMessages( | ||
| apiKey: $apiKey, | ||
| apiSecret: $apiSecret, | ||
| from: \getenv('VONAGE_FROM') ?: 'Vonage', | ||
| ); | ||
|
|
||
| $message = new SMS( | ||
| to: [$to], | ||
| content: 'Test Content', | ||
| ); | ||
|
|
||
| $response = $sender->send($message); | ||
|
|
||
| $this->assertResponse($response); | ||
| } | ||
|
|
||
| // Tests sending an SMS where the 'from' number is provided by the message object. | ||
| public function testSendSMSWithFallbackFrom(): void | ||
| { | ||
| $apiKey = \getenv('VONAGE_API_KEY'); | ||
| $apiSecret = \getenv('VONAGE_API_SECRET'); | ||
| $to = \getenv('VONAGE_TO'); | ||
| $from = \getenv('VONAGE_FROM') ?: null; | ||
|
|
||
| if (!$apiKey || !$apiSecret || !$to || !$from) { | ||
| $this->markTestSkipped('Vonage Messages credentials or sender/recipient are not available.'); | ||
| } | ||
|
|
||
| $sender = new VonageMessages( | ||
| apiKey: $apiKey, | ||
| apiSecret: $apiSecret, | ||
| ); | ||
|
|
||
| $message = new SMS( | ||
| to: [$to], | ||
| content: 'Test Content', | ||
| from: $from, | ||
| ); | ||
|
|
||
| $response = $sender->send($message); | ||
|
|
||
| $this->assertResponse($response); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an SMS has an empty
toarray, the inherited validation permits it, but this line accesses index 0 and passes null toltrim(), causing the send operation to fail with aTypeErrorinstead of returning a messaging response.Prompt To Fix With AI