Browse Source

Add tests for mobile app notifications

pull/8522/head
ViacheslavKlimov 3 years ago
parent
commit
17a3065c64
  1. 3
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  2. 4
      application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java
  3. 9
      application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java
  4. 26
      application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java
  5. 60
      application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

3
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -280,9 +280,6 @@ public class ThingsboardInstallService {
log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ..."); log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2"); databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
//TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache //TODO DON'T FORGET to update switch statement in the CacheCleanupService if you need to clear the cache
case "3.6.2":
log.info("Upgrading ThingsBoard from version 3.6.2 to 3.6.3 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.6.2");
break; break;
default: default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion); throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);

4
application/src/main/java/org/thingsboard/server/service/notification/DefaultNotificationCenter.java

@ -245,11 +245,11 @@ public class DefaultNotificationCenter extends AbstractSubscriptionService imple
if (targetConfig.getUsersFilter().getType().isForRules() && ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) { if (targetConfig.getUsersFilter().getType().isForRules() && ctx.getRequest().getInfo() instanceof RuleOriginatedNotificationInfo) {
recipients = new PageDataIterable<>(pageLink -> { recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink); return notificationTargetService.findRecipientsForRuleNotificationTargetConfig(ctx.getTenantId(), targetConfig, (RuleOriginatedNotificationInfo) ctx.getRequest().getInfo(), pageLink);
}, 500); }, 256);
} else { } else {
recipients = new PageDataIterable<>(pageLink -> { recipients = new PageDataIterable<>(pageLink -> {
return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink); return notificationTargetService.findRecipientsForNotificationTargetConfig(ctx.getTenantId(), targetConfig, pageLink);
}, 500); }, 256);
} }
break; break;
} }

9
application/src/main/java/org/thingsboard/server/service/notification/provider/DefaultFirebaseService.java

@ -26,6 +26,7 @@ import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message; import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification; import com.google.firebase.messaging.Notification;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.notification.FirebaseService; import org.thingsboard.rule.engine.api.notification.FirebaseService;
@ -36,6 +37,7 @@ import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@Service @Service
@Slf4j
public class DefaultFirebaseService implements FirebaseService { public class DefaultFirebaseService implements FirebaseService {
private final Cache<String, FirebaseContext> contexts = Caffeine.newBuilder() private final Cache<String, FirebaseContext> contexts = Caffeine.newBuilder()
@ -66,6 +68,7 @@ public class DefaultFirebaseService implements FirebaseService {
.setToken(fcmToken) .setToken(fcmToken)
.build(); .build();
firebaseContext.getMessaging().send(message); firebaseContext.getMessaging().send(message);
log.trace("[{}] Sent message for FCM token {}", tenantId, fcmToken);
} }
public static class FirebaseContext { public static class FirebaseContext {
@ -100,13 +103,16 @@ public class DefaultFirebaseService implements FirebaseService {
} catch (IllegalStateException alreadyExists) { // should never normally happen } catch (IllegalStateException alreadyExists) { // should never normally happen
messaging = FirebaseMessaging.getInstance(app); messaging = FirebaseMessaging.getInstance(app);
} }
log.debug("[{}] Initialized new FirebaseContext", key);
} }
public void check(String credentials) { public void check(String credentials) {
if (!this.credentials.equals(credentials)) { if (!this.credentials.equals(credentials)) {
app.delete(); destroy();
this.credentials = credentials; this.credentials = credentials;
init(); init();
} else if (app == null || messaging == null) {
throw new IllegalStateException("Firebase app couldn't be initialized");
} }
} }
@ -114,6 +120,7 @@ public class DefaultFirebaseService implements FirebaseService {
app.delete(); app.delete();
app = null; app = null;
messaging = null; messaging = null;
log.debug("[{}] Destroyed FirebaseContext", key);
} }
} }

26
application/src/test/java/org/thingsboard/server/service/notification/AbstractNotificationApiTest.java

@ -41,6 +41,7 @@ import org.thingsboard.server.common.data.notification.rule.DefaultNotificationR
import org.thingsboard.server.common.data.notification.rule.NotificationRule; import org.thingsboard.server.common.data.notification.rule.NotificationRule;
import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo; import org.thingsboard.server.common.data.notification.rule.NotificationRuleInfo;
import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig; import org.thingsboard.server.common.data.notification.rule.trigger.config.NotificationRuleTriggerConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
@ -48,6 +49,8 @@ import org.thingsboard.server.common.data.notification.targets.platform.UserList
import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.UsersFilter;
import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.DeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.EmailDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.HasSubject;
import org.thingsboard.server.common.data.notification.template.MobileAppDeliveryMethodNotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.notification.template.NotificationTemplate;
import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig; import org.thingsboard.server.common.data.notification.template.NotificationTemplateConfig;
import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate; import org.thingsboard.server.common.data.notification.template.SmsDeliveryMethodNotificationTemplate;
@ -168,26 +171,28 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate; DeliveryMethodNotificationTemplate deliveryMethodNotificationTemplate;
switch (deliveryMethod) { switch (deliveryMethod) {
case WEB: { case WEB: {
WebDeliveryMethodNotificationTemplate template = new WebDeliveryMethodNotificationTemplate(); deliveryMethodNotificationTemplate = new WebDeliveryMethodNotificationTemplate();
template.setSubject(subject);
deliveryMethodNotificationTemplate = template;
break; break;
} }
case EMAIL: { case EMAIL: {
EmailDeliveryMethodNotificationTemplate template = new EmailDeliveryMethodNotificationTemplate(); deliveryMethodNotificationTemplate = new EmailDeliveryMethodNotificationTemplate();
template.setSubject(subject);
deliveryMethodNotificationTemplate = template;
break; break;
} }
case SMS: { case SMS: {
deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate(); deliveryMethodNotificationTemplate = new SmsDeliveryMethodNotificationTemplate();
break; break;
} }
case MOBILE_APP:
deliveryMethodNotificationTemplate = new MobileAppDeliveryMethodNotificationTemplate();
break;
default: default:
throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod); throw new IllegalArgumentException("Unsupported delivery method " + deliveryMethod);
} }
deliveryMethodNotificationTemplate.setEnabled(true); deliveryMethodNotificationTemplate.setEnabled(true);
deliveryMethodNotificationTemplate.setBody(text); deliveryMethodNotificationTemplate.setBody(text);
if (deliveryMethodNotificationTemplate instanceof HasSubject) {
((HasSubject) deliveryMethodNotificationTemplate).setSubject(subject);
}
config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate); config.getDeliveryMethodsTemplates().put(deliveryMethod, deliveryMethodNotificationTemplate);
} }
notificationTemplate.setConfiguration(config); notificationTemplate.setConfiguration(config);
@ -202,6 +207,15 @@ public abstract class AbstractNotificationApiTest extends AbstractControllerTest
doPost("/api/notification/settings", notificationSettings).andExpect(status().isOk()); doPost("/api/notification/settings", notificationSettings).andExpect(status().isOk());
} }
protected void saveNotificationSettings(NotificationDeliveryMethodConfig... configs) throws Exception {
NotificationSettings settings = new NotificationSettings();
settings.setDeliveryMethodsConfigs(Arrays.stream(configs)
.collect(Collectors.toMap(
NotificationDeliveryMethodConfig::getMethod, config -> config
)));
saveNotificationSettings(settings);
}
protected Pair<User, NotificationApiWsClient> createUserAndConnectWsClient(Authority authority) throws Exception { protected Pair<User, NotificationApiWsClient> createUserAndConnectWsClient(Authority authority) throws Exception {
User user = new User(); User user = new User();
user.setTenantId(tenantId); user.setTenantId(tenantId);

60
application/src/test/java/org/thingsboard/server/service/notification/NotificationApiTest.java

@ -24,10 +24,14 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.web.client.RestTemplate; import org.springframework.web.client.RestTemplate;
import org.thingsboard.rule.engine.api.NotificationCenter; import org.thingsboard.rule.engine.api.NotificationCenter;
import org.thingsboard.rule.engine.api.notification.FirebaseService;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.UserMobileInfo;
import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.NotificationRequestId; import org.thingsboard.server.common.data.id.NotificationRequestId;
@ -44,11 +48,13 @@ import org.thingsboard.server.common.data.notification.NotificationRequestStats;
import org.thingsboard.server.common.data.notification.NotificationRequestStatus; import org.thingsboard.server.common.data.notification.NotificationRequestStatus;
import org.thingsboard.server.common.data.notification.NotificationType; import org.thingsboard.server.common.data.notification.NotificationType;
import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo; import org.thingsboard.server.common.data.notification.info.EntityActionNotificationInfo;
import org.thingsboard.server.common.data.notification.settings.MobileAppNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.NotificationSettings; import org.thingsboard.server.common.data.notification.settings.NotificationSettings;
import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig; import org.thingsboard.server.common.data.notification.settings.SlackNotificationDeliveryMethodConfig;
import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings; import org.thingsboard.server.common.data.notification.settings.UserNotificationSettings;
import org.thingsboard.server.common.data.notification.targets.MicrosoftTeamsNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.MicrosoftTeamsNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.NotificationTarget; import org.thingsboard.server.common.data.notification.targets.NotificationTarget;
import org.thingsboard.server.common.data.notification.targets.platform.AllUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter; import org.thingsboard.server.common.data.notification.targets.platform.CustomerUsersFilter;
import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig; import org.thingsboard.server.common.data.notification.targets.platform.PlatformUsersNotificationTargetConfig;
import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter; import org.thingsboard.server.common.data.notification.targets.platform.SystemAdministratorsFilter;
@ -91,6 +97,8 @@ import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@DaoSqlTest @DaoSqlTest
@Slf4j @Slf4j
@ -104,6 +112,8 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
private DbCallbackExecutorService executor; private DbCallbackExecutorService executor;
@Autowired @Autowired
private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel; private MicrosoftTeamsNotificationChannel microsoftTeamsNotificationChannel;
@MockBean
private FirebaseService firebaseService;
@Before @Before
public void beforeEach() throws Exception { public void beforeEach() throws Exception {
@ -708,6 +718,56 @@ public class NotificationApiTest extends AbstractNotificationApiTest {
assertThat(message.getPotentialAction().get(0).getTargets().get(0).getUri()).isEqualTo("https://" + expectedParams); assertThat(message.getPotentialAction().get(0).getTargets().get(0).getUri()).isEqualTo("https://" + expectedParams);
} }
@Test
public void testMobileAppNotifications() throws Exception {
loginSysAdmin();
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
saveNotificationSettings(config);
loginCustomerUser();
UserMobileInfo customerMobileInfo = new UserMobileInfo();
customerMobileInfo.setFcmToken("customerFcmToken");
doPost("/api/user/mobile/info", customerMobileInfo).andExpect(status().isOk());
loginTenantAdmin();
UserMobileInfo tenantMobileInfo = new UserMobileInfo();
tenantMobileInfo.setFcmToken("tenantFcmToken");
doPost("/api/user/mobile/info", tenantMobileInfo).andExpect(status().isOk());
loginDifferentCustomer(); // with no mobile info
loginTenantAdmin();
NotificationTarget target = createNotificationTarget(new AllUsersFilter());
NotificationTemplate template = createNotificationTemplate(NotificationType.GENERAL, "Title", "Message", NotificationDeliveryMethod.MOBILE_APP);
NotificationRequest request = submitNotificationRequest(List.of(target.getId()), template.getId(), 0);
NotificationRequestStats stats = awaitNotificationRequest(request.getId());
assertThat(stats.getSent().get(NotificationDeliveryMethod.MOBILE_APP)).hasValue(2);
assertThat(stats.getErrors().get(NotificationDeliveryMethod.MOBILE_APP).get(differentCustomerUser.getEmail()))
.contains("doesn't use the mobile app");
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("tenantFcmToken"), eq("Title"), eq("Message"));
verify(firebaseService).sendMessage(eq(tenantId), eq("testCredentials"),
eq("customerFcmToken"), eq("Title"), eq("Message"));
verifyNoMoreInteractions(firebaseService);
}
@Test
public void testMobileSettings_tenantLevel() throws Exception {
MobileAppNotificationDeliveryMethodConfig config = new MobileAppNotificationDeliveryMethodConfig();
config.setFirebaseServiceAccountCredentials("testCredentials");
NotificationSettings settings = new NotificationSettings();
settings.setDeliveryMethodsConfigs(Map.of(
NotificationDeliveryMethod.MOBILE_APP, config
));
ResultActions result = doPost("/api/notification/settings", settings)
.andExpect(status().isBadRequest());
assertThat(getErrorMessage(result)).contains("can only be configured by system administrator");
}
private NotificationRequestStats submitNotificationRequestAndWait(NotificationRequest notificationRequest) throws Exception { private NotificationRequestStats submitNotificationRequestAndWait(NotificationRequest notificationRequest) throws Exception {
SettableFuture<NotificationRequestStats> future = SettableFuture.create(); SettableFuture<NotificationRequestStats> future = SettableFuture.create();
notificationCenter.processNotificationRequest(notificationRequest.getTenantId(), notificationRequest, new FutureCallback<>() { notificationCenter.processNotificationRequest(notificationRequest.getTenantId(), notificationRequest, new FutureCallback<>() {

Loading…
Cancel
Save