Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Firebase unregistered token handling #184

Open
wants to merge 1 commit into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
import com.sflpro.notifier.db.entities.notification.push.PushNotification;
import com.sflpro.notifier.db.entities.notification.push.PushNotificationProviderType;
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipient;
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipientStatus;
import com.sflpro.notifier.services.common.exception.ServicesRuntimeException;
import com.sflpro.notifier.services.notification.exception.NotificationInvalidStateException;
import com.sflpro.notifier.services.notification.push.PushNotificationProcessor;
import com.sflpro.notifier.services.notification.push.PushNotificationRecipientService;
import com.sflpro.notifier.services.notification.push.PushNotificationService;
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
import com.sflpro.notifier.spi.push.PlatformType;
import com.sflpro.notifier.spi.push.PushMessage;
import com.sflpro.notifier.spi.push.PushMessageSender;
Expand Down Expand Up @@ -55,6 +58,9 @@ public class PushNotificationProcessorImpl implements PushNotificationProcessor
@Autowired
private TemplateContentResolver templateContentResolver;

@Autowired
private PushNotificationRecipientService pushNotificationRecipientService;

PushNotificationProcessorImpl() {
logger.debug("Initializing push notification processing service");
}
Expand All @@ -80,7 +86,16 @@ public void processNotification(@Nonnull final Long notificationId, @Nonnull fin
}
// Mark push notification as processed
updatePushNotificationState(notificationId, NotificationState.SENT);
} catch (final Exception ex) {
}
catch (final PushNotificationInvalidRouteTokenException ex) {
logger.warn("Failed to process push notification with id - {}, because of invalid token of recipient with id - {}", notificationId, pushNotification.getRecipient().getId());
updatePushNotificationState(notificationId, NotificationState.FAILED);
pushNotificationRecipientService.updatePushNotificationRecipientStatus(
pushNotification.getRecipient().getId(),
PushNotificationRecipientStatus.DISABLED
);
}
catch (final Exception ex) {
final String message = "Error occurred while processing push notification with id - " + notificationId;
updatePushNotificationState(notificationId, NotificationState.FAILED);
throw new ServicesRuntimeException(message, ex);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,12 @@
import com.sflpro.notifier.db.entities.notification.NotificationState;
import com.sflpro.notifier.db.entities.notification.push.PushNotification;
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipient;
import com.sflpro.notifier.db.entities.notification.push.PushNotificationRecipientStatus;
import com.sflpro.notifier.services.notification.exception.NotificationInvalidStateException;
import com.sflpro.notifier.services.notification.push.PushNotificationRecipientService;
import com.sflpro.notifier.services.notification.push.PushNotificationService;
import com.sflpro.notifier.services.test.AbstractServicesUnitTest;
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
import com.sflpro.notifier.spi.push.PushMessage;
import com.sflpro.notifier.spi.push.PushMessageSender;
import com.sflpro.notifier.spi.push.PushMessageSendingResult;
Expand Down Expand Up @@ -41,6 +44,8 @@ public class PushNotificationProcessorImplTest extends AbstractServicesUnitTest
@Mock
private PushMessageSender pushMessageSender;

@Mock
private PushNotificationRecipientService pushNotificationRecipientService;

/* Constructors */
public PushNotificationProcessorImplTest() {
Expand Down Expand Up @@ -126,6 +131,37 @@ public void testProcessPushNotificationWhenExceptionOccursDuringProcessing() {
verifyAll();
}

public void testProcessPushNotificationWhenInvalidTokenExceptionOccursDuringProcessing() {
// Test data
final Long notificationId = 1L;
final PushNotification notification = getServicesImplTestHelper().createPushNotification();
notification.setId(notificationId);
notification.setState(NotificationState.CREATED);
final Long recipientId = 2L;
final PushNotificationRecipient recipient = getServicesImplTestHelper().createPushNotificationSnsRecipient();
recipient.setId(recipientId);
notification.setRecipient(recipient);
final PushNotificationInvalidRouteTokenException exceptionDuringSending = new PushNotificationInvalidRouteTokenException(
UUID.randomUUID().toString(), "Exception for testing error flow", null
);
// Reset
resetAll();
// Expectations
expect(pushNotificationService.getPushNotificationForProcessing(eq(notificationId))).andReturn(notification).once();
expect(pushNotificationService.updateNotificationState(notificationId, NotificationState.PROCESSING)).andReturn(notification).once();
expect(pushMessageServiceProvider.lookupPushMessageSender(notification.getRecipient().getType()))
.andReturn(Optional.of(pushMessageSender));
expect(pushMessageSender.send(isA(PushMessage.class))).andThrow(exceptionDuringSending);
expect(pushNotificationService.updateNotificationState(notificationId, NotificationState.FAILED)).andReturn(notification).once();
expect(pushNotificationRecipientService.updatePushNotificationRecipientStatus(notification.getRecipient().getId(), PushNotificationRecipientStatus.DISABLED));
// Replay
replayAll();
// Run test scenario
pushNotificationProcessingService.processNotification(notificationId, Collections.emptyMap());
// Verify
verifyAll();
}

@Test
public void testProcessPushNotification() {
// Test data
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.sflpro.notifier.externalclients.push.firebase;

import com.google.firebase.messaging.*;
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
import com.sflpro.notifier.spi.push.PlatformType;
import com.sflpro.notifier.spi.push.PushMessage;
import com.sflpro.notifier.spi.push.PushMessageSender;
Expand All @@ -27,6 +28,8 @@ class FirebasePushMessageSender implements PushMessageSender {

private static final Logger logger = LoggerFactory.getLogger(FirebasePushMessageSender.class);

private static final String UNREGISTERED_ERROR_CODE = "UNREGISTERED";

private static final String TITLE = "title";

private static final String BODY = "body";
Expand Down Expand Up @@ -62,6 +65,15 @@ public PushMessageSendingResult send(final PushMessage message) {
platformConfigurationHandler(message.platformType()).ifPresent(handler -> handler.accept(message, builder));
return PushMessageSendingResult.of(firebaseMessaging.send(builder.build()));
} catch (final FirebaseMessagingException ex) {
final String errorCode = ex.getErrorCode();
if(UNREGISTERED_ERROR_CODE.equals(errorCode)) {
logger.debug("Unable to send message with subject {}, firebase route token is not registered", message.subject());
throw new PushNotificationInvalidRouteTokenException(
message.destinationRouteToken(),
"Firebase notification sender failed to send message with subject " + message.subject() + " with error" + errorCode,
ex
);
}
logger.error("Unable to send message with subject {}.", message.subject());
throw new MessageSendingFaildException("Filed to send message using firebase cloud messaging.", ex);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.sflpro.notifier.externalclients.push.test.AbstractPushNotificationUnitTest;
import com.sflpro.notifier.spi.exception.PushNotificationInvalidRouteTokenException;
import com.sflpro.notifier.spi.push.PlatformType;
import com.sflpro.notifier.spi.push.PushMessage;
import com.sflpro.notifier.spi.push.PushMessageSender;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Mockito;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.assertj.core.api.Assertions.*;
import static org.mockito.ArgumentMatchers.isA;
import static org.mockito.Mockito.*;

Expand Down Expand Up @@ -177,6 +178,43 @@ public void testSendToIOSDeviceWithProvidedConfigs() throws FirebaseMessagingExc
verifyZeroInteractions(defaultAndroidConfig, defaultApnsConfig);
}

@Test
public void testSendWithUnregisteredTokenThrowsException() throws FirebaseMessagingException {
final Map<String, String> properties = new HashMap<>();
properties.put(uuid(), uuid());
final FirebaseMessagingException firebaseException = mock(FirebaseMessagingException.class);
final PushMessage message = PushMessage.of(
uuid(),
uuid(),
uuid(),
PlatformType.GCM,
properties
);
properties.put("title", message.subject());
properties.put("body", message.body());
final String messageId = uuid();
final String ttlKey = "ttl";
final String priorityKey = "priority";
final String collapseKey = "collapseKey";
final String restrictedPackageNameKey = "restrictedPackageName";
when(defaultAndroidConfig.getProperty(ttlKey)).thenReturn("10");
when(defaultAndroidConfig.getProperty(priorityKey)).thenReturn("HIGH");
when(defaultAndroidConfig.getProperty(collapseKey)).thenReturn(uuid());
when(defaultAndroidConfig.getProperty(restrictedPackageNameKey)).thenReturn(uuid());
when(firebaseMessaging.send(isA(Message.class))).thenThrow(firebaseException);
when(firebaseException.getErrorCode()).thenReturn("UNREGISTERED");
assertThatThrownBy(() -> pushMessageSender.send(message))
.isInstanceOf(PushNotificationInvalidRouteTokenException.class)
.hasFieldOrPropertyWithValue("routeToken", message.destinationRouteToken())
.hasFieldOrPropertyWithValue("cause", firebaseException);
verify(firebaseMessaging).send(isA(Message.class));
verify(defaultAndroidConfig).getProperty(ttlKey);
verify(defaultAndroidConfig).getProperty(priorityKey);
verify(defaultAndroidConfig).getProperty(collapseKey);
verify(defaultAndroidConfig).getProperty(restrictedPackageNameKey);
verifyZeroInteractions(defaultApnsConfig);
}

private static void checkProperties(final PushMessage pushMessage, final Message message) {
assertThat(message)
.hasFieldOrPropertyWithValue("data", pushMessage.properties())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.sflpro.notifier.spi.exception;

public class PushNotificationInvalidRouteTokenException extends RuntimeException {

private final String routeToken;

public PushNotificationInvalidRouteTokenException(final String routeToken, final String message, final Throwable cause) {
super(message, cause);
this.routeToken = routeToken;
}

public String getRouteToken() {
return routeToken;
}
}