Pull GCM/APN senders into service

// FREEBIE
This commit is contained in:
Moxie Marlinspike
2017-04-25 18:03:07 -07:00
parent 28939e7405
commit 189d95f4fa
19 changed files with 998 additions and 510 deletions

View File

@@ -0,0 +1,87 @@
package org.whispersystems.textsecuregcm.tests.push;
import com.google.common.base.Optional;
import com.notnoop.apns.ApnsService;
import org.junit.Before;
import org.junit.Test;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnMessage;
import org.whispersystems.textsecuregcm.push.TransientPushFailureException;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import java.util.Date;
import java.util.HashMap;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.*;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
public class APNSenderTest {
private static final String DESTINATION_NUMBER = "+14151231234";
private static final String DESTINATION_APN_ID = "foo";
private final AccountsManager accountsManager = mock(AccountsManager.class);
private final JedisPool jedisPool = mock(JedisPool.class);
private final Jedis jedis = mock(Jedis.class);
private final ApnsService voipService = mock(ApnsService.class);
private final ApnsService apnsService = mock(ApnsService.class);
private final Account destinationAccount = mock(Account.class);
private final Device destinationDevice = mock(Device.class );
@Before
public void setup() {
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
when(accountsManager.get(DESTINATION_NUMBER)).thenReturn(Optional.of(destinationAccount));
when(jedisPool.getResource()).thenReturn(jedis);
when(jedis.get("APN-" + DESTINATION_APN_ID)).thenReturn(DESTINATION_NUMBER + "." + 1);
when(voipService.getInactiveDevices()).thenReturn(new HashMap<String, Date>() {{
put(DESTINATION_APN_ID, new Date(System.currentTimeMillis()));
}});
when(apnsService.getInactiveDevices()).thenReturn(new HashMap<String, Date>());
}
@Test
public void testSendVoip() throws TransientPushFailureException {
APNSender apnSender = new APNSender(accountsManager, jedisPool, apnsService, voipService, false, false);
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, "message", true, 30);
apnSender.sendMessage(message);
verify(jedis, times(1)).set(eq("APN-" + DESTINATION_APN_ID.toLowerCase()), eq(DESTINATION_NUMBER + "." + 1));
verify(voipService, times(1)).push(eq(DESTINATION_APN_ID), eq(message.getMessage()), eq(new Date(30)));
verifyNoMoreInteractions(apnsService);
}
@Test
public void testSendApns() throws TransientPushFailureException {
APNSender apnSender = new APNSender(accountsManager, jedisPool, apnsService, voipService, false, false);
ApnMessage message = new ApnMessage(DESTINATION_APN_ID, DESTINATION_NUMBER, 1, "message", false, 30);
apnSender.sendMessage(message);
verify(jedis, times(1)).set(eq("APN-" + DESTINATION_APN_ID.toLowerCase()), eq(DESTINATION_NUMBER + "." + 1));
verify(apnsService, times(1)).push(eq(DESTINATION_APN_ID), eq(message.getMessage()), eq(new Date(30)));
verifyNoMoreInteractions(voipService);
}
@Test
public void testFeedbackUnregistered() {
APNSender apnSender = new APNSender(accountsManager, jedisPool, apnsService, voipService, false, false);
apnSender.checkFeedback();
verify(jedis, times(1)).get(eq("APN-" +DESTINATION_APN_ID));
verify(accountsManager, times(1)).get(eq(DESTINATION_NUMBER));
verify(destinationDevice, times(1)).setApnId(eq((String)null));
verify(accountsManager, times(1)).update(eq(destinationAccount));
}
}

View File

@@ -2,10 +2,10 @@ package org.whispersystems.textsecuregcm.tests.push;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.entities.ApnMessage;
import org.whispersystems.textsecuregcm.push.APNSender;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager;
import org.whispersystems.textsecuregcm.push.ApnFallbackManager.ApnFallbackTask;
import org.whispersystems.textsecuregcm.push.PushServiceClient;
import org.whispersystems.textsecuregcm.push.ApnMessage;
import org.whispersystems.textsecuregcm.storage.PubSubManager;
import org.whispersystems.textsecuregcm.storage.PubSubProtos;
import org.whispersystems.textsecuregcm.util.Util;
@@ -14,23 +14,21 @@ import org.whispersystems.textsecuregcm.websocket.WebsocketAddress;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
public class ApnFallbackManagerTest {
@Test
public void testFullFallback() throws Exception {
PushServiceClient pushServiceClient = mock(PushServiceClient.class);
PubSubManager pubSubManager = mock(PubSubManager.class);
WebsocketAddress address = new WebsocketAddress("+14152222223", 1L);
WebSocketConnectionInfo info = new WebSocketConnectionInfo(address);
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 1111);
ApnFallbackTask task = new ApnFallbackTask("foo", "voipfoo", message, 500, 0);
APNSender apnSender = mock(APNSender.class );
PubSubManager pubSubManager = mock(PubSubManager.class);
WebsocketAddress address = new WebsocketAddress("+14152222223", 1L);
WebSocketConnectionInfo info = new WebSocketConnectionInfo(address);
ApnMessage message = new ApnMessage("bar", "123", 1, "hmm", true, 1111);
ApnFallbackTask task = new ApnFallbackTask("foo", "voipfoo", message, 500, 0);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(pushServiceClient, pubSubManager);
ApnFallbackManager apnFallbackManager = new ApnFallbackManager(apnSender, pubSubManager);
apnFallbackManager.start();
apnFallbackManager.schedule(address, task);
@@ -38,7 +36,7 @@ public class ApnFallbackManagerTest {
Util.sleep(1100);
ArgumentCaptor<ApnMessage> captor = ArgumentCaptor.forClass(ApnMessage.class);
verify(pushServiceClient, times(2)).send(captor.capture());
verify(apnSender, times(2)).sendMessage(captor.capture());
verify(pubSubManager).unsubscribe(eq(info), eq(apnFallbackManager));
List<ApnMessage> arguments = captor.getAllValues();
@@ -56,7 +54,7 @@ public class ApnFallbackManagerTest {
@Test
public void testNoFallback() throws Exception {
PushServiceClient pushServiceClient = mock(PushServiceClient.class);
APNSender pushServiceClient = mock(APNSender.class);
PubSubManager pubSubManager = mock(PubSubManager.class);
WebsocketAddress address = new WebsocketAddress("+14152222222", 1);
WebSocketConnectionInfo info = new WebSocketConnectionInfo(address);

View File

@@ -0,0 +1,128 @@
package org.whispersystems.textsecuregcm.tests.push;
import com.google.common.base.Optional;
import com.google.common.util.concurrent.SettableFuture;
import org.junit.Test;
import org.mockito.Matchers;
import org.whispersystems.gcm.server.Message;
import org.whispersystems.gcm.server.Result;
import org.whispersystems.gcm.server.Sender;
import org.whispersystems.textsecuregcm.push.GCMSender;
import org.whispersystems.textsecuregcm.push.GcmMessage;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.*;
public class GCMSenderTest {
@Test
public void testSendMessage() {
AccountsManager accountsManager = mock(AccountsManager.class);
Sender sender = mock(Sender.class );
Result successResult = mock(Result.class );
SynchronousExecutorService executorService = new SynchronousExecutorService();
when(successResult.isInvalidRegistrationId()).thenReturn(false);
when(successResult.isUnregistered()).thenReturn(false);
when(successResult.hasCanonicalRegistrationId()).thenReturn(false);
when(successResult.isSuccess()).thenReturn(true);
GcmMessage message = new GcmMessage("foo", "+12223334444", 1, false);
GCMSender gcmSender = new GCMSender(accountsManager, sender, executorService);
SettableFuture<Result> successFuture = SettableFuture.create();
successFuture.set(successResult);
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(successFuture);
when(successResult.getContext()).thenReturn(message);
gcmSender.sendMessage(message);
verify(sender, times(1)).send(any(Message.class), eq(message));
}
@Test
public void testSendError() {
String destinationNumber = "+12223334444";
String gcmId = "foo";
AccountsManager accountsManager = mock(AccountsManager.class);
Sender sender = mock(Sender.class );
Result invalidResult = mock(Result.class );
SynchronousExecutorService executorService = new SynchronousExecutorService();
Account destinationAccount = mock(Account.class);
Device destinationDevice = mock(Device.class );
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
when(accountsManager.get(destinationNumber)).thenReturn(Optional.of(destinationAccount));
when(destinationDevice.getGcmId()).thenReturn(gcmId);
when(invalidResult.isInvalidRegistrationId()).thenReturn(true);
when(invalidResult.isUnregistered()).thenReturn(false);
when(invalidResult.hasCanonicalRegistrationId()).thenReturn(false);
when(invalidResult.isSuccess()).thenReturn(true);
GcmMessage message = new GcmMessage(gcmId, destinationNumber, 1, false);
GCMSender gcmSender = new GCMSender(accountsManager, sender, executorService);
SettableFuture<Result> invalidFuture = SettableFuture.create();
invalidFuture.set(invalidResult);
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(invalidFuture);
when(invalidResult.getContext()).thenReturn(message);
gcmSender.sendMessage(message);
verify(sender, times(1)).send(any(Message.class), eq(message));
verify(accountsManager, times(1)).get(eq(destinationNumber));
verify(accountsManager, times(1)).update(eq(destinationAccount));
verify(destinationDevice, times(1)).setGcmId(eq((String)null));
}
@Test
public void testCanonicalId() {
String destinationNumber = "+12223334444";
String gcmId = "foo";
String canonicalId = "bar";
AccountsManager accountsManager = mock(AccountsManager.class);
Sender sender = mock(Sender.class );
Result canonicalResult = mock(Result.class );
SynchronousExecutorService executorService = new SynchronousExecutorService();
Account destinationAccount = mock(Account.class);
Device destinationDevice = mock(Device.class );
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
when(accountsManager.get(destinationNumber)).thenReturn(Optional.of(destinationAccount));
when(destinationDevice.getGcmId()).thenReturn(gcmId);
when(canonicalResult.isInvalidRegistrationId()).thenReturn(false);
when(canonicalResult.isUnregistered()).thenReturn(false);
when(canonicalResult.hasCanonicalRegistrationId()).thenReturn(true);
when(canonicalResult.isSuccess()).thenReturn(false);
when(canonicalResult.getCanonicalRegistrationId()).thenReturn(canonicalId);
GcmMessage message = new GcmMessage(gcmId, destinationNumber, 1, false);
GCMSender gcmSender = new GCMSender(accountsManager, sender, executorService);
SettableFuture<Result> invalidFuture = SettableFuture.create();
invalidFuture.set(canonicalResult);
when(sender.send(any(Message.class), Matchers.anyObject())).thenReturn(invalidFuture);
when(canonicalResult.getContext()).thenReturn(message);
gcmSender.sendMessage(message);
verify(sender, times(1)).send(any(Message.class), eq(message));
verify(accountsManager, times(1)).get(eq(destinationNumber));
verify(accountsManager, times(1)).update(eq(destinationAccount));
verify(destinationDevice, times(1)).setGcmId(eq(canonicalId));
}
}

View File

@@ -0,0 +1,111 @@
package org.whispersystems.textsecuregcm.tests.util;
import com.google.common.util.concurrent.SettableFuture;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
public class SynchronousExecutorService implements ExecutorService {
private boolean shutdown = false;
@Override
public void shutdown() {
shutdown = true;
}
@Override
public List<Runnable> shutdownNow() {
shutdown = true;
return Collections.emptyList();
}
@Override
public boolean isShutdown() {
return shutdown;
}
@Override
public boolean isTerminated() {
return shutdown;
}
@Override
public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
return true;
}
@Override
public <T> Future<T> submit(Callable<T> task) {
SettableFuture<T> future = null;
try {
future = SettableFuture.create();
future.set(task.call());
} catch (Throwable e) {
future.setException(e);
}
return future;
}
@Override
public <T> Future<T> submit(Runnable task, T result) {
SettableFuture<T> future = SettableFuture.create();
task.run();
future.set(result);
return future;
}
@Override
public Future<?> submit(Runnable task) {
SettableFuture future = SettableFuture.create();
task.run();
future.set(null);
return future;
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
List<Future<T>> results = new LinkedList<>();
for (Callable<T> callable : tasks) {
SettableFuture<T> future = SettableFuture.create();
try {
future.set(callable.call());
} catch (Throwable e) {
future.setException(e);
}
results.add(future);
}
return results;
}
@Override
public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
return invokeAll(tasks);
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
return null;
}
@Override
public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
@Override
public void execute(Runnable command) {
command.run();
}
}