Send non-urgent push notifications with lower priority
This commit is contained in:
parent
5f6b66dad6
commit
b4281c5a70
|
@ -455,7 +455,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
|
||||||
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
|
APNSender apnSender = new APNSender(apnSenderExecutor, config.getApnConfiguration());
|
||||||
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
|
FcmSender fcmSender = new FcmSender(fcmSenderExecutor, config.getFcmConfiguration().credentials());
|
||||||
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, apnSender, accountsManager);
|
ApnPushNotificationScheduler apnPushNotificationScheduler = new ApnPushNotificationScheduler(pushSchedulerCluster, apnSender, accountsManager);
|
||||||
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnPushNotificationScheduler, pushLatencyManager);
|
PushNotificationManager pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender, apnPushNotificationScheduler, pushLatencyManager, dynamicConfigurationManager);
|
||||||
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
|
RateLimiters rateLimiters = new RateLimiters(config.getLimitsConfiguration(), rateLimitersCluster);
|
||||||
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
|
DynamicRateLimiters dynamicRateLimiters = new DynamicRateLimiters(rateLimitersCluster, dynamicConfigurationManager);
|
||||||
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
|
ProvisioningManager provisioningManager = new ProvisioningManager(pubSubManager);
|
||||||
|
|
|
@ -64,6 +64,10 @@ public class DynamicConfiguration {
|
||||||
@Valid
|
@Valid
|
||||||
DynamicMessagePersisterConfiguration messagePersister = new DynamicMessagePersisterConfiguration();
|
DynamicMessagePersisterConfiguration messagePersister = new DynamicMessagePersisterConfiguration();
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
@Valid
|
||||||
|
DynamicPushNotificationConfiguration pushNotifications = new DynamicPushNotificationConfiguration();
|
||||||
|
|
||||||
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
|
public Optional<DynamicExperimentEnrollmentConfiguration> getExperimentEnrollmentConfiguration(
|
||||||
final String experimentName) {
|
final String experimentName) {
|
||||||
return Optional.ofNullable(experiments.get(experimentName));
|
return Optional.ofNullable(experiments.get(experimentName));
|
||||||
|
@ -126,4 +130,8 @@ public class DynamicConfiguration {
|
||||||
public DynamicMessagePersisterConfiguration getMessagePersisterConfiguration() {
|
public DynamicMessagePersisterConfiguration getMessagePersisterConfiguration() {
|
||||||
return messagePersister;
|
return messagePersister;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DynamicPushNotificationConfiguration getPushNotificationConfiguration() {
|
||||||
|
return pushNotifications;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2013-2022 Signal Messenger, LLC
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.whispersystems.textsecuregcm.configuration.dynamic;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
|
|
||||||
|
public class DynamicPushNotificationConfiguration {
|
||||||
|
|
||||||
|
@JsonProperty
|
||||||
|
private boolean lowUrgencyEnabled = false;
|
||||||
|
|
||||||
|
public boolean isLowUrgencyEnabled() {
|
||||||
|
return lowUrgencyEnabled;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,6 +45,11 @@ public class APNSender implements Managed, PushNotificationSender {
|
||||||
.setLocalizedAlertMessage("APN_Message")
|
.setLocalizedAlertMessage("APN_Message")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
static final String APN_BACKGROUND_PAYLOAD = new SimpleApnsPayloadBuilder()
|
||||||
|
.setContentAvailable(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
static final Instant MAX_EXPIRATION = Instant.ofEpochMilli(Integer.MAX_VALUE * 1000L);
|
static final Instant MAX_EXPIRATION = Instant.ofEpochMilli(Integer.MAX_VALUE * 1000L);
|
||||||
|
|
||||||
|
@ -83,7 +88,13 @@ public class APNSender implements Managed, PushNotificationSender {
|
||||||
final boolean isVoip = notification.tokenType() == PushNotification.TokenType.APN_VOIP;
|
final boolean isVoip = notification.tokenType() == PushNotification.TokenType.APN_VOIP;
|
||||||
|
|
||||||
final String payload = switch (notification.notificationType()) {
|
final String payload = switch (notification.notificationType()) {
|
||||||
case NOTIFICATION -> isVoip ? APN_VOIP_NOTIFICATION_PAYLOAD : APN_NSE_NOTIFICATION_PAYLOAD;
|
case NOTIFICATION -> {
|
||||||
|
if (isVoip) {
|
||||||
|
yield APN_VOIP_NOTIFICATION_PAYLOAD;
|
||||||
|
} else {
|
||||||
|
yield notification.urgent() ? APN_NSE_NOTIFICATION_PAYLOAD : APN_BACKGROUND_PAYLOAD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case CHALLENGE -> new SimpleApnsPayloadBuilder()
|
case CHALLENGE -> new SimpleApnsPayloadBuilder()
|
||||||
.setSound("default")
|
.setSound("default")
|
||||||
|
@ -98,8 +109,19 @@ public class APNSender implements Managed, PushNotificationSender {
|
||||||
.build();
|
.build();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
final PushType pushType;
|
||||||
|
|
||||||
|
if (isVoip) {
|
||||||
|
pushType = PushType.VOIP;
|
||||||
|
} else {
|
||||||
|
pushType = notification.urgent() ? PushType.ALERT : PushType.BACKGROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
final DeliveryPriority deliveryPriority =
|
||||||
|
(notification.urgent() || isVoip) ? DeliveryPriority.IMMEDIATE : DeliveryPriority.CONSERVE_POWER;
|
||||||
|
|
||||||
final String collapseId =
|
final String collapseId =
|
||||||
(notification.notificationType() == PushNotification.NotificationType.NOTIFICATION && !isVoip)
|
(notification.notificationType() == PushNotification.NotificationType.NOTIFICATION && notification.urgent() && !isVoip)
|
||||||
? "incoming-message" : null;
|
? "incoming-message" : null;
|
||||||
|
|
||||||
final Instant start = Instant.now();
|
final Instant start = Instant.now();
|
||||||
|
@ -108,8 +130,8 @@ public class APNSender implements Managed, PushNotificationSender {
|
||||||
topic,
|
topic,
|
||||||
payload,
|
payload,
|
||||||
MAX_EXPIRATION,
|
MAX_EXPIRATION,
|
||||||
DeliveryPriority.IMMEDIATE,
|
deliveryPriority,
|
||||||
isVoip ? PushType.VOIP : PushType.ALERT,
|
pushType,
|
||||||
collapseId))
|
collapseId))
|
||||||
.whenComplete((response, throwable) -> {
|
.whenComplete((response, throwable) -> {
|
||||||
// Note that we deliberately run this small bit of non-blocking measurement on the "send notification" thread
|
// Note that we deliberately run this small bit of non-blocking measurement on the "send notification" thread
|
||||||
|
|
|
@ -250,7 +250,7 @@ public class ApnPushNotificationScheduler implements Managed {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
apnSender.sendNotification(new PushNotification(apnId, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device));
|
apnSender.sendNotification(new PushNotification(apnId, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device, true));
|
||||||
retry.increment();
|
retry.increment();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -263,8 +263,7 @@ public class ApnPushNotificationScheduler implements Managed {
|
||||||
getLastBackgroundNotificationTimestampKey(account, device),
|
getLastBackgroundNotificationTimestampKey(account, device),
|
||||||
String.valueOf(clock.millis()), new SetArgs().ex(BACKGROUND_NOTIFICATION_PERIOD)));
|
String.valueOf(clock.millis()), new SetArgs().ex(BACKGROUND_NOTIFICATION_PERIOD)));
|
||||||
|
|
||||||
// TODO Set priority, etc.
|
apnSender.sendNotification(new PushNotification(device.getApnId(), PushNotification.TokenType.APN, PushNotification.NotificationType.NOTIFICATION, null, account, device, false));
|
||||||
apnSender.sendNotification(new PushNotification(device.getApnId(), PushNotification.TokenType.APN, PushNotification.NotificationType.NOTIFICATION, null, account, device));
|
|
||||||
|
|
||||||
backgroundNotificationSentCounter.increment();
|
backgroundNotificationSentCounter.increment();
|
||||||
}
|
}
|
||||||
|
|
|
@ -84,7 +84,7 @@ public class FcmSender implements PushNotificationSender {
|
||||||
Message.Builder builder = Message.builder()
|
Message.Builder builder = Message.builder()
|
||||||
.setToken(pushNotification.deviceToken())
|
.setToken(pushNotification.deviceToken())
|
||||||
.setAndroidConfig(AndroidConfig.builder()
|
.setAndroidConfig(AndroidConfig.builder()
|
||||||
.setPriority(AndroidConfig.Priority.HIGH)
|
.setPriority(pushNotification.urgent() ? AndroidConfig.Priority.HIGH : AndroidConfig.Priority.NORMAL)
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
final String key = switch (pushNotification.notificationType()) {
|
final String key = switch (pushNotification.notificationType()) {
|
||||||
|
|
|
@ -50,7 +50,7 @@ public class MessageSender {
|
||||||
this.pushLatencyManager = pushLatencyManager;
|
this.pushLatencyManager = pushLatencyManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendMessage(final Account account, final Device device, final Envelope message, boolean online)
|
public void sendMessage(final Account account, final Device device, final Envelope message, final boolean online)
|
||||||
throws NotPushRegisteredException {
|
throws NotPushRegisteredException {
|
||||||
|
|
||||||
final String channel;
|
final String channel;
|
||||||
|
@ -83,7 +83,7 @@ public class MessageSender {
|
||||||
|
|
||||||
if (!clientPresent) {
|
if (!clientPresent) {
|
||||||
try {
|
try {
|
||||||
pushNotificationManager.sendNewMessageNotification(account, device.getId());
|
pushNotificationManager.sendNewMessageNotification(account, device.getId(), message.getUrgent());
|
||||||
|
|
||||||
final boolean useVoip = StringUtils.isNotBlank(device.getVoipApnId());
|
final boolean useVoip = StringUtils.isNotBlank(device.getVoipApnId());
|
||||||
RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId(), useVoip));
|
RedisOperation.unchecked(() -> pushLatencyManager.recordPushSent(account.getUuid(), device.getId(), useVoip));
|
||||||
|
|
|
@ -14,7 +14,8 @@ public record PushNotification(String deviceToken,
|
||||||
NotificationType notificationType,
|
NotificationType notificationType,
|
||||||
@Nullable String data,
|
@Nullable String data,
|
||||||
@Nullable Account destination,
|
@Nullable Account destination,
|
||||||
@Nullable Device destinationDevice) {
|
@Nullable Device destinationDevice,
|
||||||
|
boolean urgent) {
|
||||||
|
|
||||||
public enum NotificationType {
|
public enum NotificationType {
|
||||||
NOTIFICATION, CHALLENGE, RATE_LIMIT_CHALLENGE
|
NOTIFICATION, CHALLENGE, RATE_LIMIT_CHALLENGE
|
||||||
|
|
|
@ -13,10 +13,12 @@ import io.micrometer.core.instrument.Tags;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.whispersystems.textsecuregcm.configuration.dynamic.DynamicConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.redis.RedisOperation;
|
import org.whispersystems.textsecuregcm.redis.RedisOperation;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.util.Pair;
|
import org.whispersystems.textsecuregcm.util.Pair;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
@ -27,6 +29,7 @@ public class PushNotificationManager {
|
||||||
private final FcmSender fcmSender;
|
private final FcmSender fcmSender;
|
||||||
private final ApnPushNotificationScheduler apnPushNotificationScheduler;
|
private final ApnPushNotificationScheduler apnPushNotificationScheduler;
|
||||||
private final PushLatencyManager pushLatencyManager;
|
private final PushLatencyManager pushLatencyManager;
|
||||||
|
private final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager;
|
||||||
|
|
||||||
private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification");
|
private static final String SENT_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "sentPushNotification");
|
||||||
private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification");
|
private static final String FAILED_NOTIFICATION_COUNTER_NAME = name(PushNotificationManager.class, "failedPushNotification");
|
||||||
|
@ -37,25 +40,31 @@ public class PushNotificationManager {
|
||||||
final APNSender apnSender,
|
final APNSender apnSender,
|
||||||
final FcmSender fcmSender,
|
final FcmSender fcmSender,
|
||||||
final ApnPushNotificationScheduler apnPushNotificationScheduler,
|
final ApnPushNotificationScheduler apnPushNotificationScheduler,
|
||||||
final PushLatencyManager pushLatencyManager) {
|
final PushLatencyManager pushLatencyManager,
|
||||||
|
final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager) {
|
||||||
|
|
||||||
this.accountsManager = accountsManager;
|
this.accountsManager = accountsManager;
|
||||||
this.apnSender = apnSender;
|
this.apnSender = apnSender;
|
||||||
this.fcmSender = fcmSender;
|
this.fcmSender = fcmSender;
|
||||||
this.apnPushNotificationScheduler = apnPushNotificationScheduler;
|
this.apnPushNotificationScheduler = apnPushNotificationScheduler;
|
||||||
this.pushLatencyManager = pushLatencyManager;
|
this.pushLatencyManager = pushLatencyManager;
|
||||||
|
this.dynamicConfigurationManager = dynamicConfigurationManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendNewMessageNotification(final Account destination, final long destinationDeviceId) throws NotPushRegisteredException {
|
public void sendNewMessageNotification(final Account destination, final long destinationDeviceId, final boolean urgent) throws NotPushRegisteredException {
|
||||||
final Device device = destination.getDevice(destinationDeviceId).orElseThrow(NotPushRegisteredException::new);
|
final Device device = destination.getDevice(destinationDeviceId).orElseThrow(NotPushRegisteredException::new);
|
||||||
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
|
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
|
||||||
|
|
||||||
|
final boolean effectiveUrgent =
|
||||||
|
dynamicConfigurationManager.getConfiguration().getPushNotificationConfiguration().isLowUrgencyEnabled() ?
|
||||||
|
urgent : true;
|
||||||
|
|
||||||
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
|
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
|
||||||
PushNotification.NotificationType.NOTIFICATION, null, destination, device));
|
PushNotification.NotificationType.NOTIFICATION, null, destination, device, effectiveUrgent));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendRegistrationChallengeNotification(final String deviceToken, final PushNotification.TokenType tokenType, final String challengeToken) {
|
public void sendRegistrationChallengeNotification(final String deviceToken, final PushNotification.TokenType tokenType, final String challengeToken) {
|
||||||
sendNotification(new PushNotification(deviceToken, tokenType, PushNotification.NotificationType.CHALLENGE, challengeToken, null, null));
|
sendNotification(new PushNotification(deviceToken, tokenType, PushNotification.NotificationType.CHALLENGE, challengeToken, null, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void sendRateLimitChallengeNotification(final Account destination, final String challengeToken)
|
public void sendRateLimitChallengeNotification(final Account destination, final String challengeToken)
|
||||||
|
@ -65,7 +74,7 @@ public class PushNotificationManager {
|
||||||
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
|
final Pair<String, PushNotification.TokenType> tokenAndType = getToken(device);
|
||||||
|
|
||||||
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
|
sendNotification(new PushNotification(tokenAndType.first(), tokenAndType.second(),
|
||||||
PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, destination, device));
|
PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, destination, device, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handleMessagesRetrieved(final Account account, final Device device, final String userAgent) {
|
public void handleMessagesRetrieved(final Account account, final Device device, final String userAgent) {
|
||||||
|
@ -92,44 +101,55 @@ public class PushNotificationManager {
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
void sendNotification(final PushNotification pushNotification) {
|
void sendNotification(final PushNotification pushNotification) {
|
||||||
final PushNotificationSender sender = switch (pushNotification.tokenType()) {
|
if (pushNotification.tokenType() == PushNotification.TokenType.APN && !pushNotification.urgent()) {
|
||||||
case FCM -> fcmSender;
|
// APNs imposes a per-device limit on background push notifications; schedule a notification for some time in the
|
||||||
case APN, APN_VOIP -> apnSender;
|
// future (possibly even now!) rather than sending a notification directly
|
||||||
};
|
apnPushNotificationScheduler.scheduleBackgroundNotification(pushNotification.destination(),
|
||||||
|
pushNotification.destinationDevice());
|
||||||
|
} else {
|
||||||
|
final PushNotificationSender sender = switch (pushNotification.tokenType()) {
|
||||||
|
case FCM -> fcmSender;
|
||||||
|
case APN, APN_VOIP -> apnSender;
|
||||||
|
};
|
||||||
|
|
||||||
sender.sendNotification(pushNotification).whenComplete((result, throwable) -> {
|
sender.sendNotification(pushNotification).whenComplete((result, throwable) -> {
|
||||||
if (throwable == null) {
|
if (throwable == null) {
|
||||||
Tags tags = Tags.of("tokenType", pushNotification.tokenType().name(),
|
Tags tags = Tags.of("tokenType", pushNotification.tokenType().name(),
|
||||||
"notificationType", pushNotification.notificationType().name(),
|
"notificationType", pushNotification.notificationType().name(),
|
||||||
"accepted", String.valueOf(result.accepted()),
|
"urgent", String.valueOf(pushNotification.urgent()),
|
||||||
"unregistered", String.valueOf(result.unregistered()));
|
"accepted", String.valueOf(result.accepted()),
|
||||||
|
"unregistered", String.valueOf(result.unregistered()));
|
||||||
|
|
||||||
if (StringUtils.isNotBlank(result.errorCode())) {
|
if (StringUtils.isNotBlank(result.errorCode())) {
|
||||||
tags = tags.and("errorCode", result.errorCode());
|
tags = tags.and("errorCode", result.errorCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
Metrics.counter(SENT_NOTIFICATION_COUNTER_NAME, tags).increment();
|
||||||
|
|
||||||
|
if (result.unregistered() && pushNotification.destination() != null
|
||||||
|
&& pushNotification.destinationDevice() != null) {
|
||||||
|
handleDeviceUnregistered(pushNotification.destination(), pushNotification.destinationDevice());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.accepted() &&
|
||||||
|
pushNotification.tokenType() == PushNotification.TokenType.APN_VOIP &&
|
||||||
|
pushNotification.notificationType() == PushNotification.NotificationType.NOTIFICATION &&
|
||||||
|
pushNotification.destination() != null &&
|
||||||
|
pushNotification.destinationDevice() != null) {
|
||||||
|
|
||||||
|
RedisOperation.unchecked(
|
||||||
|
() -> apnPushNotificationScheduler.scheduleRecurringVoipNotification(pushNotification.destination(),
|
||||||
|
pushNotification.destinationDevice()));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug("Failed to deliver {} push notification to {} ({})",
|
||||||
|
pushNotification.notificationType(), pushNotification.deviceToken(), pushNotification.tokenType(),
|
||||||
|
throwable);
|
||||||
|
|
||||||
|
Metrics.counter(FAILED_NOTIFICATION_COUNTER_NAME, "cause", throwable.getClass().getSimpleName()).increment();
|
||||||
}
|
}
|
||||||
|
});
|
||||||
Metrics.counter(SENT_NOTIFICATION_COUNTER_NAME, tags).increment();
|
}
|
||||||
|
|
||||||
if (result.unregistered() && pushNotification.destination() != null && pushNotification.destinationDevice() != null) {
|
|
||||||
handleDeviceUnregistered(pushNotification.destination(), pushNotification.destinationDevice());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.accepted() &&
|
|
||||||
pushNotification.tokenType() == PushNotification.TokenType.APN_VOIP &&
|
|
||||||
pushNotification.notificationType() == PushNotification.NotificationType.NOTIFICATION &&
|
|
||||||
pushNotification.destination() != null &&
|
|
||||||
pushNotification.destinationDevice() != null) {
|
|
||||||
|
|
||||||
RedisOperation.unchecked(() -> apnPushNotificationScheduler.scheduleRecurringVoipNotification(pushNotification.destination(),
|
|
||||||
pushNotification.destinationDevice()));
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.debug("Failed to deliver {} push notification to {} ({})",
|
|
||||||
pushNotification.notificationType(), pushNotification.deviceToken(), pushNotification.tokenType(), throwable);
|
|
||||||
|
|
||||||
Metrics.counter(FAILED_NOTIFICATION_COUNTER_NAME, "cause", throwable.getClass().getSimpleName()).increment();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleDeviceUnregistered(final Account account, final Device device) {
|
private void handleDeviceUnregistered(final Account account, final Device device) {
|
||||||
|
|
|
@ -52,7 +52,8 @@ public class ReceiptSender {
|
||||||
.setSourceDevice((int) sourceDeviceId)
|
.setSourceDevice((int) sourceDeviceId)
|
||||||
.setDestinationUuid(destinationUuid.toString())
|
.setDestinationUuid(destinationUuid.toString())
|
||||||
.setTimestamp(messageId)
|
.setTimestamp(messageId)
|
||||||
.setType(Envelope.Type.SERVER_DELIVERY_RECEIPT);
|
.setType(Envelope.Type.SERVER_DELIVERY_RECEIPT)
|
||||||
|
.setUrgent(false);
|
||||||
|
|
||||||
return CompletableFuture.runAsync(() -> {
|
return CompletableFuture.runAsync(() -> {
|
||||||
for (final Device destinationDevice : destinationAccount.getDevices()) {
|
for (final Device destinationDevice : destinationAccount.getDevices()) {
|
||||||
|
|
|
@ -110,7 +110,9 @@ public class ChangeNumberManager {
|
||||||
.setSourceUuid(sourceAndDestinationAccount.getUuid().toString())
|
.setSourceUuid(sourceAndDestinationAccount.getUuid().toString())
|
||||||
.setSourceDevice((int) Device.MASTER_ID)
|
.setSourceDevice((int) Device.MASTER_ID)
|
||||||
.setUpdatedPni(sourceAndDestinationAccount.getPhoneNumberIdentifier().toString())
|
.setUpdatedPni(sourceAndDestinationAccount.getPhoneNumberIdentifier().toString())
|
||||||
|
.setUrgent(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
messageSender.sendMessage(sourceAndDestinationAccount, destinationDevice.get(), envelope, false);
|
messageSender.sendMessage(sourceAndDestinationAccount, destinationDevice.get(), envelope, false);
|
||||||
} catch (NotPushRegisteredException e) {
|
} catch (NotPushRegisteredException e) {
|
||||||
logger.debug("Not registered", e);
|
logger.debug("Not registered", e);
|
||||||
|
|
|
@ -96,7 +96,7 @@ public class AuthenticatedConnectListener implements WebSocketConnectListener {
|
||||||
|
|
||||||
if (messagesManager.hasCachedMessages(auth.getAccount().getUuid(), device.getId())) {
|
if (messagesManager.hasCachedMessages(auth.getAccount().getUuid(), device.getId())) {
|
||||||
try {
|
try {
|
||||||
pushNotificationManager.sendNewMessageNotification(auth.getAccount(), device.getId());
|
pushNotificationManager.sendNewMessageNotification(auth.getAccount(), device.getId(), true);
|
||||||
} catch (NotPushRegisteredException ignored) {
|
} catch (NotPushRegisteredException ignored) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,18 @@ import com.eatthepath.pushy.apns.ApnsClient;
|
||||||
import com.eatthepath.pushy.apns.ApnsPushNotification;
|
import com.eatthepath.pushy.apns.ApnsPushNotification;
|
||||||
import com.eatthepath.pushy.apns.DeliveryPriority;
|
import com.eatthepath.pushy.apns.DeliveryPriority;
|
||||||
import com.eatthepath.pushy.apns.PushNotificationResponse;
|
import com.eatthepath.pushy.apns.PushNotificationResponse;
|
||||||
|
import com.eatthepath.pushy.apns.PushType;
|
||||||
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
|
import com.eatthepath.pushy.apns.util.SimpleApnsPushNotification;
|
||||||
import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture;
|
import com.eatthepath.pushy.apns.util.concurrent.PushNotificationFuture;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import org.apache.commons.lang3.RandomStringUtils;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.stubbing.Answer;
|
import org.mockito.stubbing.Answer;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
|
@ -33,45 +37,51 @@ import org.whispersystems.textsecuregcm.tests.util.SynchronousExecutorService;
|
||||||
|
|
||||||
class APNSenderTest {
|
class APNSenderTest {
|
||||||
|
|
||||||
private static final String DESTINATION_APN_ID = "foo";
|
private static final String DESTINATION_DEVICE_TOKEN = RandomStringUtils.randomAlphanumeric(32);
|
||||||
|
private static final String BUNDLE_ID = "org.signal.test";
|
||||||
|
|
||||||
private Account destinationAccount;
|
private Account destinationAccount;
|
||||||
private Device destinationDevice;
|
private Device destinationDevice;
|
||||||
|
|
||||||
private ApnsClient apnsClient;
|
private ApnsClient apnsClient;
|
||||||
|
private APNSender apnSender;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() {
|
void setup() {
|
||||||
destinationAccount = mock(Account.class);
|
destinationAccount = mock(Account.class);
|
||||||
destinationDevice = mock(Device.class);
|
destinationDevice = mock(Device.class);
|
||||||
|
|
||||||
apnsClient = mock(ApnsClient.class);
|
apnsClient = mock(ApnsClient.class);
|
||||||
|
apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, BUNDLE_ID);
|
||||||
|
|
||||||
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
|
when(destinationAccount.getDevice(1)).thenReturn(Optional.of(destinationDevice));
|
||||||
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
|
when(destinationDevice.getApnId()).thenReturn(DESTINATION_DEVICE_TOKEN);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
void testSendVoip() {
|
@ValueSource(booleans = {true, false})
|
||||||
|
void testSendVoip(final boolean urgent) {
|
||||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||||
when(response.isAccepted()).thenReturn(true);
|
when(response.isAccepted()).thenReturn(true);
|
||||||
|
|
||||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
.thenAnswer(
|
||||||
|
(Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
||||||
|
|
||||||
PushNotification pushNotification = new PushNotification(DESTINATION_APN_ID, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice);
|
PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN_VOIP,
|
||||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, "foo");
|
PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, urgent);
|
||||||
|
|
||||||
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
||||||
|
|
||||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||||
verify(apnsClient).sendNotification(notification.capture());
|
verify(apnsClient).sendNotification(notification.capture());
|
||||||
|
|
||||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_DEVICE_TOKEN);
|
||||||
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
||||||
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
||||||
|
// Delivery priority should always be `IMMEDIATE` for VOIP notifications
|
||||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||||
assertThat(notification.getValue().getTopic()).isEqualTo("foo.voip");
|
assertThat(notification.getValue().getTopic()).isEqualTo(BUNDLE_ID + ".voip");
|
||||||
|
|
||||||
assertThat(result.accepted()).isTrue();
|
assertThat(result.accepted()).isTrue();
|
||||||
assertThat(result.errorCode()).isNull();
|
assertThat(result.errorCode()).isNull();
|
||||||
|
@ -80,27 +90,41 @@ class APNSenderTest {
|
||||||
verifyNoMoreInteractions(apnsClient);
|
verifyNoMoreInteractions(apnsClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
void testSendApns() {
|
@ValueSource(booleans = {true, false})
|
||||||
|
void testSendApns(final boolean urgent) {
|
||||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||||
when(response.isAccepted()).thenReturn(true);
|
when(response.isAccepted()).thenReturn(true);
|
||||||
|
|
||||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
.thenAnswer(
|
||||||
|
(Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
||||||
|
|
||||||
PushNotification pushNotification = new PushNotification(DESTINATION_APN_ID, PushNotification.TokenType.APN, PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice);
|
PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN,
|
||||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, "foo");
|
PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, urgent);
|
||||||
|
|
||||||
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
||||||
|
|
||||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||||
verify(apnsClient).sendNotification(notification.capture());
|
verify(apnsClient).sendNotification(notification.capture());
|
||||||
|
|
||||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_DEVICE_TOKEN);
|
||||||
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
||||||
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_NSE_NOTIFICATION_PAYLOAD);
|
assertThat(notification.getValue().getPayload())
|
||||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
.isEqualTo(urgent ? APNSender.APN_NSE_NOTIFICATION_PAYLOAD : APNSender.APN_BACKGROUND_PAYLOAD);
|
||||||
assertThat(notification.getValue().getTopic()).isEqualTo("foo");
|
|
||||||
|
assertThat(notification.getValue().getPriority())
|
||||||
|
.isEqualTo(urgent ? DeliveryPriority.IMMEDIATE : DeliveryPriority.CONSERVE_POWER);
|
||||||
|
|
||||||
|
assertThat(notification.getValue().getTopic()).isEqualTo(BUNDLE_ID);
|
||||||
|
assertThat(notification.getValue().getPushType())
|
||||||
|
.isEqualTo(urgent ? PushType.ALERT : PushType.BACKGROUND);
|
||||||
|
|
||||||
|
if (urgent) {
|
||||||
|
assertThat(notification.getValue().getCollapseId()).isNotNull();
|
||||||
|
} else {
|
||||||
|
assertThat(notification.getValue().getCollapseId()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
assertThat(result.accepted()).isTrue();
|
assertThat(result.accepted()).isTrue();
|
||||||
assertThat(result.errorCode()).isNull();
|
assertThat(result.errorCode()).isNull();
|
||||||
|
@ -110,19 +134,19 @@ class APNSenderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testUnregisteredUser() throws Exception {
|
void testUnregisteredUser() {
|
||||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||||
when(response.isAccepted()).thenReturn(false);
|
when(response.isAccepted()).thenReturn(false);
|
||||||
when(response.getRejectionReason()).thenReturn(Optional.of("Unregistered"));
|
when(response.getRejectionReason()).thenReturn(Optional.of("Unregistered"));
|
||||||
|
|
||||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
.thenAnswer(
|
||||||
|
(Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
||||||
|
|
||||||
|
PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN_VOIP,
|
||||||
|
PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, true);
|
||||||
|
|
||||||
PushNotification pushNotification = new PushNotification(DESTINATION_APN_ID, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice);
|
when(destinationDevice.getApnId()).thenReturn(DESTINATION_DEVICE_TOKEN);
|
||||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, "foo");
|
|
||||||
|
|
||||||
when(destinationDevice.getApnId()).thenReturn(DESTINATION_APN_ID);
|
|
||||||
when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(11));
|
when(destinationDevice.getPushTimestamp()).thenReturn(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(11));
|
||||||
|
|
||||||
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
||||||
|
@ -130,7 +154,7 @@ class APNSenderTest {
|
||||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||||
verify(apnsClient).sendNotification(notification.capture());
|
verify(apnsClient).sendNotification(notification.capture());
|
||||||
|
|
||||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_DEVICE_TOKEN);
|
||||||
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
||||||
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
||||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||||
|
@ -142,24 +166,23 @@ class APNSenderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testGenericFailure() {
|
void testGenericFailure() {
|
||||||
ApnsClient apnsClient = mock(ApnsClient.class);
|
|
||||||
|
|
||||||
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
PushNotificationResponse<SimpleApnsPushNotification> response = mock(PushNotificationResponse.class);
|
||||||
when(response.isAccepted()).thenReturn(false);
|
when(response.isAccepted()).thenReturn(false);
|
||||||
when(response.getRejectionReason()).thenReturn(Optional.of("BadTopic"));
|
when(response.getRejectionReason()).thenReturn(Optional.of("BadTopic"));
|
||||||
|
|
||||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
.thenAnswer(
|
||||||
|
(Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), response));
|
||||||
|
|
||||||
PushNotification pushNotification = new PushNotification(DESTINATION_APN_ID, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice);
|
PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN_VOIP,
|
||||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, "foo");
|
PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, true);
|
||||||
|
|
||||||
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
final SendPushNotificationResult result = apnSender.sendNotification(pushNotification).join();
|
||||||
|
|
||||||
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
ArgumentCaptor<SimpleApnsPushNotification> notification = ArgumentCaptor.forClass(SimpleApnsPushNotification.class);
|
||||||
verify(apnsClient).sendNotification(notification.capture());
|
verify(apnsClient).sendNotification(notification.capture());
|
||||||
|
|
||||||
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_APN_ID);
|
assertThat(notification.getValue().getToken()).isEqualTo(DESTINATION_DEVICE_TOKEN);
|
||||||
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
assertThat(notification.getValue().getExpiration()).isEqualTo(APNSender.MAX_EXPIRATION);
|
||||||
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
assertThat(notification.getValue().getPayload()).isEqualTo(APNSender.APN_VOIP_NOTIFICATION_PAYLOAD);
|
||||||
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
assertThat(notification.getValue().getPriority()).isEqualTo(DeliveryPriority.IMMEDIATE);
|
||||||
|
@ -175,10 +198,11 @@ class APNSenderTest {
|
||||||
when(response.isAccepted()).thenReturn(true);
|
when(response.isAccepted()).thenReturn(true);
|
||||||
|
|
||||||
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
when(apnsClient.sendNotification(any(SimpleApnsPushNotification.class)))
|
||||||
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0), new IOException("lost connection")));
|
.thenAnswer((Answer) invocationOnMock -> new MockPushNotificationFuture<>(invocationOnMock.getArgument(0),
|
||||||
|
new IOException("lost connection")));
|
||||||
|
|
||||||
PushNotification pushNotification = new PushNotification(DESTINATION_APN_ID, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice);
|
PushNotification pushNotification = new PushNotification(DESTINATION_DEVICE_TOKEN, PushNotification.TokenType.APN_VOIP,
|
||||||
APNSender apnSender = new APNSender(new SynchronousExecutorService(), apnsClient, "foo");
|
PushNotification.NotificationType.NOTIFICATION, null, destinationAccount, destinationDevice, true);
|
||||||
|
|
||||||
assertThatThrownBy(() -> apnSender.sendNotification(pushNotification).join())
|
assertThatThrownBy(() -> apnSender.sendNotification(pushNotification).join())
|
||||||
.isInstanceOf(CompletionException.class)
|
.isInstanceOf(CompletionException.class)
|
||||||
|
@ -189,7 +213,8 @@ class APNSenderTest {
|
||||||
verifyNoMoreInteractions(apnsClient);
|
verifyNoMoreInteractions(apnsClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockPushNotificationFuture <P extends ApnsPushNotification, V> extends PushNotificationFuture<P, V> {
|
private static class MockPushNotificationFuture<P extends ApnsPushNotification, V> extends
|
||||||
|
PushNotificationFuture<P, V> {
|
||||||
|
|
||||||
MockPushNotificationFuture(final P pushNotification, final V response) {
|
MockPushNotificationFuture(final P pushNotification, final V response) {
|
||||||
super(pushNotification);
|
super(pushNotification);
|
||||||
|
|
|
@ -192,9 +192,7 @@ class ApnPushNotificationSchedulerTest {
|
||||||
assertEquals(account, pushNotification.destination());
|
assertEquals(account, pushNotification.destination());
|
||||||
assertEquals(device, pushNotification.destinationDevice());
|
assertEquals(device, pushNotification.destinationDevice());
|
||||||
assertEquals(PushNotification.NotificationType.NOTIFICATION, pushNotification.notificationType());
|
assertEquals(PushNotification.NotificationType.NOTIFICATION, pushNotification.notificationType());
|
||||||
|
assertFalse(pushNotification.urgent());
|
||||||
// TODO Check urgency
|
|
||||||
// assertFalse(pushNotification.urgent());
|
|
||||||
|
|
||||||
assertEquals(0, worker.processRecurringVoipNotifications(slot));
|
assertEquals(0, worker.processRecurringVoipNotifications(slot));
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,7 +54,7 @@ class FcmSenderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendMessage() {
|
void testSendMessage() {
|
||||||
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null);
|
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null, true);
|
||||||
|
|
||||||
final SettableApiFuture<String> sendFuture = SettableApiFuture.create();
|
final SettableApiFuture<String> sendFuture = SettableApiFuture.create();
|
||||||
sendFuture.set("message-id");
|
sendFuture.set("message-id");
|
||||||
|
@ -71,7 +71,7 @@ class FcmSenderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendMessageRejected() {
|
void testSendMessageRejected() {
|
||||||
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null);
|
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null, true);
|
||||||
|
|
||||||
final FirebaseMessagingException invalidArgumentException = mock(FirebaseMessagingException.class);
|
final FirebaseMessagingException invalidArgumentException = mock(FirebaseMessagingException.class);
|
||||||
when(invalidArgumentException.getMessagingErrorCode()).thenReturn(MessagingErrorCode.INVALID_ARGUMENT);
|
when(invalidArgumentException.getMessagingErrorCode()).thenReturn(MessagingErrorCode.INVALID_ARGUMENT);
|
||||||
|
@ -91,7 +91,7 @@ class FcmSenderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendMessageUnregistered() {
|
void testSendMessageUnregistered() {
|
||||||
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null);
|
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null, true);
|
||||||
|
|
||||||
final FirebaseMessagingException unregisteredException = mock(FirebaseMessagingException.class);
|
final FirebaseMessagingException unregisteredException = mock(FirebaseMessagingException.class);
|
||||||
when(unregisteredException.getMessagingErrorCode()).thenReturn(MessagingErrorCode.UNREGISTERED);
|
when(unregisteredException.getMessagingErrorCode()).thenReturn(MessagingErrorCode.UNREGISTERED);
|
||||||
|
@ -111,7 +111,7 @@ class FcmSenderTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testSendMessageException() {
|
void testSendMessageException() {
|
||||||
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null);
|
final PushNotification pushNotification = new PushNotification("foo", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, null, null, true);
|
||||||
|
|
||||||
final SettableApiFuture<String> sendFuture = SettableApiFuture.create();
|
final SettableApiFuture<String> sendFuture = SettableApiFuture.create();
|
||||||
sendFuture.setException(new IOException());
|
sendFuture.setException(new IOException());
|
||||||
|
|
|
@ -116,7 +116,7 @@ class MessageSenderTest {
|
||||||
messageSender.sendMessage(account, device, message, false);
|
messageSender.sendMessage(account, device, message, false);
|
||||||
|
|
||||||
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
||||||
verify(pushNotificationManager).sendNewMessageNotification(account, device.getId());
|
verify(pushNotificationManager).sendNewMessageNotification(account, device.getId(), message.getUrgent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -127,7 +127,7 @@ class MessageSenderTest {
|
||||||
messageSender.sendMessage(account, device, message, false);
|
messageSender.sendMessage(account, device, message, false);
|
||||||
|
|
||||||
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
||||||
verify(pushNotificationManager).sendNewMessageNotification(account, device.getId());
|
verify(pushNotificationManager).sendNewMessageNotification(account, device.getId(), message.getUrgent());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -136,7 +136,7 @@ class MessageSenderTest {
|
||||||
when(device.getFetchesMessages()).thenReturn(true);
|
when(device.getFetchesMessages()).thenReturn(true);
|
||||||
|
|
||||||
doThrow(NotPushRegisteredException.class)
|
doThrow(NotPushRegisteredException.class)
|
||||||
.when(pushNotificationManager).sendNewMessageNotification(account, DEVICE_ID);
|
.when(pushNotificationManager).sendNewMessageNotification(account, DEVICE_ID, message.getUrgent());
|
||||||
|
|
||||||
assertDoesNotThrow(() -> messageSender.sendMessage(account, device, message, false));
|
assertDoesNotThrow(() -> messageSender.sendMessage(account, device, message, false));
|
||||||
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
verify(messagesManager).insert(ACCOUNT_UUID, DEVICE_ID, message);
|
||||||
|
|
|
@ -18,9 +18,14 @@ import java.util.UUID;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
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.DynamicPushNotificationConfiguration;
|
||||||
import org.whispersystems.textsecuregcm.storage.Account;
|
import org.whispersystems.textsecuregcm.storage.Account;
|
||||||
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
import org.whispersystems.textsecuregcm.storage.AccountsManager;
|
||||||
import org.whispersystems.textsecuregcm.storage.Device;
|
import org.whispersystems.textsecuregcm.storage.Device;
|
||||||
|
import org.whispersystems.textsecuregcm.storage.DynamicConfigurationManager;
|
||||||
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
import org.whispersystems.textsecuregcm.tests.util.AccountsHelper;
|
||||||
import org.whispersystems.textsecuregcm.util.Util;
|
import org.whispersystems.textsecuregcm.util.Util;
|
||||||
|
|
||||||
|
@ -31,6 +36,7 @@ class PushNotificationManagerTest {
|
||||||
private FcmSender fcmSender;
|
private FcmSender fcmSender;
|
||||||
private ApnPushNotificationScheduler apnPushNotificationScheduler;
|
private ApnPushNotificationScheduler apnPushNotificationScheduler;
|
||||||
private PushLatencyManager pushLatencyManager;
|
private PushLatencyManager pushLatencyManager;
|
||||||
|
private DynamicPushNotificationConfiguration pushNotificationConfiguration;
|
||||||
|
|
||||||
private PushNotificationManager pushNotificationManager;
|
private PushNotificationManager pushNotificationManager;
|
||||||
|
|
||||||
|
@ -41,15 +47,26 @@ class PushNotificationManagerTest {
|
||||||
fcmSender = mock(FcmSender.class);
|
fcmSender = mock(FcmSender.class);
|
||||||
apnPushNotificationScheduler = mock(ApnPushNotificationScheduler.class);
|
apnPushNotificationScheduler = mock(ApnPushNotificationScheduler.class);
|
||||||
pushLatencyManager = mock(PushLatencyManager.class);
|
pushLatencyManager = mock(PushLatencyManager.class);
|
||||||
|
pushNotificationConfiguration = mock(DynamicPushNotificationConfiguration.class);
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked") final DynamicConfigurationManager<DynamicConfiguration> dynamicConfigurationManager =
|
||||||
|
mock(DynamicConfigurationManager.class);
|
||||||
|
|
||||||
|
final DynamicConfiguration dynamicConfiguration = mock(DynamicConfiguration.class);
|
||||||
|
|
||||||
|
when(dynamicConfigurationManager.getConfiguration()).thenReturn(dynamicConfiguration);
|
||||||
|
when(dynamicConfiguration.getPushNotificationConfiguration()).thenReturn(pushNotificationConfiguration);
|
||||||
|
when(pushNotificationConfiguration.isLowUrgencyEnabled()).thenReturn(true);
|
||||||
|
|
||||||
AccountsHelper.setupMockUpdate(accountsManager);
|
AccountsHelper.setupMockUpdate(accountsManager);
|
||||||
|
|
||||||
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
|
pushNotificationManager = new PushNotificationManager(accountsManager, apnSender, fcmSender,
|
||||||
apnPushNotificationScheduler, pushLatencyManager);
|
apnPushNotificationScheduler, pushLatencyManager, dynamicConfigurationManager);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
void sendNewMessageNotification() throws NotPushRegisteredException {
|
@ValueSource(booleans = {true, false})
|
||||||
|
void sendNewMessageNotification(final boolean urgent) throws NotPushRegisteredException {
|
||||||
final Account account = mock(Account.class);
|
final Account account = mock(Account.class);
|
||||||
final Device device = mock(Device.class);
|
final Device device = mock(Device.class);
|
||||||
|
|
||||||
|
@ -62,8 +79,30 @@ class PushNotificationManagerTest {
|
||||||
when(fcmSender.sendNotification(any()))
|
when(fcmSender.sendNotification(any()))
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
|
||||||
pushNotificationManager.sendNewMessageNotification(account, Device.MASTER_ID);
|
pushNotificationManager.sendNewMessageNotification(account, Device.MASTER_ID, urgent);
|
||||||
verify(fcmSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device));
|
verify(fcmSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(booleans = {true, false})
|
||||||
|
void sendNewMessageNotificationLowUrgencyDisabled(final boolean urgent) throws NotPushRegisteredException {
|
||||||
|
final Account account = mock(Account.class);
|
||||||
|
final Device device = mock(Device.class);
|
||||||
|
|
||||||
|
final String deviceToken = "token";
|
||||||
|
|
||||||
|
when(device.getId()).thenReturn(Device.MASTER_ID);
|
||||||
|
when(device.getApnId()).thenReturn(deviceToken);
|
||||||
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
|
when(pushNotificationConfiguration.isLowUrgencyEnabled()).thenReturn(false);
|
||||||
|
|
||||||
|
when(apnSender.sendNotification(any()))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
|
||||||
|
pushNotificationManager.sendNewMessageNotification(account, Device.MASTER_ID, urgent);
|
||||||
|
|
||||||
|
verify(apnSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.APN, PushNotification.NotificationType.NOTIFICATION, null, account, device, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -75,7 +114,7 @@ class PushNotificationManagerTest {
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
|
||||||
pushNotificationManager.sendRegistrationChallengeNotification(deviceToken, PushNotification.TokenType.APN_VOIP, challengeToken);
|
pushNotificationManager.sendRegistrationChallengeNotification(deviceToken, PushNotification.TokenType.APN_VOIP, challengeToken);
|
||||||
verify(apnSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.CHALLENGE, challengeToken, null, null));
|
verify(apnSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.CHALLENGE, challengeToken, null, null, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,11 +133,12 @@ class PushNotificationManagerTest {
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
|
||||||
pushNotificationManager.sendRateLimitChallengeNotification(account, challengeToken);
|
pushNotificationManager.sendRateLimitChallengeNotification(account, challengeToken);
|
||||||
verify(apnSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.APN, PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, account, device));
|
verify(apnSender).sendNotification(new PushNotification(deviceToken, PushNotification.TokenType.APN, PushNotification.NotificationType.RATE_LIMIT_CHALLENGE, challengeToken, account, device, true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
void testSendNotification() {
|
@ValueSource(booleans = {true, false})
|
||||||
|
void testSendNotificationFcm(final boolean urgent) {
|
||||||
final Account account = mock(Account.class);
|
final Account account = mock(Account.class);
|
||||||
final Device device = mock(Device.class);
|
final Device device = mock(Device.class);
|
||||||
|
|
||||||
|
@ -106,7 +146,7 @@ class PushNotificationManagerTest {
|
||||||
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
final PushNotification pushNotification = new PushNotification(
|
final PushNotification pushNotification = new PushNotification(
|
||||||
"token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device);
|
"token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent);
|
||||||
|
|
||||||
when(fcmSender.sendNotification(pushNotification))
|
when(fcmSender.sendNotification(pushNotification))
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
@ -120,8 +160,9 @@ class PushNotificationManagerTest {
|
||||||
verifyNoInteractions(apnPushNotificationScheduler);
|
verifyNoInteractions(apnPushNotificationScheduler);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@ParameterizedTest
|
||||||
void testSendNotificationApnVoip() {
|
@ValueSource(booleans = {true, false})
|
||||||
|
void testSendNotificationApn(final boolean urgent) {
|
||||||
final Account account = mock(Account.class);
|
final Account account = mock(Account.class);
|
||||||
final Device device = mock(Device.class);
|
final Device device = mock(Device.class);
|
||||||
|
|
||||||
|
@ -129,7 +170,35 @@ class PushNotificationManagerTest {
|
||||||
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
final PushNotification pushNotification = new PushNotification(
|
final PushNotification pushNotification = new PushNotification(
|
||||||
"token", PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device);
|
"token", PushNotification.TokenType.APN, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent);
|
||||||
|
|
||||||
|
when(apnSender.sendNotification(pushNotification))
|
||||||
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
|
||||||
|
pushNotificationManager.sendNotification(pushNotification);
|
||||||
|
|
||||||
|
verifyNoInteractions(fcmSender);
|
||||||
|
|
||||||
|
if (urgent) {
|
||||||
|
verify(apnSender).sendNotification(pushNotification);
|
||||||
|
verifyNoInteractions(apnPushNotificationScheduler);
|
||||||
|
} else {
|
||||||
|
verifyNoInteractions(apnSender);
|
||||||
|
verify(apnPushNotificationScheduler).scheduleBackgroundNotification(account, device);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(booleans = {true, false})
|
||||||
|
void testSendNotificationApnVoip(final boolean urgent) {
|
||||||
|
final Account account = mock(Account.class);
|
||||||
|
final Device device = mock(Device.class);
|
||||||
|
|
||||||
|
when(device.getId()).thenReturn(Device.MASTER_ID);
|
||||||
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
|
final PushNotification pushNotification = new PushNotification(
|
||||||
|
"token", PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device, urgent);
|
||||||
|
|
||||||
when(apnSender.sendNotification(pushNotification))
|
when(apnSender.sendNotification(pushNotification))
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(true, null, false)));
|
||||||
|
@ -137,10 +206,12 @@ class PushNotificationManagerTest {
|
||||||
pushNotificationManager.sendNotification(pushNotification);
|
pushNotificationManager.sendNotification(pushNotification);
|
||||||
|
|
||||||
verify(apnSender).sendNotification(pushNotification);
|
verify(apnSender).sendNotification(pushNotification);
|
||||||
|
|
||||||
verifyNoInteractions(fcmSender);
|
verifyNoInteractions(fcmSender);
|
||||||
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
|
verify(accountsManager, never()).updateDevice(eq(account), eq(Device.MASTER_ID), any());
|
||||||
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
|
verify(device, never()).setUninstalledFeedbackTimestamp(Util.todayInMillis());
|
||||||
verify(apnPushNotificationScheduler).scheduleRecurringVoipNotification(account, device);
|
verify(apnPushNotificationScheduler).scheduleRecurringVoipNotification(account, device);
|
||||||
|
verify(apnPushNotificationScheduler, never()).scheduleBackgroundNotification(any(), any());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -153,7 +224,7 @@ class PushNotificationManagerTest {
|
||||||
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
final PushNotification pushNotification = new PushNotification(
|
final PushNotification pushNotification = new PushNotification(
|
||||||
"token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device);
|
"token", PushNotification.TokenType.FCM, PushNotification.NotificationType.NOTIFICATION, null, account, device, true);
|
||||||
|
|
||||||
when(fcmSender.sendNotification(pushNotification))
|
when(fcmSender.sendNotification(pushNotification))
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(false, null, true)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(false, null, true)));
|
||||||
|
@ -175,7 +246,7 @@ class PushNotificationManagerTest {
|
||||||
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
when(account.getDevice(Device.MASTER_ID)).thenReturn(Optional.of(device));
|
||||||
|
|
||||||
final PushNotification pushNotification = new PushNotification(
|
final PushNotification pushNotification = new PushNotification(
|
||||||
"token", PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device);
|
"token", PushNotification.TokenType.APN_VOIP, PushNotification.NotificationType.NOTIFICATION, null, account, device, true);
|
||||||
|
|
||||||
when(apnSender.sendNotification(pushNotification))
|
when(apnSender.sendNotification(pushNotification))
|
||||||
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(false, null, true)));
|
.thenReturn(CompletableFuture.completedFuture(new SendPushNotificationResult(false, null, true)));
|
||||||
|
|
Loading…
Reference in New Issue