subState = new HashMap<>(keys.size());
keys.forEach(key -> subState.put(key, startTs));
data.forEach(v -> subState.put(v.getKey(), v.getTs()));
+ Lock subLock = new ReentrantLock();
TbTimeseriesSubscription sub = TbTimeseriesSubscription.builder()
.serviceId(serviceId)
.sessionId(sessionId)
.subscriptionId(cmd.getCmdId())
.tenantId(sessionRef.getSecurityCtx().getTenantId())
.entityId(entityId)
- .updateConsumer(DefaultTelemetryWebSocketService.this::sendWsMsg)
+ .updateConsumer((sessionId, update) -> {
+ subLock.lock();
+ try {
+ sendWsMsg(sessionId, update);
+ } finally {
+ subLock.unlock();
+ }
+ })
.allKeys(false)
.keyStates(subState).build();
- oldSubService.addSubscription(sub);
+
+ subLock.lock();
+ try{
+ oldSubService.addSubscription(sub);
+ sendWsMsg(sessionRef, new TelemetrySubscriptionUpdate(cmd.getCmdId(), data));
+ } finally {
+ subLock.unlock();
+ }
}
@Override
@@ -793,7 +852,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
private void sendWsMsg(TelemetryWebSocketSessionRef sessionRef, int cmdId, Object update) {
try {
- String msg = jsonMapper.writeValueAsString(update);
+ String msg = JacksonUtil.OBJECT_MAPPER.writeValueAsString(update);
executor.submit(() -> {
try {
msgEndpoint.send(sessionRef, cmdId, msg);
diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java
index ca3876464e..4961f901c5 100644
--- a/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java
+++ b/application/src/main/java/org/thingsboard/server/service/telemetry/sub/AlarmSubscriptionUpdate.java
@@ -17,15 +17,8 @@ package org.thingsboard.server.service.telemetry.sub;
import lombok.Getter;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.kv.TsKvEntry;
-import org.thingsboard.server.common.data.query.AlarmData;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.TreeMap;
-import java.util.stream.Collectors;
+import org.thingsboard.server.common.data.alarm.AlarmAssigneeUpdate;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
public class AlarmSubscriptionUpdate {
@@ -36,15 +29,15 @@ public class AlarmSubscriptionUpdate {
@Getter
private String errorMsg;
@Getter
- private Alarm alarm;
+ private AlarmInfo alarm;
@Getter
private boolean alarmDeleted;
- public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm) {
+ public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm) {
this(subscriptionId, alarm, false);
}
- public AlarmSubscriptionUpdate(int subscriptionId, Alarm alarm, boolean alarmDeleted) {
+ public AlarmSubscriptionUpdate(int subscriptionId, AlarmInfo alarm, boolean alarmDeleted) {
super();
this.subscriptionId = subscriptionId;
this.alarm = alarm;
@@ -64,7 +57,7 @@ public class AlarmSubscriptionUpdate {
@Override
public String toString() {
- return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg + ", alarm="
- + alarm + "]";
+ return "AlarmUpdate [subscriptionId=" + subscriptionId + ", errorCode=" + errorCode + ", errorMsg=" + errorMsg +
+ ", alarm=" + alarm + "]";
}
-}
+}
\ No newline at end of file
diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
index 7b1ce89ccd..0c0ff3e0c9 100644
--- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
+++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java
@@ -65,7 +65,6 @@ import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
-import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.dao.device.DeviceCredentialsService;
import org.thingsboard.server.dao.device.DeviceProvisionService;
import org.thingsboard.server.dao.device.DeviceService;
@@ -95,6 +94,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceLwM2MC
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg;
import org.thingsboard.server.queue.common.TbProtoQueueMsg;
+import org.thingsboard.server.queue.util.DataDecodingEncodingService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.apiusage.TbApiUsageStateService;
import org.thingsboard.server.service.executors.DbCallbackExecutorService;
@@ -290,6 +290,7 @@ public class DefaultTransportApiService implements TransportApiService {
device.setType(requestMsg.getDeviceType());
device.setCustomerId(gateway.getCustomerId());
DeviceProfile deviceProfile = deviceProfileCache.findOrCreateDeviceProfile(gateway.getTenantId(), requestMsg.getDeviceType());
+
device.setDeviceProfileId(deviceProfile.getId());
ObjectNode additionalInfo = JacksonUtil.newObjectNode();
additionalInfo.put(DataConstants.LAST_CONNECTED_GATEWAY, gatewayId.toString());
diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
index 6ea3208208..ea059fcb2d 100644
--- a/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
+++ b/application/src/main/java/org/thingsboard/server/service/ttl/AlarmsCleanUpService.java
@@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.id.AlarmId;
import org.thingsboard.server.common.data.id.TenantId;
diff --git a/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java b/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java
deleted file mode 100644
index 7ce958ef2d..0000000000
--- a/application/src/main/java/org/thingsboard/server/utils/EventDeduplicationExecutor.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/**
- * Copyright © 2016-2023 The Thingsboard Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.thingsboard.server.utils;
-
-import lombok.extern.slf4j.Slf4j;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.function.Consumer;
-
-/**
- * This class deduplicate executions of the specified function.
- * Useful in cluster mode, when you get event about partition change multiple times.
- * Assuming that the function execution is expensive, we should execute it immediately when first time event occurs and
- * later, once the processing of first event is done, process last pending task.
- *
- * @param parameters of the function
- */
-@Slf4j
-public class EventDeduplicationExecutor
{
- private final String name;
- private final ExecutorService executor;
- private final Consumer
function;
- private P pendingTask;
- private boolean busy;
-
- public EventDeduplicationExecutor(String name, ExecutorService executor, Consumer
function) {
- this.name = name;
- this.executor = executor;
- this.function = function;
- }
-
- public void submit(P params) {
- log.info("[{}] Going to submit: {}", name, params);
- synchronized (EventDeduplicationExecutor.this) {
- if (!busy) {
- busy = true;
- pendingTask = null;
- try {
- log.info("[{}] Submitting task: {}", name, params);
- executor.submit(() -> {
- try {
- log.info("[{}] Executing task: {}", name, params);
- function.accept(params);
- } catch (Throwable e) {
- log.warn("[{}] Failed to process task with parameters: {}", name, params, e);
- throw e;
- } finally {
- unlockAndProcessIfAny();
- }
- });
- } catch (Throwable e) {
- log.warn("[{}] Failed to submit task with parameters: {}", name, params, e);
- unlockAndProcessIfAny();
- throw e;
- }
- } else {
- log.info("[{}] Task is already in progress. {} pending task: {}", name, pendingTask == null ? "adding" : "updating", params);
- pendingTask = params;
- }
- }
- }
-
- private void unlockAndProcessIfAny() {
- synchronized (EventDeduplicationExecutor.this) {
- busy = false;
- if (pendingTask != null) {
- submit(pendingTask);
- }
- }
- }
-}
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 5549861950..10c4c9edac 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -73,6 +73,8 @@ server:
min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
# Default value of the server side RPC timeout.
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
+ rate_limits:
+ reset_password_per_user: "${RESET_PASSWORD_PER_USER_RATE_LIMIT_CONFIGURATION:5:3600}"
# Application info
app:
@@ -571,6 +573,7 @@ spring:
password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
hikari:
maximumPoolSize: "${SPRING_DATASOURCE_MAXIMUM_POOL_SIZE:16}"
+ registerMbeans: "${SPRING_DATASOURCE_HIKARI_REGISTER_MBEANS:false}" # true - enable MBean to diagnose pools state via JMX
# Audit log parameters
audit-log:
@@ -1209,3 +1212,4 @@ management:
exposure:
# Expose metrics endpoint (use value 'prometheus' to enable prometheus metrics).
include: '${METRICS_ENDPOINTS_EXPOSE:info}'
+
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
index 06cee7b71d..32d1f65765 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
@@ -52,7 +52,7 @@ public abstract class AbstractControllerTest extends AbstractNotifyEntityTest {
@LocalServerPort
protected int wsPort;
- private TbTestWebSocketClient wsClient; // lazy
+ private volatile TbTestWebSocketClient wsClient; // lazy
public TbTestWebSocketClient getWsClient() {
if (wsClient == null) {
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
index 6c42c7604d..b3025a2d64 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractNotifyEntityTest.java
@@ -18,7 +18,10 @@ package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.mock.mockito.SpyBean;
+import org.thingsboard.server.actors.service.ActorService;
+import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.HasName;
@@ -38,6 +41,7 @@ import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg;
import org.thingsboard.server.dao.audit.AuditLogService;
import org.thingsboard.server.dao.model.ModelConstants;
+import org.thingsboard.server.service.session.DeviceSessionCacheService;
import java.util.ArrayList;
import java.util.List;
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
index 24ce820760..c09f259aaa 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
@@ -26,17 +26,23 @@ import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
+import org.awaitility.Awaitility;
import org.hamcrest.Matcher;
import org.hibernate.exception.ConstraintViolationException;
import org.junit.After;
+import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
@@ -44,6 +50,7 @@ import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpInputMessage;
import org.springframework.mock.http.MockHttpOutputMessage;
+import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultActions;
@@ -52,6 +59,15 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;
+import org.thingsboard.rule.engine.api.MailService;
+import org.thingsboard.server.actors.DefaultTbActorSystem;
+import org.thingsboard.server.actors.TbActorId;
+import org.thingsboard.server.actors.TbActorMailbox;
+import org.thingsboard.server.actors.TbEntityActorId;
+import org.thingsboard.server.actors.device.DeviceActor;
+import org.thingsboard.server.actors.device.DeviceActorMessageProcessor;
+import org.thingsboard.server.actors.device.SessionInfo;
+import org.thingsboard.server.actors.service.DefaultActorService;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType;
@@ -69,7 +85,9 @@ import org.thingsboard.server.common.data.device.profile.MqttTopics;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
import org.thingsboard.server.common.data.edge.Edge;
+import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -80,10 +98,11 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.config.ThingsboardSecurityConfiguration;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.tenant.TenantProfileService;
-import org.thingsboard.server.service.mail.TestMailService;
+import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest;
import org.thingsboard.server.service.security.auth.rest.LoginRequest;
@@ -95,9 +114,14 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete;
@@ -144,6 +168,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected MockMvc mockMvc;
+ protected String currentActivateToken;
+ protected String currentResetPasswordToken;
+
protected String token;
protected String refreshToken;
protected String username;
@@ -155,6 +182,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
protected TenantId differentTenantId;
protected CustomerId differentCustomerId;
protected UserId customerUserId;
+ protected UserId differentCustomerUserId;
@SuppressWarnings("rawtypes")
private HttpMessageConverter mappingJackson2HttpMessageConverter;
@@ -168,6 +196,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@Autowired
private TenantProfileService tenantProfileService;
+ @Autowired
+ public TimeseriesService tsService;
+
+ @Autowired
+ protected DefaultActorService actorService;
+
+ @SpyBean
+ protected MailService mailService;
+
@Rule
public TestRule watcher = new TestWatcher() {
protected void starting(Description description) {
@@ -198,7 +235,9 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
@Before
public void setupWebTest() throws Exception {
- log.info("Executing web test setup");
+ log.debug("Executing web test setup");
+
+ setupMailServiceMock();
if (this.mockMvc == null) {
this.mockMvc = webAppContextSetup(webApplicationContext)
@@ -238,12 +277,33 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
resetTokens();
- log.info("Executed web test setup");
+ log.debug("Executed web test setup");
+ }
+
+ private void setupMailServiceMock() throws ThingsboardException {
+ Mockito.doNothing().when(mailService).sendAccountActivatedEmail(anyString(), anyString());
+ Mockito.doAnswer(new Answer() {
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ String activationLink = (String) args[0];
+ currentActivateToken = activationLink.split("=")[1];
+ return null;
+ }
+ }).when(mailService).sendActivationEmail(anyString(), anyString());
+
+ Mockito.doAnswer(new Answer() {
+ public Void answer(InvocationOnMock invocation) {
+ Object[] args = invocation.getArguments();
+ String passwordResetLink = (String) args[0];
+ currentResetPasswordToken = passwordResetLink.split("=")[1];
+ return null;
+ }
+ }).when(mailService).sendResetPasswordEmailAsync(anyString(), anyString());
}
@After
public void teardownWebTest() throws Exception {
- log.info("Executing web test teardown");
+ log.debug("Executing web test teardown");
loginSysAdmin();
doDelete("/api/tenant/" + tenantId.getId().toString())
@@ -325,7 +385,8 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
differentCustomerUser.setCustomerId(savedDifferentCustomer.getId());
differentCustomerUser.setEmail(DIFFERENT_CUSTOMER_USER_EMAIL);
- createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
+ differentCustomerUser = createUserAndLogin(differentCustomerUser, DIFFERENT_CUSTOMER_USER_PASSWORD);
+ differentCustomerUserId = differentCustomerUser.getId();
}
}
@@ -368,11 +429,11 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
}
private JsonNode getActivateRequest(String password) throws Exception {
- doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
+ doGet("/api/noauth/activate?activateToken={activateToken}", this.currentActivateToken)
.andExpect(status().isSeeOther())
- .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
+ .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + this.currentActivateToken));
return new ObjectMapper().createObjectNode()
- .put("activateToken", TestMailService.currentActivateToken)
+ .put("activateToken", this.currentActivateToken)
.put("password", password);
}
@@ -792,4 +853,37 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return (T) field.get(target);
}
+ protected int getDeviceActorSubscriptionCount(DeviceId deviceId, FeatureType featureType) {
+ DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
+ Map subscriptions = (Map) ReflectionTestUtils.getField(processor, getMapName(featureType));
+ return subscriptions.size();
+ }
+
+ protected void awaitForDeviceActorToReceiveSubscription(DeviceId deviceId, FeatureType featureType, int subscriptionCount) {
+ DeviceActorMessageProcessor processor = getDeviceActorProcessor(deviceId);
+ Map subscriptions = (Map) ReflectionTestUtils.getField(processor, getMapName(featureType));
+ Awaitility.await("Device actor received subscription command from the transport").atMost(TIMEOUT, TimeUnit.SECONDS).until(() -> subscriptions.size() == subscriptionCount);
+ }
+
+ protected static String getMapName(FeatureType featureType) {
+ switch (featureType) {
+ case ATTRIBUTES:
+ return "attributeSubscriptions";
+ case RPC:
+ return "rpcSubscriptions";
+ default:
+ throw new RuntimeException("Not supported feature " + featureType + "!");
+ }
+ }
+
+ protected DeviceActorMessageProcessor getDeviceActorProcessor(DeviceId deviceId) {
+ DefaultTbActorSystem actorSystem = (DefaultTbActorSystem) ReflectionTestUtils.getField(actorService, "system");
+ ConcurrentMap actors = (ConcurrentMap) ReflectionTestUtils.getField(actorSystem, "actors");
+ Awaitility.await("Device actor was created").atMost(TIMEOUT, TimeUnit.SECONDS)
+ .until(() -> actors.containsKey(new TbEntityActorId(deviceId)));
+ TbActorMailbox actorMailbox = actors.get(new TbEntityActorId(deviceId));
+ DeviceActor actor = (DeviceActor) ReflectionTestUtils.getField(actorMailbox, "actor");
+ return (DeviceActorMessageProcessor) ReflectionTestUtils.getField(actor, "processor");
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java
index 021171f1af..e6c27aa4a8 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java
@@ -21,12 +21,9 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.Test;
import org.mockito.Mockito;
-import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.common.util.JacksonUtil;
-import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.security.model.JwtSettings;
-import org.thingsboard.server.service.mail.DefaultMailService;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
@@ -35,6 +32,8 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -43,12 +42,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
public abstract class BaseAdminControllerTest extends AbstractControllerTest {
final JwtSettings defaultJwtSettings = new JwtSettings(9000, 604800, "thingsboard.io", "thingsboardDefaultSigningKey");
- @Autowired
- MailService mailService;
-
- @Autowired
- DefaultMailService defaultMailService;
-
@Test
public void testFindAdminSettingsByKey() throws Exception {
loginSysAdmin();
@@ -118,10 +111,12 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
@Test
public void testSendTestMail() throws Exception {
+ Mockito.doNothing().when(mailService).sendTestMail(any(), anyString());
loginSysAdmin();
AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class);
doPost("/api/admin/settings/testMail", adminSettings)
.andExpect(status().isOk());
+ Mockito.verify(mailService).sendTestMail(Mockito.any(), Mockito.anyString());
}
@Test
@@ -137,15 +132,8 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest {
adminSettings.setJsonValue(objectNode);
- Mockito.doAnswer((invocations) -> {
- var jsonConfig = (JsonNode) invocations.getArgument(0);
- var email = (String) invocations.getArgument(1);
-
- defaultMailService.sendTestMail(jsonConfig, email);
- return null;
- }).when(mailService).sendTestMail(Mockito.any(), Mockito.anyString());
doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError());
- Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any());
+ Mockito.verify(mailService).sendTestMail(Mockito.any(), Mockito.anyString());
}
void resetJwtSettingsToDefault() throws Exception {
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
index 7c8b3c4db8..8566d9d6a4 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmCommentControllerTest.java
@@ -79,7 +79,6 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
- .status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("test alarm type")
.build();
@@ -316,7 +315,6 @@ public abstract class BaseAlarmCommentControllerTest extends AbstractControllerT
Alarm alarm = Alarm.builder()
.originator(device.getId())
- .status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
index 175f833c1c..6a94cc5a1e 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java
@@ -29,6 +29,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Primary;
import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
@@ -124,7 +125,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
- testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.UPDATED);
}
@@ -140,8 +142,57 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Assert.assertNotNull(updatedAlarm);
Assert.assertEquals(AlarmSeverity.MAJOR, updatedAlarm.getSeverity());
- testNotifyEntityAllOneTime(updatedAlarm, updatedAlarm.getId(), updatedAlarm.getOriginator(),
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.UPDATED);
+
+ alarm = updatedAlarm;
+ alarm.setAcknowledged(true);
+ alarm.setAckTs(System.currentTimeMillis() - 1000);
+ updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertTrue(updatedAlarm.isAcknowledged());
+ Assert.assertEquals(alarm.getAckTs(), updatedAlarm.getAckTs());
+
+ foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ACK);
+
+ alarm = updatedAlarm;
+ alarm.setCleared(true);
+ alarm.setClearTs(System.currentTimeMillis() - 1000);
+ updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertTrue(updatedAlarm.isCleared());
+ Assert.assertEquals(alarm.getClearTs(), updatedAlarm.getClearTs());
+
+ foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_CLEAR);
+
+ alarm = updatedAlarm;
+ alarm.setAssigneeId(tenantAdminUserId);
+ alarm.setAssignTs(System.currentTimeMillis() - 1000);
+ updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertEquals(tenantAdminUserId, updatedAlarm.getAssigneeId());
+ Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs());
+
+ foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+
+ alarm = updatedAlarm;
+ alarm.setAssigneeId(null);
+ alarm.setAssignTs(System.currentTimeMillis() - 1000);
+ updatedAlarm = doPost("/api/alarm", alarm, Alarm.class);
+ Assert.assertNotNull(updatedAlarm);
+ Assert.assertNull(updatedAlarm.getAssigneeId());
+ Assert.assertEquals(alarm.getAssignTs(), updatedAlarm.getAssignTs());
+
+ foundAlarm = doGet("/api/alarm/info/" + updatedAlarm.getId(), AlarmInfo.class);
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
}
@Test
@@ -187,7 +238,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
- testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
+ testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(),
tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.DELETED);
}
@@ -200,7 +251,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doDelete("/api/alarm/" + alarm.getId()).andExpect(status().isOk());
- testNotifyEntityOneTimeMsgToEdgeServiceNever(alarm, alarm.getId(), alarm.getOriginator(),
+ testNotifyEntityOneTimeMsgToEdgeServiceNever(new Alarm(alarm), alarm.getId(), alarm.getOriginator(),
tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.DELETED);
}
@@ -245,7 +296,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
- Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
@@ -261,7 +312,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/alarm/" + alarm.getId() + "/clear").andExpect(status().isOk());
- Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.CLEARED_UNACK, foundAlarm.getStatus());
@@ -278,7 +329,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
doPost("/api/alarm/" + alarm.getId() + "/ack").andExpect(status().isOk());
- Alarm foundAlarm = doGet("/api/alarm/" + alarm.getId(), Alarm.class);
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
Assert.assertNotNull(foundAlarm);
Assert.assertEquals(AlarmStatus.ACTIVE_ACK, foundAlarm.getStatus());
@@ -348,6 +399,135 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
.andExpect(statusReason(containsString(msgErrorPermission)));
}
+ @Test
+ public void testAssignAlarm() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ Mockito.reset(tbClusterService, auditLogService);
+ long beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+ }
+
+ @Test
+ public void testAssignAlarmViaDifferentTenant() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+
+ loginDifferentTenant();
+
+ Mockito.reset(tbClusterService, auditLogService);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isForbidden());
+ }
+
+ @Test
+ public void testReassignAlarm() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ Mockito.reset(tbClusterService, auditLogService);
+ long beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
+
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+
+ logout();
+
+ loginCustomerUser();
+ Mockito.reset(tbClusterService, auditLogService);
+ beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + customerUserId.getId()).andExpect(status().isOk());
+
+ foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(customerUserId, foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_ASSIGN);
+ }
+
+ @Test
+ public void testUnassignAlarm() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ Mockito.reset(tbClusterService, auditLogService);
+ long beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+
+ beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+ doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
+ foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertNull(foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_UNASSIGN);
+ }
+
+ @Test
+ public void testUnassignTenantAlarmViaCustomer() throws Exception {
+ loginTenantAdmin();
+ Alarm alarm = createAlarm(TEST_ALARM_TYPE);
+ Mockito.reset(tbClusterService, auditLogService);
+ long beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doPost("/api/alarm/" + alarm.getId() + "/assign/" + tenantAdminUserId.getId()).andExpect(status().isOk());
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(tenantAdminUserId, foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, tenantAdminUserId, TENANT_ADMIN_EMAIL, ActionType.ALARM_ASSIGN);
+
+ logout();
+ loginCustomerUser();
+
+ Mockito.reset(tbClusterService, auditLogService);
+ beforeAssignmentTs = System.currentTimeMillis();
+ Thread.sleep(2);
+
+ doDelete("/api/alarm/" + alarm.getId() + "/assign").andExpect(status().isOk());
+ foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertNull(foundAlarm.getAssigneeId());
+ Assert.assertTrue(foundAlarm.getAssignTs() > beforeAssignmentTs && foundAlarm.getAssignTs() < System.currentTimeMillis());
+
+ testNotifyEntityAllOneTime(foundAlarm, foundAlarm.getId(), foundAlarm.getOriginator(),
+ tenantId, customerId, customerUserId, CUSTOMER_USER_EMAIL, ActionType.ALARM_UNASSIGN);
+ }
+
@Test
public void testFindAlarmsViaCustomerUser() throws Exception {
loginCustomerUser();
@@ -364,7 +544,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
var response = doGetTyped(
"/api/alarm/" + EntityType.DEVICE + "/"
+ customerDevice.getUuidId() + "?page=0&pageSize=" + size,
- new TypeReference>() {}
+ new TypeReference>() {
+ }
);
var foundAlarmInfos = response.getData();
Assert.assertNotNull("Found pageData is null", foundAlarmInfos);
@@ -412,7 +593,6 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
Alarm alarm = Alarm.builder()
.originator(device.getId())
- .status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type("Test")
.build();
@@ -431,7 +611,8 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
this.token = tokens.get("token").asText();
PageData pageData = doGetTyped(
- "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() {}
+ "/api/alarm/DEVICE/" + device.getUuidId() + "?page=0&pageSize=1", new TypeReference>() {
+ }
);
Assert.assertNotNull("Found pageData is null", pageData);
@@ -457,12 +638,11 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(alarmDao, customerDevice.getId(), alarmId, "/api/alarm/" + alarmId);
}
- private Alarm createAlarm(String type) throws Exception {
+ private AlarmInfo createAlarm(String type) throws Exception {
Alarm alarm = Alarm.builder()
.tenantId(tenantId)
.customerId(customerId)
.originator(customerDevice.getId())
- .status(AlarmStatus.ACTIVE_UNACK)
.severity(AlarmSeverity.CRITICAL)
.type(type)
.build();
@@ -470,6 +650,10 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest {
alarm = doPost("/api/alarm", alarm, Alarm.class);
Assert.assertNotNull(alarm);
- return alarm;
+ AlarmInfo foundAlarm = doGet("/api/alarm/info/" + alarm.getId(), AlarmInfo.class);
+ Assert.assertNotNull(foundAlarm);
+ Assert.assertEquals(alarm, new Alarm(foundAlarm));
+
+ return foundAlarm;
}
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
index f289c8066f..7149f52630 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
@@ -44,7 +44,6 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.user.UserDao;
-import org.thingsboard.server.service.mail.TestMailService;
import java.util.ArrayList;
import java.util.Collections;
@@ -54,6 +53,8 @@ import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.is;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -106,12 +107,12 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
Mockito.reset(tbClusterService, auditLogService);
resetTokens();
- doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken)
+ doGet("/api/noauth/activate?activateToken={activateToken}", this.currentActivateToken)
.andExpect(status().isSeeOther())
- .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken));
+ .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + this.currentActivateToken));
JsonNode activateRequest = new ObjectMapper().createObjectNode()
- .put("activateToken", TestMailService.currentActivateToken)
+ .put("activateToken", this.currentActivateToken)
.put("password", "testPassword");
JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class);
@@ -208,17 +209,19 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest)
.andExpect(status().isOk());
Thread.sleep(1000);
- doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken)
+ doGet("/api/noauth/resetPassword?resetToken={resetToken}", this.currentResetPasswordToken)
.andExpect(status().isSeeOther())
- .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken));
+ .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + this.currentResetPasswordToken));
JsonNode resetPasswordRequest = new ObjectMapper().createObjectNode()
- .put("resetToken", TestMailService.currentResetPasswordToken)
+ .put("resetToken", this.currentResetPasswordToken)
.put("password", "testPassword2");
+ Mockito.doNothing().when(mailService).sendPasswordWasResetEmail(anyString(), anyString());
JsonNode tokenInfo = readResponse(
doPost("/api/noauth/resetPassword", resetPasswordRequest)
.andExpect(status().isOk()), JsonNode.class);
+ Mockito.verify(mailService).sendPasswordWasResetEmail(anyString(), anyString());
validateAndSetJwtToken(tokenInfo, email);
doGet("/api/auth/user")
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
index 09fb5c505d..ce354749f3 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
@@ -548,7 +548,7 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
SingleEntityFilter entityFilter = new SingleEntityFilter();
entityFilter.setSingleEntity(tenantId);
- assertThatNoException().isThrownBy(() -> {
+ assertThatNoException().as("subscribeForAttributes").isThrownBy(() -> {
JsonNode update = getWsClient().subscribeForAttributes(tenantId, TbAttributeSubscriptionScope.SERVER_SCOPE.name(), List.of("attr"));
assertThat(update.get("errorMsg").isNull()).isTrue();
assertThat(update.get("errorCode").asInt()).isEqualTo(SubscriptionErrorCode.NO_ERROR.getCode());
@@ -560,7 +560,7 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
new BaseAttributeKvEntry(System.currentTimeMillis(), new StringDataEntry("attr", expectedAttrValue))
));
JsonNode update = JacksonUtil.toJsonNode(getWsClient().waitForUpdate());
- assertThat(update).isNotNull();
+ assertThat(update).as("waitForUpdate").isNotNull();
assertThat(update.get("data").get("attr").get(0).get(1).asText()).isEqualTo(expectedAttrValue);
}
@@ -569,15 +569,17 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
tsService.saveAndNotify(device.getTenantId(), null, device.getId(), tsData, 0, new FutureCallback() {
@Override
public void onSuccess(@Nullable Void result) {
+ log.debug("sendTelemetry callback onSuccess");
latch.countDown();
}
@Override
public void onFailure(Throwable t) {
+ log.error("Failed to send telemetry", t);
latch.countDown();
}
});
- latch.await(3, TimeUnit.SECONDS);
+ assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendTelemetry callback");
}
private void sendAttributes(Device device, TbAttributeSubscriptionScope scope, List attrData) throws InterruptedException {
@@ -589,14 +591,16 @@ public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
tsService.saveAndNotify(tenantId, entityId, scope.name(), attrData, new FutureCallback() {
@Override
public void onSuccess(@Nullable Void result) {
+ log.debug("sendAttributes callback onSuccess");
latch.countDown();
}
@Override
public void onFailure(Throwable t) {
+ log.error("Failed to sendAttributes", t);
latch.countDown();
}
});
- latch.await(3, TimeUnit.SECONDS);
+ assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).as("await sendAttributes callback").isTrue();
}
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
index 56df8513fe..828d279e93 100644
--- a/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/BaseWidgetsBundleControllerTest.java
@@ -192,7 +192,7 @@ public abstract class BaseWidgetsBundleControllerTest extends AbstractController
WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class);
savedWidgetsBundle.setAlias("new_alias");
- Mockito.reset(tbClusterService);
+ Mockito.clearInvocations(tbClusterService);
doPost("/api/widgetsBundle", savedWidgetsBundle)
.andExpect(status().isBadRequest())
diff --git a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
index db26e9f6df..1f9ac4ea69 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
@@ -47,6 +47,7 @@ import java.util.concurrent.TimeUnit;
@Slf4j
public class TbTestWebSocketClient extends WebSocketClient {
+ private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(30);
private volatile String lastMsg;
private volatile CountDownLatch reply;
private volatile CountDownLatch update;
@@ -87,12 +88,14 @@ public class TbTestWebSocketClient extends WebSocketClient {
}
public void registerWaitForUpdate(int count) {
+ log.debug("registerWaitForUpdate [{}]", count);
lastMsg = null;
update = new CountDownLatch(count);
}
@Override
public void send(String text) throws NotYetConnectedException {
+ log.debug("send [{}]", text);
reply = new CountDownLatch(1);
super.send(text);
}
@@ -110,21 +113,31 @@ public class TbTestWebSocketClient extends WebSocketClient {
}
public String waitForUpdate() {
- return waitForUpdate(TimeUnit.SECONDS.toMillis(3));
+ return waitForUpdate(TIMEOUT);
}
public String waitForUpdate(long ms) {
+ log.debug("waitForUpdate [{}]", ms);
try {
- update.await(ms, TimeUnit.MILLISECONDS);
+ if (!update.await(ms, TimeUnit.MILLISECONDS)) {
+ log.warn("Failed to await update (waiting time [{}]ms elapsed)", ms, new RuntimeException("stacktrace"));
+ }
} catch (InterruptedException e) {
- log.warn("Failed to await reply", e);
+ log.warn("Failed to await update", e);
}
return lastMsg;
}
public String waitForReply() {
+ return waitForReply(TIMEOUT);
+ }
+
+ public String waitForReply(long ms) {
+ log.debug("waitForReply [{}]", ms);
try {
- reply.await(3, TimeUnit.SECONDS);
+ if (!reply.await(ms, TimeUnit.MILLISECONDS)) {
+ log.warn("Failed to await reply (waiting time [{}]ms elapsed)", ms, new RuntimeException("stacktrace"));
+ }
} catch (InterruptedException e) {
log.warn("Failed to await reply", e);
}
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
index 5014280b07..1cf2cd44bb 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java
@@ -76,7 +76,6 @@ abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest {
Device device = findDeviceByName("Edge Device 1");
Alarm alarm = new Alarm();
alarm.setOriginator(device.getId());
- alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
alarm.setType("alarm");
alarm.setSeverity(AlarmSeverity.CRITICAL);
edgeImitator.expectMessageAmount(1);
diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
index b266465bd1..98508e72ce 100644
--- a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
+++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java
@@ -49,6 +49,7 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
+import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg;
import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg;
@@ -668,6 +669,7 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest {
client.connectAndWait(deviceCredentials.getCredentialsId());
MqttTestCallback onUpdateCallback = new MqttTestCallback();
client.setCallback(onUpdateCallback);
+
client.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE);
edgeImitator.expectResponsesAmount(1);
diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
index f9916d27a0..93aeb17de6 100644
--- a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java
@@ -27,9 +27,10 @@ import org.springframework.test.context.junit4.SpringRunner;
import org.thingsboard.server.cluster.TbClusterService;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.alarm.Alarm;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.UserId;
+import org.thingsboard.server.dao.alarm.AlarmApiCallResult;
import org.thingsboard.server.dao.alarm.AlarmCommentService;
import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.customer.CustomerService;
@@ -81,36 +82,41 @@ public class DefaultTbAlarmServiceTest {
@Test
public void testSave() throws ThingsboardException {
- var alarm = new Alarm();
- when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm);
+ var alarm = new AlarmInfo();
+ when(alarmSubscriptionService.createAlarm(any())).thenReturn(AlarmApiCallResult.builder()
+ .successful(true)
+ .modified(true)
+ .alarm(alarm)
+ .build());
service.save(alarm, new User());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
- verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm));
+ verify(alarmSubscriptionService, times(1)).createAlarm(any());
}
@Test
- public void testAck() {
+ public void testAck() throws ThingsboardException {
var alarm = new Alarm();
- alarm.setStatus(AlarmStatus.ACTIVE_UNACK);
- when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
+ when(alarmSubscriptionService.acknowledgeAlarm(any(), any(), anyLong()))
+ .thenReturn(AlarmApiCallResult.builder().successful(true).modified(true).build());
service.ack(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
- verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong());
+ verify(alarmSubscriptionService, times(1)).acknowledgeAlarm(any(), any(), anyLong());
}
@Test
- public void testClear() {
+ public void testClear() throws ThingsboardException {
var alarm = new Alarm();
- alarm.setStatus(AlarmStatus.ACTIVE_ACK);
- when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true));
+ alarm.setAcknowledged(true);
+ when(alarmSubscriptionService.clearAlarm(any(), any(), anyLong(), any()))
+ .thenReturn(AlarmApiCallResult.builder().successful(true).cleared(true).build());
service.clear(alarm, new User(new UserId(UUID.randomUUID())));
verify(alarmCommentService, times(1)).createOrUpdateAlarmComment(any(), any());
verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any());
- verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong());
+ verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), anyLong(), any());
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java b/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java
deleted file mode 100644
index 7c4c45c951..0000000000
--- a/application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java
+++ /dev/null
@@ -1,58 +0,0 @@
-/**
- * Copyright © 2016-2023 The Thingsboard Authors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.thingsboard.server.service.mail;
-
-import org.mockito.Mockito;
-import org.mockito.invocation.InvocationOnMock;
-import org.mockito.stubbing.Answer;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Primary;
-import org.springframework.context.annotation.Profile;
-import org.thingsboard.rule.engine.api.MailService;
-import org.thingsboard.server.common.data.exception.ThingsboardException;
-
-@Profile("test")
-@Configuration
-public class TestMailService {
-
- public static String currentActivateToken;
- public static String currentResetPasswordToken;
-
- @Bean
- @Primary
- public MailService mailService() throws ThingsboardException {
- MailService mailService = Mockito.mock(MailService.class);
- Mockito.doAnswer(new Answer() {
- public Void answer(InvocationOnMock invocation) {
- Object[] args = invocation.getArguments();
- String activationLink = (String) args[0];
- currentActivateToken = activationLink.split("=")[1];
- return null;
- }
- }).when(mailService).sendActivationEmail(Mockito.anyString(), Mockito.anyString());
- Mockito.doAnswer(new Answer() {
- public Void answer(InvocationOnMock invocation) {
- Object[] args = invocation.getArguments();
- String passwordResetLink = (String) args[0];
- currentResetPasswordToken = passwordResetLink.split("=")[1];
- return null;
- }
- }).when(mailService).sendResetPasswordEmailAsync(Mockito.anyString(), Mockito.anyString());
- return mailService;
- }
-
-}
diff --git a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
index ce02296733..b0282832bc 100644
--- a/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/resource/sql/BaseTbResourceServiceTest.java
@@ -21,6 +21,7 @@ import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.jupiter.api.Assertions;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.EntityInfo;
@@ -237,7 +238,7 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resourceService.delete(savedResource, null);
}
- @Test(expected = DataValidationException.class)
+ @Test
public void testSaveTbResourceWithExistsFileName() throws Exception {
TbResource resource = new TbResource();
resource.setTenantId(tenantId);
@@ -256,23 +257,27 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setData("Test Data");
try {
- resourceService.save(resource2);
+ Assertions.assertThrows(DataValidationException.class, () -> {
+ resourceService.save(resource2);
+ });
} finally {
resourceService.delete(savedResource, null);
}
}
- @Test(expected = DataValidationException.class)
+ @Test
public void testSaveTbResourceWithEmptyTitle() throws Exception {
TbResource resource = new TbResource();
resource.setTenantId(tenantId);
resource.setResourceType(ResourceType.JKS);
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
- resourceService.save(resource);
+ Assertions.assertThrows(DataValidationException.class, () -> {
+ resourceService.save(resource);
+ });
}
- @Test(expected = DataValidationException.class)
+ @Test
public void testSaveTbResourceWithInvalidTenant() throws Exception {
TbResource resource = new TbResource();
resource.setTenantId(TenantId.fromUUID(Uuids.timeBased()));
@@ -280,7 +285,9 @@ public class BaseTbResourceServiceTest extends AbstractControllerTest {
resource.setTitle("My resource");
resource.setFileName(DEFAULT_FILE_NAME);
resource.setData("Test Data");
- resourceService.save(resource);
+ Assertions.assertThrows(DataValidationException.class, () -> {
+ resourceService.save(resource);
+ });
}
@Test
diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
index 08cd031a52..4373a4c108 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java
@@ -36,6 +36,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
@DaoSqlTest
@TestPropertySource(properties = {
+ "js.evaluator=local",
"js.max_script_body_size=50",
"js.max_total_args_size=50",
"js.max_result_size=50",
diff --git a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java
index cf4de21cc2..89a4b707bf 100644
--- a/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/AbstractTransportIntegrationTest.java
@@ -28,7 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
public abstract class AbstractTransportIntegrationTest extends AbstractControllerTest {
- protected static final int DEFAULT_WAIT_TIMEOUT_SECONDS = 10;
+ protected static final int DEFAULT_WAIT_TIMEOUT_SECONDS = 30;
protected static final String MQTT_URL = "tcp://localhost:1883";
protected static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/";
diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java
index 41cc4c2b76..ab2f6b9bca 100644
--- a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java
+++ b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java
@@ -15,30 +15,14 @@
*/
package org.thingsboard.server.transport;
-import org.cassandraunit.dataset.cql.ClassPathCQLDataSet;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
import org.junit.extensions.cpsuite.ClasspathSuite;
import org.junit.runner.RunWith;
-import org.thingsboard.server.dao.CustomCassandraCQLUnit;
-import org.thingsboard.server.queue.memory.InMemoryStorage;
-
-import java.util.Arrays;
+import org.thingsboard.server.dao.AbstractNoSqlContainer;
@RunWith(ClasspathSuite.class)
@ClasspathSuite.ClassnameFilters({
"org.thingsboard.server.transport.*.telemetry.timeseries.nosql.*Test",
})
-public class TransportNoSqlTestSuite {
-
- @ClassRule
- public static CustomCassandraCQLUnit cassandraUnit =
- new CustomCassandraCQLUnit(
- Arrays.asList(
- new ClassPathCQLDataSet("cassandra/schema-keyspace.cql", false, false),
- new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false),
- new ClassPathCQLDataSet("cassandra/schema-ts-latest.cql", false, false)
- ),
- "cassandra-test.yaml", 30000l);
+public class TransportNoSqlTestSuite extends AbstractNoSqlContainer {
}
diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
index d90af7b6ba..1266ef2c29 100644
--- a/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/coap/AbstractCoapIntegrationTest.java
@@ -45,6 +45,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@TestPropertySource(properties = {
+ "coap.enabled=true",
+ "service.integrations.supported=ALL",
"transport.coap.enabled=true",
})
@Slf4j
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
index 0f1815b46b..6b84761208 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/AbstractMqttIntegrationTest.java
@@ -16,7 +16,9 @@
package org.thingsboard.server.transport.mqtt;
import com.fasterxml.jackson.databind.node.ObjectNode;
+import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
+import org.eclipse.paho.client.mqttv3.MqttException;
import org.springframework.test.context.TestPropertySource;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@@ -36,9 +38,12 @@ import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadCon
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
+import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.AbstractTransportIntegrationTest;
+import org.thingsboard.server.transport.mqtt.mqttv3.MqttTestClient;
import java.util.List;
@@ -46,8 +51,8 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
@TestPropertySource(properties = {
+ "service.integrations.supported=ALL",
"transport.mqtt.enabled=true",
- "js.evaluator=mock",
})
@Slf4j
public abstract class AbstractMqttIntegrationTest extends AbstractTransportIntegrationTest {
@@ -103,6 +108,8 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
if (StringUtils.hasLength(config.getAttributesTopicFilter())) {
mqttDeviceProfileTransportConfiguration.setDeviceAttributesTopic(config.getAttributesTopicFilter());
}
+ mqttDeviceProfileTransportConfiguration.setSparkplug(config.isSparkplug());
+ mqttDeviceProfileTransportConfiguration.setSparkplugAttributesMetricNames(config.sparkplugAttributesMetricNames);
mqttDeviceProfileTransportConfiguration.setSendAckOnValidationException(config.isSendAckOnValidationException());
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration;
if (TransportPayloadType.JSON.equals(transportPayloadType)) {
@@ -176,4 +183,25 @@ public abstract class AbstractMqttIntegrationTest extends AbstractTransportInteg
builder.addAllKv(kvProtos);
return builder.build();
}
+
+ protected void subscribeAndWait(MqttTestClient client, String attrSubTopic, DeviceId deviceId, FeatureType featureType) throws MqttException {
+ int subscriptionCount = getDeviceActorSubscriptionCount(deviceId, featureType);
+ client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
+ // TODO: This test awaits for the device actor to receive the subscription. Ideally it should not happen. See details below:
+ // The transport layer acknowledge subscription request once the message about subscription is in the queue.
+ // Test sends data immediately after acknowledgement.
+ // But there is a time lag between push to the queue and read from the queue in the tb-core component.
+ // Ideally, we should reply to device with SUBACK only when the device actor on the tb-core receives the message.
+ awaitForDeviceActorToReceiveSubscription(deviceId, featureType, subscriptionCount + 1);
+ }
+
+ protected void subscribeAndCheckSubscription(MqttTestClient client, String attrSubTopic, DeviceId deviceId, FeatureType featureType) throws MqttException {
+ client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
+ // TODO: This test awaits for the device actor to receive the subscription. Ideally it should not happen. See details below:
+ // The transport layer acknowledge subscription request once the message about subscription is in the queue.
+ // Test sends data immediately after acknowledgement.
+ // But there is a time lag between push to the queue and read from the queue in the tb-core component.
+ // Ideally, we should reply to device with SUBACK only when the device actor on the tb-core receives the message.
+ awaitForDeviceActorToReceiveSubscription(deviceId, featureType, 1);
+ }
}
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java
index 8bedc48b73..5815adae76 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/MqttTestConfigProperties.java
@@ -20,12 +20,16 @@ import lombok.Data;
import org.thingsboard.server.common.data.DeviceProfileProvisionType;
import org.thingsboard.server.common.data.TransportPayloadType;
+import java.util.Set;
+
@Data
@Builder
public class MqttTestConfigProperties {
String deviceName;
String gatewayName;
+ boolean isSparkplug;
+ Set sparkplugAttributesMetricNames;
TransportPayloadType transportPayloadType;
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java
index 511dabb5b7..e4b3058dfd 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/MqttTestClient.java
@@ -31,7 +31,7 @@ public class MqttTestClient {
private static final String MQTT_URL = "tcp://localhost:1883";
private static final int TIMEOUT = 30; // seconds
- private static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT);
+ public static final long TIMEOUT_MS = TimeUnit.SECONDS.toMillis(TIMEOUT);
private final MqttAsyncClient client;
diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java
index 2c0278f689..f8165b0237 100644
--- a/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java
+++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/mqttv3/attributes/AbstractMqttAttributesIntegrationTest.java
@@ -15,6 +15,7 @@
*/
package org.thingsboard.server.transport.mqtt.mqttv3.attributes;
+import com.fasterxml.jackson.core.type.TypeReference;
import com.github.os72.protobuf.dynamic.DynamicSchema;
import com.google.protobuf.Descriptors;
import com.google.protobuf.DynamicMessage;
@@ -22,6 +23,7 @@ import com.google.protobuf.InvalidProtocolBufferException;
import com.squareup.wire.schema.internal.parser.ProtoFileElement;
import io.netty.handler.codec.mqtt.MqttQoS;
import lombok.extern.slf4j.Slf4j;
+import org.awaitility.Awaitility;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DynamicProtoUtils;
@@ -30,12 +32,14 @@ import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportC
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration;
+import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityData;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter;
+import org.thingsboard.server.common.msg.session.FeatureType;
import org.thingsboard.server.gen.transport.TransportApiProtos;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
@@ -45,6 +49,7 @@ import org.thingsboard.server.transport.mqtt.mqttv3.MqttTestClient;
import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
@@ -118,21 +123,26 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
// subscribe to attributes updates from server methods
protected void processJsonTestSubscribeToAttributesUpdates(String attrSubTopic) throws Exception {
+ DeviceId deviceId = savedDevice.getId();
+
MqttTestClient client = new MqttTestClient();
client.connectAndWait(accessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
client.setCallback(onUpdateCallback);
- client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
- doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
- onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ subscribeAndWait(client, attrSubTopic, deviceId, FeatureType.ATTRIBUTES);
+
+ doPostAsync("/api/plugins/telemetry/DEVICE/" + deviceId.getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
+ assertThat(onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onUpdateCallback").isTrue();
validateUpdateAttributesJsonResponse(onUpdateCallback, SHARED_ATTRIBUTES_PAYLOAD);
MqttTestCallback onDeleteCallback = new MqttTestCallback();
client.setCallback(onDeleteCallback);
- doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
- onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ doDelete("/api/plugins/telemetry/DEVICE/" + deviceId.getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
+ assertThat(onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onDeleteCallback").isTrue();
validateUpdateAttributesJsonResponse(onDeleteCallback, SHARED_ATTRIBUTES_DELETED_RESPONSE);
client.disconnect();
}
@@ -142,16 +152,18 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
client.connectAndWait(accessToken);
MqttTestCallback onUpdateCallback = new MqttTestCallback();
client.setCallback(onUpdateCallback);
- client.subscribeAndWait(attrSubTopic, MqttQoS.AT_MOST_ONCE);
+ subscribeAndWait(client, attrSubTopic, savedDevice.getId(), FeatureType.ATTRIBUTES);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
- onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onUpdateCallback").isTrue();
validateUpdateAttributesProtoResponse(onUpdateCallback);
MqttTestCallback onDeleteCallback = new MqttTestCallback();
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
- onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onDeleteCallback").isTrue();
validateDeleteAttributesProtoResponse(onDeleteCallback);
client.disconnect();
}
@@ -162,7 +174,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
}
protected void validateUpdateAttributesProtoResponse(MqttTestCallback callback) throws InvalidProtocolBufferException {
- assertNotNull(callback.getPayloadBytes());
+ assertThat(callback.getPayloadBytes()).as("callback payload non-null").isNotNull();
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List tsKvProtoList = getTsKvProtoList("shared");
attributeUpdateNotificationMsgBuilder.addAllSharedUpdated(tsKvProtoList);
@@ -178,7 +190,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
}
protected void validateDeleteAttributesProtoResponse(MqttTestCallback callback) throws InvalidProtocolBufferException {
- assertNotNull(callback.getPayloadBytes());
+ assertThat(callback.getPayloadBytes()).as("callback payload non-null").isNotNull();
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("sharedJson");
@@ -206,10 +218,11 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
assertNotNull(savedDevice);
- client.subscribeAndWait(GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
+ subscribeAndCheckSubscription(client, GATEWAY_ATTRIBUTES_TOPIC, savedDevice.getId(), FeatureType.ATTRIBUTES);
doPostAsync("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
- onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(onUpdateCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onUpdateCallback").isTrue();
validateJsonGatewayUpdateAttributesResponse(onUpdateCallback, deviceName, SHARED_ATTRIBUTES_PAYLOAD);
@@ -217,7 +230,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
client.setCallback(onDeleteCallback);
doDelete("/api/plugins/telemetry/DEVICE/" + savedDevice.getId().getId() + "/SHARED_SCOPE?keys=sharedJson", String.class);
- onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+ assertThat(onDeleteCallback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await onDeleteCallback").isTrue();
validateJsonGatewayUpdateAttributesResponse(onDeleteCallback, deviceName, SHARED_ATTRIBUTES_DELETED_RESPONSE);
client.disconnect();
@@ -235,7 +249,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
20,
100);
assertNotNull(device);
- client.subscribeAndWait(GATEWAY_ATTRIBUTES_TOPIC, MqttQoS.AT_MOST_ONCE);
+
+ subscribeAndCheckSubscription(client, GATEWAY_ATTRIBUTES_TOPIC, device.getId(), FeatureType.ATTRIBUTES);
doPostAsync("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/attributes/SHARED_SCOPE", SHARED_ATTRIBUTES_PAYLOAD, String.class, status().isOk());
validateProtoGatewayUpdateAttributesResponse(onUpdateCallback, deviceName);
MqttTestCallback onDeleteCallback = new MqttTestCallback();
@@ -246,7 +261,7 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
}
protected void validateJsonGatewayUpdateAttributesResponse(MqttTestCallback callback, String deviceName, String expectResultData) {
- assertNotNull(callback.getPayloadBytes());
+ assertThat(callback.getPayloadBytes()).as("callback payload non-null").isNotNull();
assertEquals(JacksonUtil.toJsonNode(getGatewayAttributesResponseJson(deviceName, expectResultData)), JacksonUtil.fromBytes(callback.getPayloadBytes()));
}
@@ -260,8 +275,9 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
}
protected void validateProtoGatewayUpdateAttributesResponse(MqttTestCallback callback, String deviceName) throws InvalidProtocolBufferException, InterruptedException {
- callback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- assertNotNull(callback.getPayloadBytes());
+ assertThat(callback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await callback").isTrue();
+ assertThat(callback.getPayloadBytes()).as("callback payload non-null").isNotNull();
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
List tsKvProtoList = getTsKvProtoList("shared");
@@ -285,8 +301,9 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
}
protected void validateProtoGatewayDeleteAttributesResponse(MqttTestCallback callback, String deviceName) throws InvalidProtocolBufferException, InterruptedException {
- callback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS);
- assertNotNull(callback.getPayloadBytes());
+ assertThat(callback.getSubscribeLatch().await(DEFAULT_WAIT_TIMEOUT_SECONDS, TimeUnit.SECONDS))
+ .as("await callback").isTrue();
+ assertThat(callback.getPayloadBytes()).as("callback payload non-null").isNotNull();
TransportProtos.AttributeUpdateNotificationMsg.Builder attributeUpdateNotificationMsgBuilder = TransportProtos.AttributeUpdateNotificationMsg.newBuilder();
attributeUpdateNotificationMsgBuilder.addSharedDeleted("sharedJson");
TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotificationMsg = attributeUpdateNotificationMsgBuilder.build();
@@ -391,9 +408,20 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt
100);
assertNotNull(device);
+ String clientKeysStr = "clientStr,clientBool,clientDbl,clientLong,clientJson";
+
+ String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/CLIENT_SCOPE?keys=" + clientKeysStr;
+
+ Awaitility.await()
+ .atMost(10, TimeUnit.SECONDS)
+ .until(() -> {
+ List