diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 901e25bf..552b207d 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -12,7 +12,34 @@ jobs: with: fetch-depth: 2 - run: git checkout HEAD^2 - - name: Run Tests + - name: Run Tests + env: + MAILGUN_API_KEY: ${{ secrets.MAILGUN_API_KEY }} + MAILGUN_DOMAIN: ${{ secrets.MAILGUN_DOMAIN }} + SENDGRID_API_KEY: ${{ secrets.SENDGRID_API_KEY }} + FCM_SERVER_KEY: ${{ secrets.FCM_SERVER_KEY }} + FCM_SERVER_TO: ${{ secrets.FCM_SERVER_TO }} + TWILIO_ACCOUNT_SID: ${{ secrets.TWILIO_ACCOUNT_SID }} + TWILIO_AUTH_TOKEN: ${{ secrets.TWILIO_AUTH_TOKEN }} + TWILIO_TO: ${{ secrets.TWILIO_TO }} + TWILIO_FROM: ${{ secrets.TWILIO_FROM }} + TELNYX_API_KEY: ${{ secrets.TELNYX_API_KEY }} + TELNYX_PUBLIC_KEY: ${{ secrets.TELNYX_PUBLIC_KEY }} + APNS_AUTHKEY_8KVVCLA3HL: ${{ secrets.APNS_AUTHKEY_8KVVCLA3HL }} + APNS_AUTH_ID: ${{ secrets.APNS_AUTH_ID }} + APNS_TEAM_ID: ${{ secrets.APNS_TEAM_ID }} + APNS_BUNDLE_ID: ${{ secrets.APNS_BUNDLE_ID }} + APNS_TO: ${{ secrets.APNS_TO }} + MSG_91_SENDER_ID: ${{ secrets.MSG_91_SENDER_ID }} + MSG_91_AUTH_KEY: ${{ secrets.MSG_91_AUTH_KEY }} + MSG_91_TO: ${{ secrets.MSG_91_TO }} + MSG_91_FROM: ${{ secrets.MSG_91_FROM }} + TEST_EMAIL: ${{ secrets.TEST_EMAIL }} + TEST_FROM_EMAIL: ${{ secrets.TEST_FROM_EMAIL }} + VONAGE_API_KEY: ${{ secrets.VONAGE_API_KEY }} + VONAGE_API_SECRET: ${{ secrets.VONAGE_API_SECRET }} + VONAGE_TO: ${{ secrets.VONAGE_TO }} + VONAGE_FROM: ${{ secrets.VONAGE_FROM }} run: | docker compose up -d --build sleep 5 diff --git a/.gitignore b/.gitignore index 24265e89..3640a16f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,97 @@ .idea vendor +Makefile +.envrc +.env +*.p8 + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### macOS Patch ### +# iCloud generated files +*.icloud + +### PHPUnit ### +# Covers PHPUnit +# Reference: https://phpunit.de/ + +# Generated files .phpunit.result.cache +.phpunit.cache + +# PHPUnit +/app/phpunit.xml +/phpunit.xml + +# Build data +/build/ + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +Thumbs.db:encryptable +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.toptal.com/developers/gitignore/api/macos,linux,windows,phpunit \ No newline at end of file diff --git a/README.md b/README.md index 445406d1..09fef071 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Install using composer: composer require utopia-php/messaging ``` -## Email +## Email ```php =5.5.0" }, "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "doctrine/annotations": "^1.2", + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", + "doctrine/annotations": "^1.2.6 || ^1.13.3", "php-parallel-lint/php-console-highlighter": "^1.0.0", "php-parallel-lint/php-parallel-lint": "^1.3.2", "phpcompatibility/php-compatibility": "^9.3.5", "roave/security-advisories": "dev-latest", - "squizlabs/php_codesniffer": "^3.6.2", - "yoast/phpunit-polyfills": "^1.0.0" + "squizlabs/php_codesniffer": "^3.7.1", + "yoast/phpunit-polyfills": "^1.0.4" }, "suggest": { "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", "league/oauth2-google": "Needed for Google XOAUTH2 authentication", "psr/log": "For optional PSR-3 debug logging", - "stevenmaguire/oauth2-microsoft": "Needed for Microsoft XOAUTH2 authentication", - "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)" + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" }, "type": "library", "autoload": { @@ -437,7 +439,7 @@ "description": "PHPMailer is a full-featured email creation and transfer class for PHP", "support": { "issues": "https://github.com/PHPMailer/PHPMailer/issues", - "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.6.4" + "source": "https://github.com/PHPMailer/PHPMailer/tree/v6.8.0" }, "funding": [ { @@ -445,7 +447,7 @@ "type": "github" } ], - "time": "2022-08-22T09:22:00+00:00" + "time": "2023-03-06T14:43:22+00:00" }, { "name": "phpunit/php-code-coverage", @@ -767,20 +769,20 @@ }, { "name": "phpunit/phpunit", - "version": "9.5.25", + "version": "9.6.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d" + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", - "reference": "3e6f90ca7e3d02025b1d147bd8d4a89fd4ca8a1d", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a6d351645c3fe5a30f5e86be6577d946af65a328", + "reference": "a6d351645c3fe5a30f5e86be6577d946af65a328", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.3.1", + "doctrine/instantiator": "^1.3.1 || ^2", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", @@ -809,8 +811,8 @@ "sebastian/version": "^3.0.2" }, "suggest": { - "ext-soap": "*", - "ext-xdebug": "*" + "ext-soap": "To be able to generate mocks based on WSDL files", + "ext-xdebug": "PHP extension that provides line coverage as well as branch and path coverage" }, "bin": [ "phpunit" @@ -818,7 +820,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "9.5-dev" + "dev-master": "9.6-dev" } }, "autoload": { @@ -849,7 +851,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.25" + "security": "https://github.com/sebastianbergmann/phpunit/security/policy", + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.10" }, "funding": [ { @@ -865,7 +868,7 @@ "type": "tidelift" } ], - "time": "2022-09-25T03:44:45+00:00" + "time": "2023-07-10T04:04:23+00:00" }, { "name": "sebastian/cli-parser", diff --git a/docker-compose.yml b/docker-compose.yml index 96054433..80d626d9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,13 +2,40 @@ version: '3.9' services: tests: + environment: + - MAILGUN_API_KEY + - MAILGUN_DOMAIN + - SENDGRID_API_KEY + - FCM_SERVER_KEY + - FCM_SERVER_TO + - TWILIO_ACCOUNT_SID + - TWILIO_AUTH_TOKEN + - TWILIO_TO + - TWILIO_FROM + - TELNYX_API_KEY + - TELNYX_PUBLIC_KEY + - APNS_AUTHKEY_8KVVCLA3HL + - APNS_AUTH_ID + - APNS_TEAM_ID + - APNS_BUNDLE_ID + - APNS_TO + - MSG_91_SENDER_ID + - MSG_91_AUTH_KEY + - MSG_91_TO + - MSG_91_FROM + - TEST_EMAIL + - TEST_FROM_EMAIL + - VONAGE_API_KEY + - VONAGE_API_SECRET + - VONAGE_TO + - VONAGE_FROM build: context: . volumes: - ./src:/usr/local/src/src - ./tests:/usr/local/src/tests - ./phpunit.xml:/usr/local/src/phpunit.xml - + maildev: image: appwrite/mailcatcher:1.0.0 ports: diff --git a/src/Utopia/Messaging/Adapters/Email/Mailgun.php b/src/Utopia/Messaging/Adapters/Email/Mailgun.php index ba64c906..4e3882e8 100644 --- a/src/Utopia/Messaging/Adapters/Email/Mailgun.php +++ b/src/Utopia/Messaging/Adapters/Email/Mailgun.php @@ -14,14 +14,25 @@ class Mailgun extends EmailAdapter public function __construct( private string $apiKey, private string $domain, + private bool $isEU = false ) { } + /** + * Get adapter name. + * + * @return string + */ public function getName(): string { return 'Mailgun'; } + /** + * Get adapter description. + * + * @return int + */ public function getMaxMessagesPerRequest(): int { return 1000; @@ -34,19 +45,26 @@ public function getMaxMessagesPerRequest(): int */ protected function process(Email $message): string { - return $this->request( + $usDomain = 'api.mailgun.net'; + $euDomain = 'api.eu.mailgun.net'; + + $domain = $this->isEU ? $euDomain : $usDomain; + + $response = $this->request( method: 'POST', - url: "https://api.mailgun.net/v3/{$this->domain}/messages", + url: "https://$domain/v3/{$this->domain}/messages", headers: [ 'Authorization: Basic '.base64_encode('api:'.$this->apiKey), ], body: \http_build_query([ - 'from' => $message->getFrom(), 'to' => \implode(',', $message->getTo()), + 'from' => $message->getFrom(), 'subject' => $message->getSubject(), 'text' => $message->isHtml() ? null : $message->getContent(), 'html' => $message->isHtml() ? $message->getContent() : null, ]), ); + + return $response; } } diff --git a/src/Utopia/Messaging/Adapters/Email/Sendgrid.php b/src/Utopia/Messaging/Adapters/Email/Sendgrid.php index 17745c48..e91c31cc 100644 --- a/src/Utopia/Messaging/Adapters/Email/Sendgrid.php +++ b/src/Utopia/Messaging/Adapters/Email/Sendgrid.php @@ -7,21 +7,42 @@ class Sendgrid extends EmailAdapter { - public function __construct( - private string $apiKey, - ) { + /** + * @param string $apiKey Your Sendgrid API key to authenticate with the API. + * @return void + */ + public function __construct(private string $apiKey) + { } + /** + * Get adapter name. + * + * @return string + */ public function getName(): string { return 'Sendgrid'; } + /** + * Get max messages per request. + * + * @return int + */ public function getMaxMessagesPerRequest(): int { return 1000; } + /** + * {@inheritdoc} + * + * @param Email $message + * @return string + * + * @throws Exception + */ protected function process(Email $message): string { return $this->request( diff --git a/src/Utopia/Messaging/Adapters/Push/APNS.php b/src/Utopia/Messaging/Adapters/Push/APNS.php new file mode 100644 index 00000000..56282d83 --- /dev/null +++ b/src/Utopia/Messaging/Adapters/Push/APNS.php @@ -0,0 +1,214 @@ + [ + 'alert' => [ + 'title' => $message->getTitle(), + 'body' => $message->getBody(), + ], + 'badge' => $message->getBadge(), + 'sound' => $message->getSound(), + 'data' => $message->getData(), + ], + ]; + + return \json_encode($this->notify($message->getTo(), $payload)); + } + + private function notify(array $to, array $payload): array + { + $headers = [ + 'authorization: bearer '.$this->generateJwt(), + 'apns-topic: '.$this->bundleId, + 'apns-push-type: alert', + ]; + + $sh = curl_share_init(); + + curl_share_setopt($sh, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT); + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_SHARE, $sh); + curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_2_0); + + curl_setopt_array($ch, [ + CURLOPT_PORT => 443, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => \json_encode($payload), + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 30, + CURLOPT_HEADER => true, + ]); + + $response = ''; + + $mh = curl_multi_init(); + $handles = []; + + // Create a handle for each request + foreach ($to as $token) { + curl_setopt($ch, CURLOPT_URL, $this->endpoint.'/3/device/'.$token); + + $handle = curl_copy_handle($ch); + curl_multi_add_handle($mh, $handle); + + $handles[] = $handle; + } + + $active = null; + $status = CURLM_OK; + + // Execute the handles + while ($active && $status == CURLM_OK) { + $status = curl_multi_exec($mh, $active); + } + + // Check each handle's result + $responses = []; + foreach ($handles as $ch) { + $urlInfo = curl_getinfo($ch); + $device = basename($urlInfo['url']); // Extracts deviceToken from the URL + + if (! curl_errno($ch)) { + $statusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $responses[] = [ + 'device' => $device, + 'status' => 'success', + 'statusCode' => $statusCode, + ]; + } else { + $responses[$device] = [ + 'status' => 'error', + 'error' => curl_error($ch), + ]; + } + + curl_multi_remove_handle($mh, $ch); + curl_close($ch); + } + + curl_multi_close($mh); + curl_share_close($sh); + + return $responses; + } + + private function formatResponse(string $response): array + { + $filtered = array_filter( + explode("\r\n", $response), + function ($value) { + return ! empty($value); + } + ); + + $result = []; + + foreach ($filtered as $value) { + if (str_contains($value, 'HTTP')) { + $result['status'] = trim(str_replace('HTTP/2 ', '', $value)); + + continue; + } + + $parts = explode(':', trim($value)); + + $result[$parts[0]] = $parts[1]; + } + + return $result; + } + + /** + * Generate JWT. + * + * @return string + * + * @throws Exception + */ + private function generateJwt(): string + { + $header = json_encode(['alg' => 'ES256', 'kid' => $this->authKeyId]); + $claims = json_encode([ + 'iss' => $this->teamId, + 'iat' => time(), + ]); + + // Replaces URL sensitive characters that could be the result of base64 encoding. + // Replace to _ to avoid any special handling. + $base64UrlHeader = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($header)); + $base64UrlClaims = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($claims)); + + if (! $this->authKey) { + throw new \Exception('Invalid private key'); + } + + $signature = ''; + $success = openssl_sign("$base64UrlHeader.$base64UrlClaims", $signature, $this->authKey, OPENSSL_ALGO_SHA256); + + if (! $success) { + throw new \Exception('Failed to sign JWT'); + } + + $base64UrlSignature = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($signature)); + + return "$base64UrlHeader.$base64UrlClaims.$base64UrlSignature"; + } +} diff --git a/src/Utopia/Messaging/Adapters/Push/FCM.php b/src/Utopia/Messaging/Adapters/Push/FCM.php index 3734b16d..8a4d8ac2 100644 --- a/src/Utopia/Messaging/Adapters/Push/FCM.php +++ b/src/Utopia/Messaging/Adapters/Push/FCM.php @@ -15,11 +15,21 @@ public function __construct( ) { } + /** + * Get adapter name. + * + * @return string + */ public function getName(): string { return 'FCM'; } + /** + * Get max messages per request. + * + * @return int + */ public function getMaxMessagesPerRequest(): int { return 1000; diff --git a/src/Utopia/Messaging/Adapters/SMS/Msg91.php b/src/Utopia/Messaging/Adapters/SMS/Msg91.php index 82b2527d..766832f8 100644 --- a/src/Utopia/Messaging/Adapters/SMS/Msg91.php +++ b/src/Utopia/Messaging/Adapters/SMS/Msg91.php @@ -58,4 +58,22 @@ protected function process(SMS $message): string ]), ); } + + private function addTemplate(string $content, string $senderId, string $templateName, string $smsType = 'NORMAL') + { + return $this->request( + method: 'POST', + url: 'https://control.msg91.com/api/v5/sms/addTemplate', + headers: [ + 'content-type: application/json', + "authkey: {$this->authKey}", + ], + body: \json_encode([ + 'template' => $content, + 'sender_id' => $senderId, + 'template_name' => $templateName, + 'smsType' => 'NORMAL', + ]) + ); + } } diff --git a/src/Utopia/Messaging/Adapters/SMS/Telesign.php b/src/Utopia/Messaging/Adapters/SMS/Telesign.php index d19d5876..6e77903e 100644 --- a/src/Utopia/Messaging/Adapters/SMS/Telesign.php +++ b/src/Utopia/Messaging/Adapters/SMS/Telesign.php @@ -37,10 +37,10 @@ public function getMaxMessagesPerRequest(): int */ protected function process(SMS $message): string { - $to = \array_map( - fn ($to) => \ltrim($to, '+'), + $to = $this->formatNumbers(\array_map( + fn ($to) => $to, $message->getTo() - ); + )); return $this->request( method: 'POST', @@ -50,8 +50,18 @@ protected function process(SMS $message): string ], body: \http_build_query([ 'template' => $message->getContent(), - 'recipients' => \implode(',', $to), + 'recipients' => $to, ]), ); } + + private function formatNumbers(array $numbers): string + { + $formatted = \array_map( + fn ($number) => $number.':'.\uniqid(), + $numbers + ); + + return implode(',', $formatted); + } } diff --git a/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php b/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php deleted file mode 100644 index a977ed79..00000000 --- a/src/Utopia/Messaging/Adapters/SMS/TwilioNotify.php +++ /dev/null @@ -1,53 +0,0 @@ -request( - method: 'POST', - url: "https://notify.twilio.com/v1/Services/{$this->serviceSid}/Notifications", - headers: [ - 'Authorization: Basic '.base64_encode("{$this->accountSid}:{$this->authToken}"), - ], - body: \http_build_query([ - 'Body' => $message->getContent(), - 'ToBinding' => \json_encode(\array_map( - fn ($to) => ['binding_type' => 'sms', 'address' => $to], - $message->getTo() - )), - ]), - ); - } -} diff --git a/src/Utopia/Messaging/Adapters/SMS/Vonage.php b/src/Utopia/Messaging/Adapters/SMS/Vonage.php index 2643a6b0..7ed16714 100644 --- a/src/Utopia/Messaging/Adapters/SMS/Vonage.php +++ b/src/Utopia/Messaging/Adapters/SMS/Vonage.php @@ -48,7 +48,7 @@ protected function process(SMS $message): string body: \http_build_query([ 'text' => $message->getContent(), 'from' => $message->getFrom(), - 'to' => \implode(',', $to), + 'to' => $to[0], //\implode(',', $to), 'api_key' => $this->apiKey, 'api_secret' => $this->apiSecret, ]), diff --git a/src/Utopia/Messaging/Message.php b/src/Utopia/Messaging/Message.php index 3b5e21ae..5c335d17 100644 --- a/src/Utopia/Messaging/Message.php +++ b/src/Utopia/Messaging/Message.php @@ -7,4 +7,7 @@ */ interface Message { + public function getTo(): array; + + public function getFrom(): ?string; } diff --git a/src/Utopia/Messaging/Messages/Push.php b/src/Utopia/Messaging/Messages/Push.php index d4a4333b..93b8f5d7 100644 --- a/src/Utopia/Messaging/Messages/Push.php +++ b/src/Utopia/Messaging/Messages/Push.php @@ -40,6 +40,11 @@ public function getTo(): array return $this->to; } + public function getFrom(): ?string + { + return null; + } + /** * @return string */ diff --git a/tests/e2e/Email/MailgunTest.php b/tests/e2e/Email/MailgunTest.php new file mode 100644 index 00000000..fed2489d --- /dev/null +++ b/tests/e2e/Email/MailgunTest.php @@ -0,0 +1,42 @@ +send($message)); + + $this->assertArrayHasKey('id', $result); + $this->assertArrayHasKey('message', $result); + $this->assertTrue(str_contains(strtolower($result['message']), 'queued')); + } +} diff --git a/tests/e2e/Email/SendgridTest.php b/tests/e2e/Email/SendgridTest.php new file mode 100644 index 00000000..9437b167 --- /dev/null +++ b/tests/e2e/Email/SendgridTest.php @@ -0,0 +1,34 @@ +send($message); + + $this->assertEquals($response, ''); + } +} diff --git a/tests/e2e/Push/APNSTest.php b/tests/e2e/Push/APNSTest.php new file mode 100644 index 00000000..cbd1a354 --- /dev/null +++ b/tests/e2e/Push/APNSTest.php @@ -0,0 +1,39 @@ +send($message)); + + foreach ($responses as $response) { + $this->assertEquals('success', $response->status); + } + } +} diff --git a/tests/e2e/Push/FCMTest.php b/tests/e2e/Push/FCMTest.php new file mode 100644 index 00000000..f7befabe --- /dev/null +++ b/tests/e2e/Push/FCMTest.php @@ -0,0 +1,37 @@ +send($message)); + + $this->assertNotEmpty($response); + $this->assertEquals(1, $response->success); + $this->assertEquals(0, $response->failure); + } +} diff --git a/tests/e2e/SMS/Msg91Test.php b/tests/e2e/SMS/Msg91Test.php new file mode 100644 index 00000000..df90f4c4 --- /dev/null +++ b/tests/e2e/SMS/Msg91Test.php @@ -0,0 +1,30 @@ +send($message); + // $result = \json_decode($response, true); + + // $this->assertEquals('success', $result['type']); + + $this->markTestSkipped('Msg91 requires business verification to use template and SMS api.'); + } +} diff --git a/tests/e2e/SMS/TelesignTest.php b/tests/e2e/SMS/TelesignTest.php new file mode 100644 index 00000000..30d2054a --- /dev/null +++ b/tests/e2e/SMS/TelesignTest.php @@ -0,0 +1,14 @@ +markTestSkipped('Telesign requires support/sales call in order to enable bulk SMS'); + } +} diff --git a/tests/e2e/SMS/TelnyxTest.php b/tests/e2e/SMS/TelnyxTest.php new file mode 100644 index 00000000..dc711498 --- /dev/null +++ b/tests/e2e/SMS/TelnyxTest.php @@ -0,0 +1,29 @@ +send($message), true); + + // $this->assertEquals('success', $result["type"]); + + $this->markTestSkipped('Telnyx had no testing numbers available at this time.'); + } +} diff --git a/tests/e2e/SMS/TwilioTest.php b/tests/e2e/SMS/TwilioTest.php new file mode 100644 index 00000000..7e45d620 --- /dev/null +++ b/tests/e2e/SMS/TwilioTest.php @@ -0,0 +1,32 @@ +send($message)); + + $this->assertNotEmpty($result); + $this->assertEquals($to[0], $result->to); + $this->assertEquals($from, $result->from); + $this->assertNull($result->error_message); + } +} diff --git a/tests/e2e/SMS/VonageTest.php b/tests/e2e/SMS/VonageTest.php new file mode 100644 index 00000000..02f89228 --- /dev/null +++ b/tests/e2e/SMS/VonageTest.php @@ -0,0 +1,34 @@ +send($message); + + $result = \json_decode($response, true); + + $this->assertArrayHasKey('messages', $result); + $this->assertEquals(1, count($result['messages'])); + $this->assertEquals('1', $result['message-count']); + } +}