diff --git a/config/sample.yml b/config/sample.yml index 94168a539..27e6372ca 100644 --- a/config/sample.yml +++ b/config/sample.yml @@ -3,9 +3,12 @@ twilio: accountToken: number: localDomain: # The domain Twilio can call back to. + international: # Boolean specifying Twilio for international delivery # Optional. If specified, Nexmo will be used for non-US SMS and -# voice verification. +# voice verification if twilio.international is false. Otherwise, +# Nexmo, if specified, Nexmo will only be used as a fallback +# for failed Twilio deliveries. nexmo: apiKey: apiSecret: diff --git a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java index c60caf462..f6b2992b6 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java +++ b/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java @@ -16,6 +16,7 @@ */ package org.whispersystems.textsecuregcm; +import com.google.common.base.Optional; import com.yammer.dropwizard.Service; import com.yammer.dropwizard.config.Bootstrap; import com.yammer.dropwizard.config.Environment; @@ -29,6 +30,7 @@ import org.skife.jdbi.v2.DBI; import org.whispersystems.textsecuregcm.auth.AccountAuthenticator; import org.whispersystems.textsecuregcm.auth.FederatedPeerAuthenticator; import org.whispersystems.textsecuregcm.auth.MultiBasicAuthProvider; +import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration; import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.controllers.AttachmentController; import org.whispersystems.textsecuregcm.controllers.DirectoryController; @@ -45,7 +47,9 @@ import org.whispersystems.textsecuregcm.providers.MemcachedClientFactory; import org.whispersystems.textsecuregcm.providers.RedisClientFactory; import org.whispersystems.textsecuregcm.providers.RedisHealthCheck; import org.whispersystems.textsecuregcm.push.PushSender; -import org.whispersystems.textsecuregcm.sms.SenderFactory; +import org.whispersystems.textsecuregcm.sms.NexmoSmsSender; +import org.whispersystems.textsecuregcm.sms.SmsSender; +import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.Accounts; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -93,24 +97,26 @@ public class WhisperServerService extends Service { MemcachedClient memcachedClient = new MemcachedClientFactory(config.getMemcacheConfiguration()).getClient(); JedisPool redisClient = new RedisClientFactory(config.getRedisConfiguration()).getRedisClientPool(); - DirectoryManager directory = new DirectoryManager(redisClient); - PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, memcachedClient); - AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient); - AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager); - FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration()); - RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), memcachedClient); - SenderFactory senderFactory = new SenderFactory(config.getTwilioConfiguration(), config.getNexmoConfiguration()); - UrlSigner urlSigner = new UrlSigner(config.getS3Configuration()); - PushSender pushSender = new PushSender(config.getGcmConfiguration(), - config.getApnConfiguration(), - accountsManager, directory); + DirectoryManager directory = new DirectoryManager(redisClient); + PendingAccountsManager pendingAccountsManager = new PendingAccountsManager(pendingAccounts, memcachedClient); + AccountsManager accountsManager = new AccountsManager(accounts, directory, memcachedClient); + AccountAuthenticator accountAuthenticator = new AccountAuthenticator(accountsManager ); + FederatedClientManager federatedClientManager = new FederatedClientManager(config.getFederationConfiguration()); + RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), memcachedClient); + TwilioSmsSender twilioSmsSender = new TwilioSmsSender(config.getTwilioConfiguration()); + Optional nexmoSmsSender = initializeNexmoSmsSender(config.getNexmoConfiguration()); + SmsSender smsSender = new SmsSender(twilioSmsSender, nexmoSmsSender, config.getTwilioConfiguration().isInternational()); + UrlSigner urlSigner = new UrlSigner(config.getS3Configuration()); + PushSender pushSender = new PushSender(config.getGcmConfiguration(), + config.getApnConfiguration(), + accountsManager, directory); environment.addProvider(new MultiBasicAuthProvider<>(new FederatedPeerAuthenticator(config.getFederationConfiguration()), FederatedPeer.class, accountAuthenticator, Account.class, "WhisperServer")); - environment.addResource(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, senderFactory)); + environment.addResource(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, smsSender)); environment.addResource(new DirectoryController(rateLimiters, directory)); environment.addResource(new AttachmentController(rateLimiters, federatedClientManager, urlSigner)); environment.addResource(new KeysController(rateLimiters, keys, federatedClientManager)); @@ -133,6 +139,14 @@ public class WhisperServerService extends Service { } } + private Optional initializeNexmoSmsSender(NexmoConfiguration configuration) { + if (configuration == null) { + return Optional.absent(); + } else { + return Optional.of(new NexmoSmsSender(configuration)); + } + } + public static void main(String[] args) throws Exception { new WhisperServerService().run(args); } diff --git a/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java b/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java index d9831aa94..bba847b84 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java +++ b/src/main/java/org/whispersystems/textsecuregcm/configuration/TwilioConfiguration.java @@ -37,6 +37,9 @@ public class TwilioConfiguration { @JsonProperty private String localDomain; + @JsonProperty + private boolean international; + public String getAccountId() { return accountId; } @@ -52,4 +55,8 @@ public class TwilioConfiguration { public String getLocalDomain() { return localDomain; } + + public boolean isInternational() { + return international; + } } diff --git a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java index 190381bec..cb2ad62ff 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java +++ b/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java @@ -28,7 +28,7 @@ import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.entities.ApnRegistrationId; import org.whispersystems.textsecuregcm.entities.GcmRegistrationId; import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.sms.SenderFactory; +import org.whispersystems.textsecuregcm.sms.SmsSender; import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; @@ -61,17 +61,17 @@ public class AccountController { private final PendingAccountsManager pendingAccounts; private final AccountsManager accounts; private final RateLimiters rateLimiters; - private final SenderFactory senderFactory; + private final SmsSender smsSender; public AccountController(PendingAccountsManager pendingAccounts, AccountsManager accounts, RateLimiters rateLimiters, - SenderFactory smsSenderFactory) + SmsSender smsSenderFactory) { this.pendingAccounts = pendingAccounts; this.accounts = accounts; this.rateLimiters = rateLimiters; - this.senderFactory = smsSenderFactory; + this.smsSender = smsSenderFactory; } @Timed @@ -101,9 +101,9 @@ public class AccountController { pendingAccounts.store(number, verificationCode.getVerificationCode()); if (transport.equals("sms")) { - senderFactory.getSmsSender(number).deliverSmsVerification(number, verificationCode.getVerificationCodeDisplay()); + smsSender.deliverSmsVerification(number, verificationCode.getVerificationCodeDisplay()); } else if (transport.equals("voice")) { - senderFactory.getVoxSender(number).deliverVoxVerification(number, verificationCode.getVerificationCodeSpeech()); + smsSender.deliverVoxVerification(number, verificationCode.getVerificationCodeSpeech()); } return Response.ok().build(); @@ -190,8 +190,7 @@ public class AccountController { @Produces(MediaType.APPLICATION_XML) public Response getTwiml(@PathParam("code") String encodedVerificationText) { return Response.ok().entity(String.format(TwilioSmsSender.SAY_TWIML, - SenderFactory.VoxSender.VERIFICATION_TEXT + - encodedVerificationText)).build(); + encodedVerificationText)).build(); } private VerificationCode generateVerificationCode() { diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java index 78f1eb50f..fab421abd 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/NexmoSmsSender.java @@ -30,10 +30,7 @@ import java.net.URLConnection; import java.net.URLEncoder; import java.util.concurrent.TimeUnit; -import org.whispersystems.textsecuregcm.sms.SenderFactory.VoxSender; -import org.whispersystems.textsecuregcm.sms.SenderFactory.SmsSender; - -public class NexmoSmsSender implements SmsSender, VoxSender { +public class NexmoSmsSender { private final Meter smsMeter = Metrics.newMeter(NexmoSmsSender.class, "sms", "delivered", TimeUnit.MINUTES); private final Meter voxMeter = Metrics.newMeter(NexmoSmsSender.class, "vox", "delivered", TimeUnit.MINUTES); @@ -55,10 +52,9 @@ public class NexmoSmsSender implements SmsSender, VoxSender { this.number = config.getNumber(); } - @Override public void deliverSmsVerification(String destination, String verificationCode) throws IOException { URL url = new URL(String.format(NEXMO_SMS_URL, apiKey, apiSecret, number, destination, - URLEncoder.encode(SmsSender.VERIFICATION_TEXT + verificationCode, "UTF-8"))); + URLEncoder.encode(SmsSender.SMS_VERIFICATION_TEXT + verificationCode, "UTF-8"))); URLConnection connection = url.openConnection(); connection.setDoInput(true); @@ -70,10 +66,9 @@ public class NexmoSmsSender implements SmsSender, VoxSender { smsMeter.mark(); } - @Override public void deliverVoxVerification(String destination, String message) throws IOException { URL url = new URL(String.format(NEXMO_VOX_URL, apiKey, apiSecret, destination, - URLEncoder.encode(VoxSender.VERIFICATION_TEXT + message, "UTF-8"))); + URLEncoder.encode(SmsSender.VOX_VERIFICATION_TEXT + message, "UTF-8"))); URLConnection connection = url.openConnection(); connection.setDoInput(true); diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/SenderFactory.java b/src/main/java/org/whispersystems/textsecuregcm/sms/SenderFactory.java deleted file mode 100644 index 2926ca570..000000000 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/SenderFactory.java +++ /dev/null @@ -1,70 +0,0 @@ -/** - * Copyright (C) 2013 Open WhisperSystems - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ -package org.whispersystems.textsecuregcm.sms; - - -import com.google.common.base.Optional; -import org.whispersystems.textsecuregcm.configuration.NexmoConfiguration; -import org.whispersystems.textsecuregcm.configuration.TwilioConfiguration; - -import java.io.IOException; - -public class SenderFactory { - - private final TwilioSmsSender twilioSender; - private final Optional nexmoSender; - - public SenderFactory(TwilioConfiguration twilioConfig, NexmoConfiguration nexmoConfig) { - this.twilioSender = new TwilioSmsSender(twilioConfig); - - if (nexmoConfig != null) { - this.nexmoSender = Optional.of(new NexmoSmsSender(nexmoConfig)); - } else { - this.nexmoSender = Optional.absent(); - } - } - - public SmsSender getSmsSender(String number) { - if (nexmoSender.isPresent() && !isTwilioDestination(number)) { - return nexmoSender.get(); - } else { - return twilioSender; - } - } - - public VoxSender getVoxSender(String number) { - if (nexmoSender.isPresent()) { - return nexmoSender.get(); - } else { - return twilioSender; - } - } - - private boolean isTwilioDestination(String number) { - return number.length() == 12 && number.startsWith("+1"); - } - - public interface SmsSender { - public static final String VERIFICATION_TEXT = "Your TextSecure verification code: "; - public void deliverSmsVerification(String destination, String verificationCode) throws IOException; - } - - public interface VoxSender { - public static final String VERIFICATION_TEXT = "Your TextSecure verification code is: "; - public void deliverVoxVerification(String destination, String verificationCode) throws IOException; - } -} diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java new file mode 100644 index 000000000..4c5644d53 --- /dev/null +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/SmsSender.java @@ -0,0 +1,84 @@ +/** + * Copyright (C) 2013 Open WhisperSystems + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +package org.whispersystems.textsecuregcm.sms; + + +import com.google.common.base.Optional; +import com.twilio.sdk.TwilioRestException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +public class SmsSender { + + static final String SMS_VERIFICATION_TEXT = "Your TextSecure verification code: "; + static final String VOX_VERIFICATION_TEXT = "Your TextSecure verification code is: "; + + private final Logger logger = LoggerFactory.getLogger(SmsSender.class); + + private final TwilioSmsSender twilioSender; + private final Optional nexmoSender; + private final boolean isTwilioInternational; + + public SmsSender(TwilioSmsSender twilioSender, + Optional nexmoSender, + boolean isTwilioInternational) + { + this.isTwilioInternational = isTwilioInternational; + this.twilioSender = twilioSender; + this.nexmoSender = nexmoSender; + } + + public void deliverSmsVerification(String destination, String verificationCode) + throws IOException + { + if (!isTwilioDestination(destination) && nexmoSender.isPresent()) { + nexmoSender.get().deliverSmsVerification(destination, verificationCode); + } else { + try { + twilioSender.deliverSmsVerification(destination, verificationCode); + } catch (TwilioRestException e) { + logger.info("Twilio SMS Fallback", e); + if (nexmoSender.isPresent()) { + nexmoSender.get().deliverSmsVerification(destination, verificationCode); + } + } + } + } + + public void deliverVoxVerification(String destination, String verificationCode) + throws IOException + { + if (!isTwilioDestination(destination) && nexmoSender.isPresent()) { + nexmoSender.get().deliverVoxVerification(destination, verificationCode); + } else { + try { + twilioSender.deliverVoxVerification(destination, verificationCode); + } catch (TwilioRestException e) { + logger.info("Twilio Vox Fallback", e); + if (nexmoSender.isPresent()) { + nexmoSender.get().deliverVoxVerification(destination, verificationCode); + } + } + } + } + + private boolean isTwilioDestination(String number) { + return isTwilioInternational || number.length() == 12 && number.startsWith("+1"); + } +} diff --git a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java index 41b778ad0..f538c89fc 100644 --- a/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java +++ b/src/main/java/org/whispersystems/textsecuregcm/sms/TwilioSmsSender.java @@ -33,11 +33,11 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -public class TwilioSmsSender implements SenderFactory.SmsSender, SenderFactory.VoxSender { +public class TwilioSmsSender { public static final String SAY_TWIML = "\n" + "\n" + - " %s\n" + + " " + SmsSender.VOX_VERIFICATION_TEXT + "%s\n" + ""; private final Meter smsMeter = Metrics.newMeter(TwilioSmsSender.class, "sms", "delivered", TimeUnit.MINUTES); @@ -55,37 +55,39 @@ public class TwilioSmsSender implements SenderFactory.SmsSender, SenderFactory.V this.localDomain = config.getLocalDomain(); } - @Override public void deliverSmsVerification(String destination, String verificationCode) - throws IOException + throws IOException, TwilioRestException { + TwilioRestClient client = new TwilioRestClient(accountId, accountToken); + MessageFactory messageFactory = client.getAccount().getMessageFactory(); + List messageParams = new LinkedList<>(); + messageParams.add(new BasicNameValuePair("To", destination)); + messageParams.add(new BasicNameValuePair("From", number)); + messageParams.add(new BasicNameValuePair("Body", SmsSender.SMS_VERIFICATION_TEXT + verificationCode)); + try { - TwilioRestClient client = new TwilioRestClient(accountId, accountToken); - MessageFactory messageFactory = client.getAccount().getMessageFactory(); - List messageParams = new LinkedList<>(); - messageParams.add(new BasicNameValuePair("To", destination)); - messageParams.add(new BasicNameValuePair("From", number)); - messageParams.add(new BasicNameValuePair("Body", SenderFactory.SmsSender.VERIFICATION_TEXT + verificationCode)); messageFactory.create(messageParams); - } catch (TwilioRestException e) { - throw new IOException(e); + } catch (RuntimeException damnYouTwilio) { + throw new IOException(damnYouTwilio); } smsMeter.mark(); } - @Override - public void deliverVoxVerification(String destination, String verificationCode) throws IOException { + public void deliverVoxVerification(String destination, String verificationCode) + throws IOException, TwilioRestException + { + TwilioRestClient client = new TwilioRestClient(accountId, accountToken); + CallFactory callFactory = client.getAccount().getCallFactory(); + Map callParams = new HashMap<>(); + callParams.put("To", destination); + callParams.put("From", number); + callParams.put("Url", "https://" + localDomain + "/v1/accounts/voice/twiml/" + verificationCode); + try { - TwilioRestClient client = new TwilioRestClient(accountId, accountToken); - CallFactory callFactory = client.getAccount().getCallFactory(); - Map callParams = new HashMap<>(); - callParams.put("To", destination); - callParams.put("From", number); - callParams.put("Url", "https://" + localDomain + "/v1/accounts/voice/twiml/" + verificationCode); callFactory.create(callParams); - } catch (TwilioRestException e) { - throw new IOException(e); + } catch (RuntimeException damnYouTwilio) { + throw new IOException(damnYouTwilio); } voxMeter.mark(); diff --git a/src/test/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java b/src/test/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java index 8c1dd1d1f..fee459848 100644 --- a/src/test/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java +++ b/src/test/org/whispersystems/textsecuregcm/tests/controllers/AccountControllerTest.java @@ -8,8 +8,7 @@ import org.whispersystems.textsecuregcm.controllers.AccountController; import org.whispersystems.textsecuregcm.entities.AccountAttributes; import org.whispersystems.textsecuregcm.limits.RateLimiter; import org.whispersystems.textsecuregcm.limits.RateLimiters; -import org.whispersystems.textsecuregcm.sms.SenderFactory; -import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; +import org.whispersystems.textsecuregcm.sms.SmsSender; import org.whispersystems.textsecuregcm.storage.Account; import org.whispersystems.textsecuregcm.storage.AccountsManager; import org.whispersystems.textsecuregcm.storage.PendingAccountsManager; @@ -29,8 +28,7 @@ public class AccountControllerTest extends ResourceTest { private AccountsManager accountsManager = mock(AccountsManager.class ); private RateLimiters rateLimiters = mock(RateLimiters.class ); private RateLimiter rateLimiter = mock(RateLimiter.class ); - private TwilioSmsSender twilioSmsSender = mock(TwilioSmsSender.class ); - private SenderFactory senderFactory = mock(SenderFactory.class ); + private SmsSender smsSender = mock(SmsSender.class ); @Override protected void setUpResources() throws Exception { @@ -41,12 +39,11 @@ public class AccountControllerTest extends ResourceTest { when(rateLimiters.getVerifyLimiter()).thenReturn(rateLimiter); when(pendingAccountsManager.getCodeForNumber(SENDER)).thenReturn(Optional.of("1234")); - when(senderFactory.getSmsSender(SENDER)).thenReturn(twilioSmsSender); addResource(new AccountController(pendingAccountsManager, accountsManager, rateLimiters, - senderFactory)); + smsSender)); } @Test @@ -57,7 +54,7 @@ public class AccountControllerTest extends ResourceTest { assertThat(response.getStatus()).isEqualTo(200); - verify(twilioSmsSender).deliverSmsVerification(eq(SENDER), anyString()); + verify(smsSender).deliverSmsVerification(eq(SENDER), anyString()); } @Test diff --git a/src/test/org/whispersystems/textsecuregcm/tests/sms/DeliveryPreferenceTest.java b/src/test/org/whispersystems/textsecuregcm/tests/sms/DeliveryPreferenceTest.java new file mode 100644 index 000000000..e5fbf9516 --- /dev/null +++ b/src/test/org/whispersystems/textsecuregcm/tests/sms/DeliveryPreferenceTest.java @@ -0,0 +1,36 @@ +package org.whispersystems.textsecuregcm.tests.sms; + +import com.google.common.base.Optional; +import com.twilio.sdk.TwilioRestException; +import junit.framework.TestCase; +import org.whispersystems.textsecuregcm.sms.NexmoSmsSender; +import org.whispersystems.textsecuregcm.sms.SmsSender; +import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; + +import java.io.IOException; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +public class DeliveryPreferenceTest extends TestCase { + + private TwilioSmsSender twilioSender = mock(TwilioSmsSender.class); + private NexmoSmsSender nexmoSender = mock(NexmoSmsSender.class); + + public void testInternationalPreferenceOff() throws IOException, TwilioRestException { + SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), false); + + smsSender.deliverSmsVerification("+441112223333", "123-456"); + verify(nexmoSender).deliverSmsVerification("+441112223333", "123-456"); + verifyNoMoreInteractions(twilioSender); + } + + public void testInternationalPreferenceOn() throws IOException, TwilioRestException { + SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true); + + smsSender.deliverSmsVerification("+441112223333", "123-456"); + verify(twilioSender).deliverSmsVerification("+441112223333", "123-456"); + verifyNoMoreInteractions(nexmoSender); + } +} diff --git a/src/test/org/whispersystems/textsecuregcm/tests/sms/TwilioFallbackTest.java b/src/test/org/whispersystems/textsecuregcm/tests/sms/TwilioFallbackTest.java new file mode 100644 index 000000000..c27e0cf2d --- /dev/null +++ b/src/test/org/whispersystems/textsecuregcm/tests/sms/TwilioFallbackTest.java @@ -0,0 +1,43 @@ +package org.whispersystems.textsecuregcm.tests.sms; + +import com.google.common.base.Optional; +import com.twilio.sdk.TwilioRestException; +import junit.framework.TestCase; +import org.whispersystems.textsecuregcm.sms.NexmoSmsSender; +import org.whispersystems.textsecuregcm.sms.SmsSender; +import org.whispersystems.textsecuregcm.sms.TwilioSmsSender; + +import java.io.IOException; + +import static org.mockito.Matchers.anyString; +import static org.mockito.Mockito.*; + +public class TwilioFallbackTest extends TestCase { + + private NexmoSmsSender nexmoSender = mock(NexmoSmsSender.class ); + private TwilioSmsSender twilioSender = mock(TwilioSmsSender.class); + + @Override + protected void setUp() throws IOException, TwilioRestException { + doThrow(new TwilioRestException("foo", 404)).when(twilioSender).deliverSmsVerification(anyString(), anyString()); + doThrow(new TwilioRestException("bar", 405)).when(twilioSender).deliverVoxVerification(anyString(), anyString()); + } + + public void testNexmoSmsFallback() throws IOException, TwilioRestException { + SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true); + smsSender.deliverSmsVerification("+442223334444", "123-456"); + + verify(nexmoSender).deliverSmsVerification("+442223334444", "123-456"); + verify(twilioSender).deliverSmsVerification("+442223334444", "123-456"); + } + + public void testNexmoVoxFallback() throws IOException, TwilioRestException { + SmsSender smsSender = new SmsSender(twilioSender, Optional.of(nexmoSender), true); + smsSender.deliverVoxVerification("+442223334444", "123-456"); + + verify(nexmoSender).deliverVoxVerification("+442223334444", "123-456"); + verify(twilioSender).deliverVoxVerification("+442223334444", "123-456"); + } + + +}