Refactor scheduled APNs notifications in preparation for future development

This commit is contained in:
Jon Chambers
2022-08-12 10:47:49 -04:00
committed by GitHub
parent a44c18e9b7
commit a53a85d788
21 changed files with 200 additions and 213 deletions

View File

@@ -26,7 +26,7 @@ import org.whispersystems.textsecuregcm.storage.AccountsManager;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.util.Pair;
class ApnFallbackManagerTest {
class ApnPushNotificationSchedulerTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
@@ -36,7 +36,7 @@ class ApnFallbackManagerTest {
private APNSender apnSender;
private ApnFallbackManager apnFallbackManager;
private ApnPushNotificationScheduler apnPushNotificationScheduler;
private static final UUID ACCOUNT_UUID = UUID.randomUUID();
private static final String ACCOUNT_NUMBER = "+18005551234";
@@ -62,41 +62,43 @@ class ApnFallbackManagerTest {
apnSender = mock(APNSender.class);
apnFallbackManager = new ApnFallbackManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(), apnSender, accountsManager);
apnPushNotificationScheduler = new ApnPushNotificationScheduler(REDIS_CLUSTER_EXTENSION.getRedisCluster(), apnSender, accountsManager);
}
@Test
void testClusterInsert() {
final String endpoint = apnFallbackManager.getEndpointKey(account, device);
final String endpoint = apnPushNotificationScheduler.getEndpointKey(account, device);
assertTrue(apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 1).isEmpty());
assertTrue(
apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 1).isEmpty());
apnFallbackManager.schedule(account, device, System.currentTimeMillis() - 30_000);
apnPushNotificationScheduler.scheduleRecurringVoipNotification(account, device, System.currentTimeMillis() - 30_000);
final List<String> pendingDestinations = apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 2);
final List<String> pendingDestinations = apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 2);
assertEquals(1, pendingDestinations.size());
final Optional<Pair<String, Long>> maybeUuidAndDeviceId = ApnFallbackManager.getSeparated(
final Optional<Pair<String, Long>> maybeUuidAndDeviceId = ApnPushNotificationScheduler.getSeparated(
pendingDestinations.get(0));
assertTrue(maybeUuidAndDeviceId.isPresent());
assertEquals(ACCOUNT_UUID.toString(), maybeUuidAndDeviceId.get().first());
assertEquals(DEVICE_ID, (long) maybeUuidAndDeviceId.get().second());
assertTrue(apnFallbackManager.getPendingDestinations(SlotHash.getSlot(endpoint), 1).isEmpty());
assertTrue(
apnPushNotificationScheduler.getPendingDestinationsForRecurringVoipNotifications(SlotHash.getSlot(endpoint), 1).isEmpty());
}
@Test
void testProcessNextSlot() {
final ApnFallbackManager.NotificationWorker worker = apnFallbackManager.new NotificationWorker();
final ApnPushNotificationScheduler.NotificationWorker worker = apnPushNotificationScheduler.new NotificationWorker();
apnFallbackManager.schedule(account, device, System.currentTimeMillis() - 30_000);
apnPushNotificationScheduler.scheduleRecurringVoipNotification(account, device, System.currentTimeMillis() - 30_000);
final int slot = SlotHash.getSlot(apnFallbackManager.getEndpointKey(account, device));
final int slot = SlotHash.getSlot(apnPushNotificationScheduler.getEndpointKey(account, device));
final int previousSlot = (slot + SlotHash.SLOT_COUNT - 1) % SlotHash.SLOT_COUNT;
REDIS_CLUSTER_EXTENSION.getRedisCluster().withCluster(connection -> connection.sync()
.set(ApnFallbackManager.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(previousSlot)));
.set(ApnPushNotificationScheduler.NEXT_SLOT_TO_PERSIST_KEY, String.valueOf(previousSlot)));
assertEquals(1, worker.processNextSlot());

View File

@@ -26,7 +26,6 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.whispersystems.textsecuregcm.entities.MessageProtos;
import org.whispersystems.textsecuregcm.metrics.PushLatencyManager;
import org.whispersystems.textsecuregcm.storage.Account;
import org.whispersystems.textsecuregcm.storage.Device;
import org.whispersystems.textsecuregcm.storage.MessagesManager;

View File

@@ -0,0 +1,74 @@
/*
* Copyright 2013-2022 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.whispersystems.textsecuregcm.push;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.time.Clock;
import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicPushLatencyConfiguration;
import org.whispersystems.textsecuregcm.push.PushLatencyManager;
import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushRecord;
import org.whispersystems.textsecuregcm.push.PushLatencyManager.PushType;
import org.whispersystems.textsecuregcm.redis.RedisClusterExtension;
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
class PushLatencyManagerTest {
@RegisterExtension
static final RedisClusterExtension REDIS_CLUSTER_EXTENSION = RedisClusterExtension.builder().build();
private DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
@BeforeEach
void setUp() {
//noinspection unchecked
dynamicConfigurationManager = mock(DynamicConfigurationManager.class);
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
final DynamicPushLatencyConfiguration dynamicPushLatencyConfiguration = mock(DynamicPushLatencyConfiguration.class);
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
when(dynamicConfiguration.getPushLatencyConfiguration()).thenReturn(dynamicPushLatencyConfiguration);
when(dynamicPushLatencyConfiguration.getInstrumentedVersions()).thenReturn(Collections.emptyMap());
}
@ParameterizedTest
@ValueSource(booleans = {true, false})
void testTakeRecord(final boolean isVoip) throws ExecutionException, InterruptedException {
final UUID accountUuid = UUID.randomUUID();
final long deviceId = 1;
final Instant pushTimestamp = Instant.now();
final PushLatencyManager pushLatencyManager = new PushLatencyManager(REDIS_CLUSTER_EXTENSION.getRedisCluster(),
dynamicConfigurationManager, Clock.fixed(pushTimestamp, ZoneId.systemDefault()));
assertNull(pushLatencyManager.takePushRecord(accountUuid, deviceId).get());
pushLatencyManager.recordPushSent(accountUuid, deviceId, isVoip);
final PushRecord pushRecord = pushLatencyManager.takePushRecord(accountUuid, deviceId).get();
assertNotNull(pushRecord);
assertEquals(pushTimestamp, pushRecord.getTimestamp());
assertEquals(isVoip ? PushType.VOIP : PushType.STANDARD, pushRecord.getPushType());
assertNull(pushLatencyManager.takePushRecord(accountUuid, deviceId).get());
}
}

View File

@@ -14,6 +14,7 @@ import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.when;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -28,7 +29,8 @@ class PushNotificationManagerTest {
private AccountsManager accountsManager;
private APNSender apnSender;
private FcmSender fcmSender;
private ApnFallbackManager apnFallbackManager;
private ApnPushNotificationScheduler apnPushNotificationScheduler;
private PushLatencyManager pushLatencyManager;
private PushNotificationManager pushNotificationManager;
@@ -37,11 +39,13 @@ class PushNotificationManagerTest {
accountsManager = mock(AccountsManager.class);
apnSender = mock(APNSender.class);
fcmSender = mock(FcmSender.class);
apnFallbackManager = mock(ApnFallbackManager.class);
apnPushNotificationScheduler = mock(ApnPushNotificationScheduler.class);
pushLatencyManager = mock(PushLatencyManager.class);
AccountsHelper.setupMockUpdate(accountsManager);
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnFallbackManager);
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
apnPushNotificationScheduler, pushLatencyManager);
}
@Test
@@ -113,7 +117,7 @@ class PushNotificationManagerTest {
verifyNoInteractions(apnSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verifyNoInteractions(apnFallbackManager);
verifyNoInteractions(apnPushNotificationScheduler);
}
@Test
@@ -136,7 +140,7 @@ class PushNotificationManagerTest {
verifyNoInteractions(fcmSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verify(apnFallbackManager).schedule(account, device);
verify(apnPushNotificationScheduler).scheduleRecurringVoipNotification(account, device);
}
@Test
@@ -159,7 +163,7 @@ class PushNotificationManagerTest {
verify(accountsManager).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verifyNoInteractions(apnSender);
verifyNoInteractions(apnFallbackManager);
verifyNoInteractions(apnPushNotificationScheduler);
}
@Test
@@ -181,6 +185,22 @@ class PushNotificationManagerTest {
verifyNoInteractions(fcmSender);
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
verify(apnFallbackManager).cancel(account, device);
verify(apnPushNotificationScheduler).cancelRecurringVoipNotification(account, device);
}
@Test
void testHandleMessagesRetrieved() {
final UUID accountIdentifier = UUID.randomUUID();
final Account account = mock(Account.class);
final Device device = mock(Device.class);
final String userAgent = "User-Agent";
when(account.getUuid()).thenReturn(accountIdentifier);
when(device.getId()).thenReturn(Device.MASTER_ID);
pushNotificationManager.handleMessagesRetrieved(account, device, userAgent);
verify(pushLatencyManager).recordQueueRead(accountIdentifier, Device.MASTER_ID, userAgent);
verify(apnPushNotificationScheduler).cancelRecurringVoipNotification(account, device);
}
}