Browse Source

Merge pull request #6431 from smatvienko-tb/tests-speedup-tenant

[3.4] Tests performance improvements
pull/6519/head
Andrew Shvayka 4 years ago
committed by GitHub
parent
commit
761e36be7f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  2. 57
      application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
  3. 34
      application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java
  4. 54
      application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java
  5. 96
      application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java
  6. 371
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  7. 355
      application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java
  8. 244
      application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java
  9. 382
      application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java
  10. 119
      application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java
  11. 50
      application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java
  12. 2
      application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java
  13. 24
      application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java
  14. 2
      application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java
  15. 35
      application/src/test/resources/application-test.properties
  16. 4
      common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java
  17. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java
  18. 2
      common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java
  19. 76
      dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java
  20. 45
      dao/src/test/resources/application-test.properties
  21. 11
      dao/src/test/resources/nosql-test.properties
  22. 7
      dao/src/test/resources/sql-test.properties
  23. 32
      msa/black-box-tests/src/test/resources/logback.xml
  24. 23
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java
  25. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java
  26. 80
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java

2
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
@ -84,6 +85,7 @@ import static springfox.documentation.builders.PathSelectors.regex;
@Slf4j @Slf4j
@Configuration @Configuration
@TbCoreComponent @TbCoreComponent
@Profile("!test")
public class SwaggerConfiguration { public class SwaggerConfiguration {
@Value("${swagger.api_path_regex}") @Value("${swagger.api_path_regex}")

57
application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java

@ -16,16 +16,25 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootContextLoader; import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles; import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.web.socket.config.annotation.EnableWebSocket;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
@ActiveProfiles("test") @ActiveProfiles("test")
@RunWith(SpringRunner.class) @RunWith(SpringRunner.class)
@ -33,9 +42,49 @@ import org.springframework.test.context.web.WebAppConfiguration;
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@Configuration @Configuration
@ComponentScan({"org.thingsboard.server"}) @ComponentScan({"org.thingsboard.server"})
@WebAppConfiguration @EnableWebSocket
@SpringBootTest() @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j @Slf4j
public abstract class AbstractControllerTest extends AbstractWebTest { public abstract class AbstractControllerTest extends AbstractWebTest {
public static final String WS_URL = "ws://localhost:";
@LocalServerPort
protected int wsPort;
private TbTestWebSocketClient wsClient; // lazy
public TbTestWebSocketClient getWsClient() {
if (wsClient == null) {
synchronized (this) {
try {
if (wsClient == null) {
wsClient = buildAndConnectWebSocketClient();
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
return wsClient;
}
@Before
public void beforeWsTest() throws Exception {
// placeholder
}
@After
public void afterWsTest() throws Exception {
if (wsClient != null) {
wsClient.close();
}
}
private TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token));
assertThat(wsClient.connectBlocking(TIMEOUT, TimeUnit.SECONDS)).isTrue();
return wsClient;
}
} }

34
application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java

@ -18,6 +18,9 @@ package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import io.jsonwebtoken.Claims; import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Header; import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jwt; import io.jsonwebtoken.Jwt;
@ -67,6 +70,7 @@ import org.thingsboard.server.common.data.edge.Edge;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.HasId; import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
@ -97,6 +101,7 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
@Slf4j @Slf4j
public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
public static final int TIMEOUT = 30;
protected ObjectMapper mapper = new ObjectMapper(); protected ObjectMapper mapper = new ObjectMapper();
@ -106,7 +111,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
private static final String SYS_ADMIN_PASSWORD = "sysadmin"; private static final String SYS_ADMIN_PASSWORD = "sysadmin";
protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org"; protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
private static final String TENANT_ADMIN_PASSWORD = "tenant"; protected static final String TENANT_ADMIN_PASSWORD = "tenant";
protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org"; protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer"; private static final String CUSTOMER_USER_PASSWORD = "customer";
@ -509,16 +514,24 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType); return readResponse(doGet(urlTemplate, vars).andExpect(status().isOk()), responseType);
} }
protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) throws Exception { protected <T> T doPost(String urlTemplate, Class<T> responseClass, String... params) {
return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass); try {
return readResponse(doPost(urlTemplate, params).andExpect(status().isOk()), responseClass);
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception { protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, ResultMatcher resultMatcher, String... params) throws Exception {
return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass); return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass);
} }
protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, String... params) throws Exception { protected <T> T doPost(String urlTemplate, T content, Class<T> responseClass, String... params) {
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass); try {
return readResponse(doPost(urlTemplate, content, params).andExpect(status().isOk()), responseClass);
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
protected <T, R> R doPostWithResponse(String urlTemplate, T content, Class<R> responseClass, String... params) throws Exception { protected <T, R> R doPostWithResponse(String urlTemplate, T content, Class<R> responseClass, String... params) throws Exception {
@ -639,4 +652,15 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest {
edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20)); edge.setRoutingKey(RandomStringUtils.randomAlphanumeric(20));
return edge; return edge;
} }
protected <T extends HasId<? extends UUIDBased>> ListenableFuture<List<ResultActions>> deleteEntitiesAsync(String urlTemplate, List<T> entities, ListeningExecutorService executor) {
List<ListenableFuture<ResultActions>> futures = new ArrayList<>(entities.size());
for (T entity : entities) {
futures.add(executor.submit(() ->
doDelete(urlTemplate + entity.getId().getId())
.andExpect(status().isOk())));
}
return Futures.allAsList(futures);
}
} }

54
application/src/test/java/org/thingsboard/server/controller/AbstractWebsocketTest.java

@ -1,54 +0,0 @@
/**
* Copyright © 2016-2022 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.controller;
import lombok.extern.slf4j.Slf4j;
import org.junit.Assert;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootContextLoader;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
import java.net.URI;
import java.net.URISyntaxException;
@ActiveProfiles("test")
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = AbstractControllerTest.class, loader = SpringBootContextLoader.class)
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
@Configuration
@ComponentScan({"org.thingsboard.server"})
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
public abstract class AbstractWebsocketTest extends AbstractWebTest {
protected static final String WS_URL = "ws://localhost:";
@LocalServerPort
protected int wsPort;
protected TbTestWebSocketClient buildAndConnectWebSocketClient() throws URISyntaxException, InterruptedException {
TbTestWebSocketClient wsClient = new TbTestWebSocketClient(new URI(WS_URL + wsPort + "/api/ws/plugins/telemetry?token=" + token));
Assert.assertTrue(wsClient.connectBlocking());
return wsClient;
}
}

96
application/src/test/java/org/thingsboard/server/controller/BaseCustomerControllerTest.java

@ -15,16 +15,17 @@
*/ */
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import static org.hamcrest.Matchers.containsString; import com.fasterxml.jackson.core.type.TypeReference;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.ArrayList; import com.google.common.util.concurrent.ListeningExecutorService;
import java.util.Collections; import com.google.common.util.concurrent.MoreExecutors;
import java.util.List;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.User;
@ -32,20 +33,28 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.junit.Assert;
import org.junit.Test;
import com.fasterxml.jackson.core.type.TypeReference; import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
public abstract class BaseCustomerControllerTest extends AbstractControllerTest { public abstract class BaseCustomerControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<Customer>> PAGE_DATA_CUSTOMER_TYPE_REFERENCE = new TypeReference<>() {
};
private IdComparator<Customer> idComparator = new IdComparator<>(); ListeningExecutorService executor;
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -65,6 +74,8 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
executor.shutdownNow();
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
@ -83,11 +94,11 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
savedCustomer.setTitle("My new customer"); savedCustomer.setTitle("My new customer");
doPost("/api/customer", savedCustomer, Customer.class); doPost("/api/customer", savedCustomer, Customer.class);
Customer foundCustomer = doGet("/api/customer/"+savedCustomer.getId().getId().toString(), Customer.class); Customer foundCustomer = doGet("/api/customer/" + savedCustomer.getId().getId().toString(), Customer.class);
Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
doDelete("/api/customer/"+savedCustomer.getId().getId().toString()) doDelete("/api/customer/" + savedCustomer.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
@ -182,29 +193,30 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
public void testFindCustomers() throws Exception { public void testFindCustomers() throws Exception {
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
List<Customer> customers = new ArrayList<>(); List<ListenableFuture<Customer>> futures = new ArrayList<>(135);
for (int i = 0; i < 135; i++) { for (int i = 0; i < 135; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
customer.setTitle("Customer" + i); customer.setTitle("Customer" + i);
customers.add(doPost("/api/customer", customer, Customer.class)); futures.add(executor.submit(() ->
doPost("/api/customer", customer, Customer.class)));
} }
List<Customer> customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Customer> loadedCustomers = new ArrayList<>(); List<Customer> loadedCustomers = new ArrayList<>(135);
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Customer> pageData = null; PageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink);
loadedCustomers.addAll(pageData.getData()); loadedCustomers.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customers, idComparator); assertThat(customers).containsExactlyInAnyOrderElementsOf(loadedCustomers);
Collections.sort(loadedCustomers, idComparator);
Assert.assertEquals(customers, loadedCustomers); deleteEntitiesAsync("/api/customer/", loadedCustomers, executor).get(TIMEOUT, TimeUnit.SECONDS);
} }
@Test @Test
@ -212,7 +224,7 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
TenantId tenantId = savedTenant.getId(); TenantId tenantId = savedTenant.getId();
String title1 = "Customer title 1"; String title1 = "Customer title 1";
List<Customer> customersTitle1 = new ArrayList<>(); List<ListenableFuture<Customer>> futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
@ -220,10 +232,13 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
String title = title1 + suffix; String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle1.add(doPost("/api/customer", customer, Customer.class)); futures.add(executor.submit(() ->
doPost("/api/customer", customer, Customer.class)));
} }
List<Customer> customersTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Customer title 2"; String title2 = "Customer title 2";
List<Customer> customersTitle2 = new ArrayList<>(); futures = new ArrayList<>(175);
for (int i = 0; i < 175; i++) { for (int i = 0; i < 175; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
@ -231,57 +246,48 @@ public abstract class BaseCustomerControllerTest extends AbstractControllerTest
String title = title2 + suffix; String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle2.add(doPost("/api/customer", customer, Customer.class)); futures.add(executor.submit(() ->
doPost("/api/customer", customer, Customer.class)));
} }
List<Customer> customersTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Customer> loadedCustomersTitle1 = new ArrayList<>(); List<Customer> loadedCustomersTitle1 = new ArrayList<>();
PageLink pageLink = new PageLink(15, 0, title1); PageLink pageLink = new PageLink(15, 0, title1);
PageData<Customer> pageData = null; PageData<Customer> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink);
loadedCustomersTitle1.addAll(pageData.getData()); loadedCustomersTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle1, idComparator); assertThat(customersTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle1);
Collections.sort(loadedCustomersTitle1, idComparator);
Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
List<Customer> loadedCustomersTitle2 = new ArrayList<>(); List<Customer> loadedCustomersTitle2 = new ArrayList<>();
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink);
loadedCustomersTitle2.addAll(pageData.getData()); loadedCustomersTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle2, idComparator); assertThat(customersTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle2);
Collections.sort(loadedCustomersTitle2, idComparator);
Assert.assertEquals(customersTitle2, loadedCustomersTitle2); deleteEntitiesAsync("/api/customer/", loadedCustomersTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS);
for (Customer customer : loadedCustomersTitle1) {
doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title1); pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Customer customer : loadedCustomersTitle2) { deleteEntitiesAsync("/api/customer/", loadedCustomersTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/customer/" + customer.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/customers?", new TypeReference<PageData<Customer>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/customers?", PAGE_DATA_CUSTOMER_TYPE_REFERENCE, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }

371
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -17,11 +17,17 @@ package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntitySubtype; import org.thingsboard.server.common.data.EntitySubtype;
@ -41,22 +47,32 @@ import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@Slf4j
public abstract class BaseDeviceControllerTest extends AbstractControllerTest { public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<Device>> PAGE_DATA_DEVICE_TYPE_REF = new TypeReference<>() {
};
private IdComparator<Device> idComparator = new IdComparator<>(); ListeningExecutorService executor;
List<ListenableFuture<Device>> futures;
PageData<Device> pageData;
private Tenant savedTenant; private Tenant savedTenant;
private User tenantAdmin; private User tenantAdmin;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
log.debug("beforeTest");
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
@ -76,10 +92,14 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
log.debug("afterTest...");
executor.shutdownNow();
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId())
.andExpect(status().isOk()); .andExpect(status().isOk());
log.debug("afterTest done");
} }
@Test @Test
@ -98,7 +118,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(device.getName(), savedDevice.getName()); Assert.assertEquals(device.getName(), savedDevice.getName());
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(deviceCredentials); Assert.assertNotNull(deviceCredentials);
Assert.assertNotNull(deviceCredentials.getId()); Assert.assertNotNull(deviceCredentials.getId());
@ -110,7 +130,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
savedDevice.setName("My new device"); savedDevice.setName("My new device");
doPost("/api/device", savedDevice, Device.class); doPost("/api/device", savedDevice, Device.class);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(foundDevice.getName(), savedDevice.getName()); Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
} }
@ -145,7 +165,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setName("My device"); device.setName("My device");
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertNotNull(foundDevice); Assert.assertNotNull(foundDevice);
Assert.assertEquals(savedDevice, foundDevice); Assert.assertEquals(savedDevice, foundDevice);
} }
@ -180,6 +200,8 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals("typeA", deviceTypes.get(0).getType()); Assert.assertEquals("typeA", deviceTypes.get(0).getType());
Assert.assertEquals("typeB", deviceTypes.get(1).getType()); Assert.assertEquals("typeB", deviceTypes.get(1).getType());
Assert.assertEquals("typeC", deviceTypes.get(2).getType()); Assert.assertEquals("typeC", deviceTypes.get(2).getType());
deleteEntitiesAsync("/api/device/", devices, executor).get(TIMEOUT, TimeUnit.SECONDS);
} }
@Test @Test
@ -189,10 +211,10 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doDelete("/api/device/" + savedDevice.getId().getId().toString()) doDelete("/api/device/" + savedDevice.getId().getId())
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/device/" + savedDevice.getId().getId().toString()) doGet("/api/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@ -224,18 +246,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
customer.setTitle("My customer"); customer.setTitle("My customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId().toString() Device assignedDevice = doPost("/api/customer/" + savedCustomer.getId().getId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId()); Assert.assertEquals(savedCustomer.getId(), assignedDevice.getCustomerId());
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId()); Assert.assertEquals(savedCustomer.getId(), foundDevice.getCustomerId());
Device unassignedDevice = Device unassignedDevice =
doDelete("/api/customer/device/" + savedDevice.getId().getId().toString(), Device.class); doDelete("/api/customer/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId()); Assert.assertEquals(ModelConstants.NULL_UUID, unassignedDevice.getCustomerId().getId());
foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class); foundDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId()); Assert.assertEquals(ModelConstants.NULL_UUID, foundDevice.getCustomerId().getId());
} }
@ -246,7 +268,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + Uuids.timeBased().toString() doPost("/api/customer/" + Uuids.timeBased().toString()
+ "/device/" + savedDevice.getId().getId().toString()) + "/device/" + savedDevice.getId().getId())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@ -279,13 +301,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString() doPost("/api/customer/" + savedCustomer.getId().getId()
+ "/device/" + savedDevice.getId().getId().toString()) + "/device/" + savedDevice.getId().getId())
.andExpect(status().isForbidden()); .andExpect(status().isForbidden());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedTenant2.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant2.getId().getId())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@ -296,7 +318,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
} }
@ -307,7 +329,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId("access_token"); deviceCredentials.setCredentialsId("access_token");
@ -315,7 +337,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
.andExpect(status().isOk()); .andExpect(status().isOk());
DeviceCredentials foundDeviceCredentials = DeviceCredentials foundDeviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(deviceCredentials, foundDeviceCredentials); Assert.assertEquals(deviceCredentials, foundDeviceCredentials);
} }
@ -334,7 +356,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsType(null); deviceCredentials.setCredentialsType(null);
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@ -348,7 +370,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setCredentialsId(null); deviceCredentials.setCredentialsId(null);
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
@ -362,7 +384,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(Uuids.timeBased())); DeviceCredentials newDeviceCredentials = new DeviceCredentials(new DeviceCredentialsId(Uuids.timeBased()));
newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime()); newDeviceCredentials.setCreatedTime(deviceCredentials.getCreatedTime());
newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId()); newDeviceCredentials.setDeviceId(deviceCredentials.getDeviceId());
@ -380,7 +402,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
deviceCredentials.setDeviceId(new DeviceId(Uuids.timeBased())); deviceCredentials.setDeviceId(new DeviceId(Uuids.timeBased()));
doPost("/api/device/credentials", deviceCredentials) doPost("/api/device/credentials", deviceCredentials)
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
@ -388,19 +410,24 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
@Test @Test
public void testFindTenantDevices() throws Exception { public void testFindTenantDevices() throws Exception {
List<Device> devices = new ArrayList<>(); log.debug("testFindTenantDevices");
futures = new ArrayList<>(178);
for (int i = 0; i < 178; i++) { for (int i = 0; i < 178; i++) {
Device device = new Device(); Device device = new Device();
device.setName("Device" + i); device.setName("Device" + i);
device.setType("default"); device.setType("default");
devices.add(doPost("/api/device", device, Device.class)); futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
} }
List<Device> loadedDevices = new ArrayList<>(); log.debug("await create devices");
List<Device> devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("start reading");
List<Device> loadedDevices = new ArrayList<>(178);
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevices.addAll(pageData.getData()); loadedDevices.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
@ -408,16 +435,18 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devices, idComparator); log.debug("asserting");
Collections.sort(loadedDevices, idComparator); assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices);
log.debug("delete devices async");
Assert.assertEquals(devices, loadedDevices); deleteEntitiesAsync("/api/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("done");
} }
@Test @Test
public void testFindTenantDevicesByName() throws Exception { public void testFindTenantDevicesByName() throws Exception {
String title1 = "Device title 1"; String title1 = "Device title 1";
List<Device> devicesTitle1 = new ArrayList<>();
futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -425,10 +454,13 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
devicesTitle1.add(doPost("/api/device", device, Device.class)); futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
} }
List<Device> devicesTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Device title 2"; String title2 = "Device title 2";
List<Device> devicesTitle2 = new ArrayList<>(); futures = new ArrayList<>(75);
for (int i = 0; i < 75; i++) { for (int i = 0; i < 75; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -436,59 +468,50 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
devicesTitle2.add(doPost("/api/device", device, Device.class)); futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
} }
List<Device> devicesTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevicesTitle1 = new ArrayList<>(); List<Device> loadedDevicesTitle1 = new ArrayList<>(143);
PageLink pageLink = new PageLink(15, 0, title1); PageLink pageLink = new PageLink(15, 0, title1);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevicesTitle1.addAll(pageData.getData()); loadedDevicesTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle1, idComparator); assertThat(devicesTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle1);
Collections.sort(loadedDevicesTitle1, idComparator);
Assert.assertEquals(devicesTitle1, loadedDevicesTitle1);
List<Device> loadedDevicesTitle2 = new ArrayList<>(); List<Device> loadedDevicesTitle2 = new ArrayList<>(75);
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevicesTitle2.addAll(pageData.getData()); loadedDevicesTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle2, idComparator); assertThat(devicesTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle2);
Collections.sort(loadedDevicesTitle2, idComparator);
Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); deleteEntitiesAsync("/api/device/", loadedDevicesTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS);
for (Device device : loadedDevicesTitle1) {
doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title1); pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesTitle2) { deleteEntitiesAsync("/api/device/", loadedDevicesTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/tenant/devices?", pageData = doGetTypedWithPageLink("/api/tenant/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -497,7 +520,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
public void testFindTenantDevicesByType() throws Exception { public void testFindTenantDevicesByType() throws Exception {
String title1 = "Device title 1"; String title1 = "Device title 1";
String type1 = "typeA"; String type1 = "typeA";
List<Device> devicesType1 = new ArrayList<>(); futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -505,11 +528,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type1); device.setType(type1);
devicesType1.add(doPost("/api/device", device, Device.class)); futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
if (i == 0) {
futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time
}
} }
List<Device> devicesType1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Device title 2"; String title2 = "Device title 2";
String type2 = "typeB"; String type2 = "typeB";
List<Device> devicesType2 = new ArrayList<>(); futures = new ArrayList<>(75);
for (int i = 0; i < 75; i++) { for (int i = 0; i < 75; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -517,61 +546,54 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type2); device.setType(type2);
devicesType2.add(doPost("/api/device", device, Device.class)); futures.add(executor.submit(() ->
doPost("/api/device", device, Device.class)));
if (i == 0) {
futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time
}
} }
List<Device> loadedDevicesType1 = new ArrayList<>(); List<Device> devicesType2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevicesType1 = new ArrayList<>(143);
PageLink pageLink = new PageLink(15); PageLink pageLink = new PageLink(15);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type1); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1);
loadedDevicesType1.addAll(pageData.getData()); loadedDevicesType1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesType1, idComparator); assertThat(devicesType1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesType1);
Collections.sort(loadedDevicesType1, idComparator);
Assert.assertEquals(devicesType1, loadedDevicesType1); List<Device> loadedDevicesType2 = new ArrayList<>(75);
List<Device> loadedDevicesType2 = new ArrayList<>();
pageLink = new PageLink(4); pageLink = new PageLink(4);
do { do {
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type2); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2);
loadedDevicesType2.addAll(pageData.getData()); loadedDevicesType2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesType2, idComparator); assertThat(devicesType2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesType2);
Collections.sort(loadedDevicesType2, idComparator);
Assert.assertEquals(devicesType2, loadedDevicesType2);
for (Device device : loadedDevicesType1) { deleteEntitiesAsync("/api/device/", loadedDevicesType1, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4); pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type1); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesType2) { deleteEntitiesAsync("/api/device/", loadedDevicesType2, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4); pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type2); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -583,32 +605,35 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
customer = doPost("/api/customer", customer, Customer.class); customer = doPost("/api/customer", customer, Customer.class);
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
List<Device> devices = new ArrayList<>(); futures = new ArrayList<>(128);
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
Device device = new Device(); Device device = new Device();
device.setName("Device" + i); device.setName("Device" + i);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); ListenableFuture<Device> future = executor.submit(() -> doPost("/api/device", device, Device.class));
devices.add(doPost("/api/customer/" + customerId.getId().toString() futures.add(Futures.transform(future, (dev) ->
+ "/device/" + device.getId().getId().toString(), Device.class)); doPost("/api/customer/" + customerId.getId()
+ "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor()));
} }
List<Device> loadedDevices = new ArrayList<>(); List<Device> devices = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevices = new ArrayList<>(128);
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevices.addAll(pageData.getData()); loadedDevices.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devices, idComparator); assertThat(devices).containsExactlyInAnyOrderElementsOf(loadedDevices);
Collections.sort(loadedDevices, idComparator);
Assert.assertEquals(devices, loadedDevices); log.debug("delete devices async");
deleteEntitiesAsync("/api/customer/device/", loadedDevices, executor).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("done");
} }
@Test @Test
@ -619,7 +644,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
String title1 = "Device title 1"; String title1 = "Device title 1";
List<Device> devicesTitle1 = new ArrayList<>(); futures = new ArrayList<>(125);
for (int i = 0; i < 125; i++) { for (int i = 0; i < 125; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -627,12 +652,15 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); ListenableFuture<Device> future = executor.submit(() -> doPost("/api/device", device, Device.class));
devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString() futures.add(Futures.transform(future, (dev) ->
+ "/device/" + device.getId().getId().toString(), Device.class)); doPost("/api/customer/" + customerId.getId()
+ "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor()));
} }
List<Device> devicesTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Device title 2"; String title2 = "Device title 2";
List<Device> devicesTitle2 = new ArrayList<>(); futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -640,61 +668,52 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType("default"); device.setType("default");
device = doPost("/api/device", device, Device.class); ListenableFuture<Device> future = executor.submit(() -> doPost("/api/device", device, Device.class));
devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString() futures.add(Futures.transform(future, (dev) ->
+ "/device/" + device.getId().getId().toString(), Device.class)); doPost("/api/customer/" + customerId.getId()
+ "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor()));
} }
List<Device> devicesTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevicesTitle1 = new ArrayList<>(); List<Device> loadedDevicesTitle1 = new ArrayList<>(125);
PageLink pageLink = new PageLink(15, 0, title1); PageLink pageLink = new PageLink(15, 0, title1);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevicesTitle1.addAll(pageData.getData()); loadedDevicesTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle1, idComparator); assertThat(devicesTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle1);
Collections.sort(loadedDevicesTitle1, idComparator);
Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); List<Device> loadedDevicesTitle2 = new ArrayList<>(143);
List<Device> loadedDevicesTitle2 = new ArrayList<>();
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
loadedDevicesTitle2.addAll(pageData.getData()); loadedDevicesTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesTitle2, idComparator); assertThat(devicesTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesTitle2);
Collections.sort(loadedDevicesTitle2, idComparator);
Assert.assertEquals(devicesTitle2, loadedDevicesTitle2); deleteEntitiesAsync("/api/customer/device/", loadedDevicesTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS);
for (Device device : loadedDevicesTitle1) {
doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title1); pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesTitle2) { deleteEntitiesAsync("/api/customer/device/", loadedDevicesTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?",
new TypeReference<PageData<Device>>(){}, pageLink); PAGE_DATA_DEVICE_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -708,7 +727,7 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
String title1 = "Device title 1"; String title1 = "Device title 1";
String type1 = "typeC"; String type1 = "typeC";
List<Device> devicesType1 = new ArrayList<>(); futures = new ArrayList<>(125);
for (int i = 0; i < 125; i++) { for (int i = 0; i < 125; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -716,13 +735,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type1); device.setType(type1);
device = doPost("/api/device", device, Device.class); ListenableFuture<Device> future = executor.submit(() -> doPost("/api/device", device, Device.class));
devicesType1.add(doPost("/api/customer/" + customerId.getId().toString() futures.add(Futures.transform(future, (dev) ->
+ "/device/" + device.getId().getId().toString(), Device.class)); doPost("/api/customer/" + customerId.getId()
+ "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor()));
if (i == 0) {
futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time
}
} }
List<Device> devicesType1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Device title 2"; String title2 = "Device title 2";
String type2 = "typeD"; String type2 = "typeD";
List<Device> devicesType2 = new ArrayList<>(); futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Device device = new Device(); Device device = new Device();
String suffix = RandomStringUtils.randomAlphanumeric(15); String suffix = RandomStringUtils.randomAlphanumeric(15);
@ -730,63 +755,55 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name); device.setName(name);
device.setType(type2); device.setType(type2);
device = doPost("/api/device", device, Device.class); ListenableFuture<Device> future = executor.submit(() -> doPost("/api/device", device, Device.class));
devicesType2.add(doPost("/api/customer/" + customerId.getId().toString() futures.add(Futures.transform(future, (dev) ->
+ "/device/" + device.getId().getId().toString(), Device.class)); doPost("/api/customer/" + customerId.getId()
+ "/device/" + dev.getId().getId(), Device.class), MoreExecutors.directExecutor()));
if (i == 0) {
futures.get(0).get(TIMEOUT, TimeUnit.SECONDS); // wait for the device profile created first time
}
} }
List<Device> devicesType2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Device> loadedDevicesType1 = new ArrayList<>(); List<Device> loadedDevicesType1 = new ArrayList<>(125);
PageLink pageLink = new PageLink(15); PageLink pageLink = new PageLink(15);
PageData<Device> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type1); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1);
loadedDevicesType1.addAll(pageData.getData()); loadedDevicesType1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesType1, idComparator); assertThat(devicesType1).as(title1).containsExactlyInAnyOrderElementsOf(loadedDevicesType1);
Collections.sort(loadedDevicesType1, idComparator);
Assert.assertEquals(devicesType1, loadedDevicesType1); List<Device> loadedDevicesType2 = new ArrayList<>(143);
List<Device> loadedDevicesType2 = new ArrayList<>();
pageLink = new PageLink(4); pageLink = new PageLink(4);
do { do {
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type2); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2);
loadedDevicesType2.addAll(pageData.getData()); loadedDevicesType2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(devicesType2, idComparator); assertThat(devicesType2).as(title2).containsExactlyInAnyOrderElementsOf(loadedDevicesType2);
Collections.sort(loadedDevicesType2, idComparator);
Assert.assertEquals(devicesType2, loadedDevicesType2);
for (Device device : loadedDevicesType1) { deleteEntitiesAsync("/api/customer/device/", loadedDevicesType1, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4); pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type1); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type1);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Device device : loadedDevicesType2) { deleteEntitiesAsync("/api/customer/device/", loadedDevicesType2, executor).get(TIMEOUT, TimeUnit.SECONDS);
doDelete("/api/customer/device/" + device.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4); pageLink = new PageLink(4);
pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&", pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId() + "/devices?type={type}&",
new TypeReference<PageData<Device>>(){}, pageLink, type2); PAGE_DATA_DEVICE_TYPE_REF, pageLink, type2);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }
@ -828,17 +845,17 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
login("tenant2@thingsboard.org", "testPassword1"); login("tenant2@thingsboard.org", "testPassword1");
Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class); Device assignedDevice = doPost("/api/tenant/" + savedDifferentTenant.getId().getId() + "/device/" + savedDevice.getId().getId(), Device.class);
doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class, status().isNotFound()); doGet("/api/device/" + assignedDevice.getId().getId(), Device.class, status().isNotFound());
login("tenant9@thingsboard.org", "testPassword1"); login("tenant9@thingsboard.org", "testPassword1");
Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId().toString(), Device.class); Device foundDevice1 = doGet("/api/device/" + assignedDevice.getId().getId(), Device.class);
Assert.assertNotNull(foundDevice1); Assert.assertNotNull(foundDevice1);
doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound()); doGet("/api/relation?fromId=" + savedDevice.getId().getId() + "&fromType=DEVICE&relationType=Contains&toId=" + savedAnotherDevice.getId().getId() + "&toType=DEVICE", EntityRelation.class, status().isNotFound());
loginSysAdmin(); loginSysAdmin();
doDelete("/api/tenant/" + savedDifferentTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedDifferentTenant.getId().getId())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@ -852,19 +869,19 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
device.setType("default"); device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class); Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/edge/" + savedEdge.getId().getId().toString() doPost("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId(), Device.class);
PageData<Device> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)); PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
doDelete("/api/edge/" + savedEdge.getId().getId().toString() doDelete("/api/edge/" + savedEdge.getId().getId()
+ "/device/" + savedDevice.getId().getId().toString(), Device.class); + "/device/" + savedDevice.getId().getId(), Device.class);
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/devices?", pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId() + "/devices?",
new TypeReference<PageData<Device>>() {}, new PageLink(100)); PAGE_DATA_DEVICE_TYPE_REF, new PageLink(100));
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }

355
application/src/test/java/org/thingsboard/server/controller/BaseEntityViewControllerTest.java

@ -17,8 +17,13 @@ package org.thingsboard.server.controller;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient; import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.MqttMessage;
@ -28,7 +33,8 @@ import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.context.TestPropertySource; import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.EntityView;
@ -41,73 +47,71 @@ import org.thingsboard.server.common.data.objects.AttributesEntityView;
import org.thingsboard.server.common.data.objects.TelemetryEntityView; import org.thingsboard.server.common.data.objects.TelemetryEntityView;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.ModelConstants;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@TestPropertySource(properties = { @TestPropertySource(properties = {
"transport.mqtt.enabled=true", "transport.mqtt.enabled=true",
"js.evaluator=mock",
}) })
@Slf4j @Slf4j
public abstract class BaseEntityViewControllerTest extends AbstractControllerTest { public abstract class BaseEntityViewControllerTest extends AbstractControllerTest {
static final TypeReference<PageData<EntityView>> PAGE_DATA_ENTITY_VIEW_TYPE_REF = new TypeReference<>() {
};
static final TypeReference<PageData<EntityViewInfo>> PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF = new TypeReference<>() {
};
private IdComparator<EntityView> idComparator;
private Tenant savedTenant;
private User tenantAdmin;
private Device testDevice; private Device testDevice;
private TelemetryEntityView telemetry; private TelemetryEntityView telemetry;
List<ListenableFuture<ResultActions>> deleteFutures = new ArrayList<>();
ListeningExecutorService executor;
@Before @Before
public void beforeTest() throws Exception { public void beforeTest() throws Exception {
loginSysAdmin(); log.debug("beforeTest");
idComparator = new IdComparator<>(); executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
savedTenant = doPost("/api/tenant", getNewTenant("My tenant"), Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User(); loginTenantAdmin();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
Device device = new Device(); Device device = new Device();
device.setName("Test device"); device.setName("Test device 4view");
device.setType("default"); device.setType("default");
testDevice = doPost("/api/device", device, Device.class); testDevice = doPost("/api/device", device, Device.class);
telemetry = new TelemetryEntityView( telemetry = new TelemetryEntityView(
Arrays.asList("tsKey1", "tsKey2", "tsKey3"), List.of("tsKey1", "tsKey2", "tsKey3"),
new AttributesEntityView( new AttributesEntityView(
Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"), List.of("caKey1", "caKey2", "caKey3", "caKey4"),
Arrays.asList("saKey1", "saKey2", "saKey3", "saKey4"), List.of("saKey1", "saKey2", "saKey3", "saKey4"),
Arrays.asList("shKey1", "shKey2", "shKey3", "shKey4"))); List.of("shKey1", "shKey2", "shKey3", "shKey4")));
} }
@After @After
public void afterTest() throws Exception { public void afterTest() throws Exception {
loginSysAdmin(); executor.shutdownNow();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk());
} }
@Test @Test
@ -126,7 +130,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
Assert.assertNotNull(savedView); Assert.assertNotNull(savedView);
Assert.assertNotNull(savedView.getId()); Assert.assertNotNull(savedView.getId());
Assert.assertTrue(savedView.getCreatedTime() > 0); Assert.assertTrue(savedView.getCreatedTime() > 0);
assertEquals(savedTenant.getId(), savedView.getTenantId()); assertEquals(tenantId, savedView.getTenantId());
Assert.assertNotNull(savedView.getCustomerId()); Assert.assertNotNull(savedView.getCustomerId());
assertEquals(NULL_UUID, savedView.getCustomerId().getId()); assertEquals(NULL_UUID, savedView.getCustomerId().getId());
assertEquals(name, savedView.getName()); assertEquals(name, savedView.getName());
@ -233,7 +237,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
Customer customer = getNewCustomer("Different customer"); Customer customer = getNewCustomer("Different customer");
Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
login(tenantAdmin.getEmail(), "testPassword1"); login(TENANT_ADMIN_EMAIL, TENANT_ADMIN_PASSWORD);
EntityView savedView = getNewSavedEntityView("Test entity view"); EntityView savedView = getNewSavedEntityView("Test entity view");
@ -252,21 +256,18 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
CustomerId customerId = customer.getId(); CustomerId customerId = customer.getId();
String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?"; String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViewInfos?";
List<EntityViewInfo> views = new ArrayList<>(); List<ListenableFuture<EntityViewInfo>> viewFutures = new ArrayList<>(128);
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
views.add( String entityName = "Test entity view " + i;
viewFutures.add(executor.submit(() ->
new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/" new EntityViewInfo(doPost("/api/customer/" + customerId.getId().toString() + "/entityView/"
+ getNewSavedEntityView("Test entity view " + i).getId().getId().toString(), EntityView.class), + getNewSavedEntityView(entityName).getId().getId().toString(), EntityView.class),
customer.getTitle(), customer.isPublic()) customer.getTitle(), customer.isPublic())));
);
} }
List<EntityViewInfo> entityViewInfos = Futures.allAsList(viewFutures).get(TIMEOUT, SECONDS);
List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), urlTemplate); List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), urlTemplate);
Collections.sort(views, idComparator); assertThat(entityViewInfos).containsExactlyInAnyOrderElementsOf(loadedViews);
Collections.sort(loadedViews, idComparator);
assertEquals(views, loadedViews);
} }
@Test @Test
@ -275,35 +276,37 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?"; String urlTemplate = "/api/customer/" + customerId.getId().toString() + "/entityViews?";
String name1 = "Entity view name1"; String name1 = "Entity view name1";
List<EntityView> namesOfView1 = fillListOf(125, name1, "/api/customer/" + customerId.getId().toString() List<EntityView> namesOfView1 = Futures.allAsList(fillListByTemplate(125, name1, "/api/customer/" + customerId.getId().toString()
+ "/entityView/"); + "/entityView/")).get(TIMEOUT, SECONDS);
List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate); List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), urlTemplate);
Collections.sort(namesOfView1, idComparator); assertThat(namesOfView1).as(name1).containsExactlyInAnyOrderElementsOf(loadedNamesOfView1);
Collections.sort(loadedNamesOfView1, idComparator);
assertEquals(namesOfView1, loadedNamesOfView1);
String name2 = "Entity view name2"; String name2 = "Entity view name2";
List<EntityView> NamesOfView2 = fillListOf(143, name2, "/api/customer/" + customerId.getId().toString() List<EntityView> namesOfView2 = Futures.allAsList(fillListByTemplate(143, name2, "/api/customer/" + customerId.getId().toString()
+ "/entityView/"); + "/entityView/")).get(TIMEOUT, SECONDS);
List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate); List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), urlTemplate);
Collections.sort(NamesOfView2, idComparator); assertThat(namesOfView2).as(name2).containsExactlyInAnyOrderElementsOf(loadedNamesOfView2);
Collections.sort(loadedNamesOfView2, idComparator);
assertEquals(NamesOfView2, loadedNamesOfView2);
deleteFutures.clear();
for (EntityView view : loadedNamesOfView1) { for (EntityView view : loadedNamesOfView1) {
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); deleteFutures.add(executor.submit(() ->
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk())));
} }
PageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate, Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS);
new TypeReference<PageData<EntityView>>() {
}, new PageLink(4, 0, name1)); PageData<EntityView> pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF,
new PageLink(4, 0, name1));
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size()); assertEquals(0, pageData.getData().size());
deleteFutures.clear();
for (EntityView view : loadedNamesOfView2) { for (EntityView view : loadedNamesOfView2) {
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); deleteFutures.add(executor.submit(() ->
doDelete("/api/customer/entityView/" + view.getId().getId().toString()).andExpect(status().isOk())));
} }
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityView>>() { Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS);
},
pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF,
new PageLink(4, 0, name2)); new PageLink(4, 0, name2));
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size()); assertEquals(0, pageData.getData().size());
@ -311,49 +314,51 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testGetTenantEntityViews() throws Exception { public void testGetTenantEntityViews() throws Exception {
List<ListenableFuture<EntityViewInfo>> entityViewInfoFutures = new ArrayList<>(178);
List<EntityViewInfo> views = new ArrayList<>();
for (int i = 0; i < 178; i++) { for (int i = 0; i < 178; i++) {
views.add(new EntityViewInfo(getNewSavedEntityView("Test entity view" + i), null, false)); ListenableFuture<EntityView> entityViewFuture = getNewSavedEntityViewAsync("Test entity view" + i);
entityViewInfoFutures.add(Futures.transform(entityViewFuture,
view -> new EntityViewInfo(view, null, false),
MoreExecutors.directExecutor()));
} }
List<EntityViewInfo> entityViewInfos = Futures.allAsList(entityViewInfoFutures).get(TIMEOUT, SECONDS);
List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), "/api/tenant/entityViewInfos?"); List<EntityViewInfo> loadedViews = loadListOfInfo(new PageLink(23), "/api/tenant/entityViewInfos?");
assertThat(entityViewInfos).containsExactlyInAnyOrderElementsOf(loadedViews);
Collections.sort(views, idComparator);
Collections.sort(loadedViews, idComparator);
assertEquals(views, loadedViews);
} }
@Test @Test
public void testGetTenantEntityViewsByName() throws Exception { public void testGetTenantEntityViewsByName() throws Exception {
String name1 = "Entity view name1"; String name1 = "Entity view name1";
List<EntityView> namesOfView1 = fillListOf(143, name1); List<EntityView> namesOfView1 = Futures.allAsList(fillListOf(17, name1)).get(TIMEOUT, SECONDS);
List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(15, 0, name1), "/api/tenant/entityViews?"); List<EntityView> loadedNamesOfView1 = loadListOf(new PageLink(5, 0, name1), "/api/tenant/entityViews?");
Collections.sort(namesOfView1, idComparator); assertThat(namesOfView1).as(name1).containsExactlyInAnyOrderElementsOf(loadedNamesOfView1);
Collections.sort(loadedNamesOfView1, idComparator);
assertEquals(namesOfView1, loadedNamesOfView1);
String name2 = "Entity view name2"; String name2 = "Entity view name2";
List<EntityView> NamesOfView2 = fillListOf(75, name2); List<EntityView> namesOfView2 = Futures.allAsList(fillListOf(15, name2)).get(TIMEOUT, SECONDS);
;
List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), "/api/tenant/entityViews?"); List<EntityView> loadedNamesOfView2 = loadListOf(new PageLink(4, 0, name2), "/api/tenant/entityViews?");
Collections.sort(NamesOfView2, idComparator); assertThat(namesOfView2).as(name2).containsExactlyInAnyOrderElementsOf(loadedNamesOfView2);
Collections.sort(loadedNamesOfView2, idComparator);
assertEquals(NamesOfView2, loadedNamesOfView2);
deleteFutures.clear();
for (EntityView view : loadedNamesOfView1) { for (EntityView view : loadedNamesOfView1) {
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); deleteFutures.add(executor.submit(() ->
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk())));
} }
PageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS);
new TypeReference<PageData<EntityView>>() {
}, new PageLink(4, 0, name1)); PageData<EntityView> pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", PAGE_DATA_ENTITY_VIEW_TYPE_REF,
new PageLink(4, 0, name1));
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size()); assertEquals(0, pageData.getData().size());
deleteFutures.clear();
for (EntityView view : loadedNamesOfView2) { for (EntityView view : loadedNamesOfView2) {
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk()); deleteFutures.add(executor.submit(() ->
doDelete("/api/entityView/" + view.getId().getId().toString()).andExpect(status().isOk())));
} }
pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", new TypeReference<PageData<EntityView>>() { Futures.allAsList(deleteFutures).get(TIMEOUT, SECONDS);
},
pageData = doGetTypedWithPageLink("/api/tenant/entityViews?", PAGE_DATA_ENTITY_VIEW_TYPE_REF,
new PageLink(4, 0, name2)); new PageLink(4, 0, name2));
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
assertEquals(0, pageData.getData().size()); assertEquals(0, pageData.getData().size());
@ -361,20 +366,21 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testTheCopyOfAttrsIntoTSForTheView() throws Exception { public void testTheCopyOfAttrsIntoTSForTheView() throws Exception {
Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4");
Set<String> actualAttributesSet = Set<String> actualAttributesSet =
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}"); getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet);
log.debug("got correct actualAttributesSet, saving new entity view...");
Set<String> expectedActualAttributesSet =
new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
EntityView savedView = getNewSavedEntityView("Test entity view"); EntityView savedView = getNewSavedEntityView("Test entity view");
Thread.sleep(1000); log.debug("fetching entity view telemetry...");
List<Map<String, Object>> values = await("telemetry/ENTITY_VIEW")
List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + .atMost(TIMEOUT, SECONDS)
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); .until(() -> doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {
}),
x -> x.size() >= expectedActualAttributesSet.size());
log.debug("asserting...");
assertEquals("value1", getValue(values, "caKey1")); assertEquals("value1", getValue(values, "caKey1"));
assertEquals(true, getValue(values, "caKey2")); assertEquals(true, getValue(values, "caKey2"));
assertEquals(42.0, getValue(values, "caKey3")); assertEquals(42.0, getValue(values, "caKey3"));
@ -383,18 +389,17 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
@Test @Test
public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception { public void testTheCopyOfAttrsOutOfTSForTheView() throws Exception {
Set<String> expectedActualAttributesSet = Set.of("caKey1", "caKey2", "caKey3", "caKey4");
Set<String> actualAttributesSet = Set<String> actualAttributesSet =
getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}"); getAttributesByKeys("{\"caKey1\":\"value1\", \"caKey2\":true, \"caKey3\":42.0, \"caKey4\":73}", expectedActualAttributesSet);
Set<String> expectedActualAttributesSet = new HashSet<>(Arrays.asList("caKey1", "caKey2", "caKey3", "caKey4"));
assertTrue(actualAttributesSet.containsAll(expectedActualAttributesSet));
List<Map<String, Object>> valueTelemetryOfDevices = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() + List<Map<String, Object>> valueTelemetryOfDevices = doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + testDevice.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {
});
EntityView view = new EntityView(); EntityView view = new EntityView();
view.setEntityId(testDevice.getId()); view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId()); view.setTenantId(tenantId);
view.setName("Test entity view"); view.setName("Test entity view");
view.setType("default"); view.setType("default");
view.setKeys(telemetry); view.setKeys(telemetry);
@ -402,28 +407,49 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10); view.setEndTimeMs((long) getValue(valueTelemetryOfDevices, "lastActivityTime") / 10);
EntityView savedView = doPost("/api/entityView", view, EntityView.class); EntityView savedView = doPost("/api/entityView", view, EntityView.class);
Thread.sleep(1000);
List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() + List<Map<String, Object>> values = doGetAsyncTyped("/api/plugins/telemetry/ENTITY_VIEW/" + savedView.getId().getId().toString() +
"/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {}); "/values/attributes?keys=" + String.join(",", actualAttributesSet), new TypeReference<>() {
});
assertEquals(0, values.size()); assertEquals(0, values.size());
} }
@Test @Test
public void testGetTelemetryWhenEntityViewTimeRangeInsideTimestampRange() throws Exception { public void testGetTelemetryWhenEntityViewTimeRangeInsideTimestampRange() throws Exception {
uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}"); DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName());
Thread.sleep(1000); List<String> tsKeys = List.of("tsKey1", "tsKey2", "tsKey3");
DeviceCredentials deviceCredentials = doGet("/api/device/" + testDevice.getId().getId() + "/credentials", DeviceCredentials.class);
assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
long now = System.currentTimeMillis();
getWsClient().subscribeTsUpdate(tsKeys, now, TimeUnit.HOURS.toMillis(1), dtf);
getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value1\", \"tsKey2\":true, \"tsKey3\":40.0}", accessToken);
getWsClient().waitForUpdate();
long startTimeMs = System.currentTimeMillis(); long startTimeMs = System.currentTimeMillis();
uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}");
Thread.sleep(1000); getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}"); uploadTelemetry("{\"tsKey1\":\"value2\", \"tsKey2\":false, \"tsKey3\":80.0}", accessToken);
getWsClient().waitForUpdate();
Thread.sleep(3);
getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value3\", \"tsKey2\":false, \"tsKey3\":120.0}", accessToken);
getWsClient().waitForUpdate();
long endTimeMs = System.currentTimeMillis(); long endTimeMs = System.currentTimeMillis();
uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}"); getWsClient().registerWaitForUpdate();
uploadTelemetry("{\"tsKey1\":\"value4\", \"tsKey2\":true, \"tsKey3\":160.0}", accessToken);
getWsClient().waitForUpdate();
String deviceId = testDevice.getId().getId().toString(); String deviceId = testDevice.getId().getId().toString();
Set<String> keys = getTelemetryKeys("DEVICE", deviceId); Set<String> keys = getTelemetryKeys("DEVICE", deviceId);
Thread.sleep(1000);
EntityView view = createEntityView("Test entity view", startTimeMs, endTimeMs); EntityView view = createEntityView("Test entity view", startTimeMs, endTimeMs);
EntityView savedView = doPost("/api/entityView", view, EntityView.class); EntityView savedView = doPost("/api/entityView", view, EntityView.class);
@ -440,14 +466,8 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
Assert.assertEquals(1, actualValues.get("tsKey3").size()); Assert.assertEquals(1, actualValues.get("tsKey3").size());
} }
private void uploadTelemetry(String strKvs) throws Exception { private void uploadTelemetry(String strKvs, String accessToken) throws Exception {
String viewDeviceId = testDevice.getId().getId().toString(); String viewDeviceId = testDevice.getId().getId().toString();
DeviceCredentials deviceCredentials =
doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String clientId = MqttAsyncClient.generateClientId(); String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
@ -455,35 +475,45 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken); options.setUserName(accessToken);
client.connect(options); client.connect(options);
awaitConnected(client, TimeUnit.SECONDS.toMillis(30)); awaitConnected(client, SECONDS.toMillis(30));
MqttMessage message = new MqttMessage(); MqttMessage message = new MqttMessage();
message.setPayload(strKvs.getBytes()); message.setPayload(strKvs.getBytes());
client.publish("v1/devices/me/telemetry", message); IMqttDeliveryToken token = client.publish("v1/devices/me/telemetry", message);
Thread.sleep(1000); await("mqtt ack").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS).until(() -> token.getMessage() == null);
client.disconnect(); client.disconnect();
} }
private void awaitConnected(MqttAsyncClient client, long ms) throws InterruptedException { private void awaitConnected(MqttAsyncClient client, long ms) throws InterruptedException {
long start = System.currentTimeMillis(); await("awaitConnected").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS)
while (!client.isConnected()) { .until(client::isConnected);
Thread.sleep(100);
if (start + ms < System.currentTimeMillis()) {
throw new RuntimeException("Client is not connected!");
}
}
} }
private Set<String> getTelemetryKeys(String type, String id) throws Exception { private Set<String> getTelemetryKeys(String type, String id) throws Exception {
return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", new TypeReference<>() {})); return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/timeseries", new TypeReference<>() {
}));
}
private Set<String> getAttributeKeys(String type, String id) throws Exception {
return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + "/keys/attributes", new TypeReference<>() {
}));
} }
private Map<String, List<Map<String, String>>> getTelemetryValues(String type, String id, Set<String> keys, Long startTs, Long endTs) throws Exception { private Map<String, List<Map<String, String>>> getTelemetryValues(String type, String id, Set<String> keys, Long startTs, Long endTs) throws Exception {
return doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id + return doGetAsyncTyped("/api/plugins/telemetry/" + type + "/" + id +
"/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, new TypeReference<>() {}); "/values/timeseries?keys=" + String.join(",", keys) + "&startTs=" + startTs + "&endTs=" + endTs, new TypeReference<>() {
});
} }
private Set<String> getAttributesByKeys(String stringKV) throws Exception { private Set<String> getAttributesByKeys(String stringKV, Set<String> expectedKeySet) throws Exception {
DeviceTypeFilter dtf = new DeviceTypeFilter(testDevice.getType(), testDevice.getName());
List<EntityKey> keysToSubscribe = expectedKeySet.stream()
.map(key -> new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, key))
.collect(Collectors.toList());
getWsClient().subscribeLatestUpdate(keysToSubscribe, dtf);
String viewDeviceId = testDevice.getId().getId().toString(); String viewDeviceId = testDevice.getId().getId().toString();
log.debug("deviceid {}", viewDeviceId);
DeviceCredentials deviceCredentials = DeviceCredentials deviceCredentials =
doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class); doGet("/api/device/" + viewDeviceId + "/credentials", DeviceCredentials.class);
assertEquals(testDevice.getId(), deviceCredentials.getDeviceId()); assertEquals(testDevice.getId(), deviceCredentials.getDeviceId());
@ -491,20 +521,24 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
String accessToken = deviceCredentials.getCredentialsId(); String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken); assertNotNull(accessToken);
log.debug("creating mqtt client...");
String clientId = MqttAsyncClient.generateClientId(); String clientId = MqttAsyncClient.generateClientId();
MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence()); MqttAsyncClient client = new MqttAsyncClient("tcp://localhost:1883", clientId, new MemoryPersistence());
MqttConnectOptions options = new MqttConnectOptions(); MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(accessToken); options.setUserName(accessToken);
client.connect(options); client.connect(options);
awaitConnected(client, TimeUnit.SECONDS.toMillis(30)); awaitConnected(client, SECONDS.toMillis(30));
log.debug("mqtt connected...");
MqttMessage message = new MqttMessage(); MqttMessage message = new MqttMessage();
message.setPayload((stringKV).getBytes()); message.setPayload((stringKV).getBytes());
client.publish("v1/devices/me/attributes", message); getWsClient().registerWaitForUpdate();
Thread.sleep(1000); IMqttDeliveryToken token = client.publish("v1/devices/me/attributes", message);
client.disconnect(); log.debug("publish token.message {}", token.getMessage());
return new HashSet<>(doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + viewDeviceId + "/keys/attributes", new TypeReference<>() {})); await("mqtt ack").pollInterval(5, MILLISECONDS).atMost(TIMEOUT, SECONDS).until(() -> token.getMessage() == null);
log.debug("token.message delivered {}", token.getMessage());
assertThat(getWsClient().waitForUpdate()).as("ws update received").isNotBlank();
return getAttributeKeys("DEVICE", viewDeviceId);
} }
private Object getValue(List<Map<String, Object>> values, String stringValue) { private Object getValue(List<Map<String, Object>> values, String stringValue) {
@ -514,15 +548,19 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
.findFirst().get().get("value"); .findFirst().get().get("value");
} }
private EntityView getNewSavedEntityView(String name) throws Exception { private EntityView getNewSavedEntityView(String name) {
EntityView view = createEntityView(name, 0, 0); EntityView view = createEntityView(name, 0, 0);
return doPost("/api/entityView", view, EntityView.class); return doPost("/api/entityView", view, EntityView.class);
} }
private ListenableFuture<EntityView> getNewSavedEntityViewAsync(String name) {
return executor.submit(() -> getNewSavedEntityView(name));
}
private EntityView createEntityView(String name, long startTimeMs, long endTimeMs) { private EntityView createEntityView(String name, long startTimeMs, long endTimeMs) {
EntityView view = new EntityView(); EntityView view = new EntityView();
view.setEntityId(testDevice.getId()); view.setEntityId(testDevice.getId());
view.setTenantId(savedTenant.getId()); view.setTenantId(tenantId);
view.setName(name); view.setName(name);
view.setType("default"); view.setType("default");
view.setKeys(telemetry); view.setKeys(telemetry);
@ -543,33 +581,41 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
return tenant; return tenant;
} }
private List<EntityView> fillListOf(int limit, String partOfName, String urlTemplate) throws Exception { private List<ListenableFuture<EntityView>> fillListByTemplate(int limit, String partOfName, String urlTemplate) {
List<EntityView> views = new ArrayList<>(); List<ListenableFuture<EntityView>> futures = new ArrayList<>(limit);
for (EntityView view : fillListOf(limit, partOfName)) { for (ListenableFuture<EntityView> viewFuture : fillListOf(limit, partOfName)) {
views.add(doPost(urlTemplate + view.getId().getId().toString(), EntityView.class)); futures.add(Futures.transform(viewFuture, view ->
doPost(urlTemplate + view.getId().getId().toString(), EntityView.class),
MoreExecutors.directExecutor()));
} }
return views; return futures;
} }
private List<EntityView> fillListOf(int limit, String partOfName) throws Exception { private List<ListenableFuture<EntityView>> fillListOf(int limit, String partOfName) {
List<EntityView> viewNames = new ArrayList<>(); List<ListenableFuture<EntityView>> viewNameFutures = new ArrayList<>(limit);
for (int i = 0; i < limit; i++) { for (int i = 0; i < limit; i++) {
String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15); boolean even = i % 2 == 0;
fullName = i % 2 == 0 ? fullName.toLowerCase() : fullName.toUpperCase(); ListenableFuture<CustomerId> customerFuture = executor.submit(() -> {
EntityView view = getNewSavedEntityView(fullName); Customer customer = getNewCustomer("Test customer " + Math.random());
Customer customer = getNewCustomer("Test customer " + String.valueOf(Math.random())); return doPost("/api/customer", customer, Customer.class).getId();
view.setCustomerId(doPost("/api/customer", customer, Customer.class).getId()); });
viewNames.add(doPost("/api/entityView", view, EntityView.class));
viewNameFutures.add(Futures.transform(customerFuture, customerId -> {
String fullName = partOfName + ' ' + RandomStringUtils.randomAlphanumeric(15);
fullName = even ? fullName.toLowerCase() : fullName.toUpperCase();
EntityView view = getNewSavedEntityView(fullName);
view.setCustomerId(customerId);
return doPost("/api/entityView", view, EntityView.class);
}, MoreExecutors.directExecutor()));
} }
return viewNames; return viewNameFutures;
} }
private List<EntityView> loadListOf(PageLink pageLink, String urlTemplate) throws Exception { private List<EntityView> loadListOf(PageLink pageLink, String urlTemplate) throws Exception {
List<EntityView> loadedItems = new ArrayList<>(); List<EntityView> loadedItems = new ArrayList<>();
PageData<EntityView> pageData; PageData<EntityView> pageData;
do { do {
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityView>>() { pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_TYPE_REF, pageLink);
}, pageLink);
loadedItems.addAll(pageData.getData()); loadedItems.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
@ -583,8 +629,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
List<EntityViewInfo> loadedItems = new ArrayList<>(); List<EntityViewInfo> loadedItems = new ArrayList<>();
PageData<EntityViewInfo> pageData; PageData<EntityViewInfo> pageData;
do { do {
pageData = doGetTypedWithPageLink(urlTemplate, new TypeReference<PageData<EntityViewInfo>>() { pageData = doGetTypedWithPageLink(urlTemplate, PAGE_DATA_ENTITY_VIEW_INFO_TYPE_REF, pageLink);
}, pageLink);
loadedItems.addAll(pageData.getData()); loadedItems.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
@ -608,7 +653,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
PageData<EntityView> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?", PageData<EntityView> pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?",
new TypeReference<PageData<EntityView>>() {}, new PageLink(100)); PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(100));
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
@ -616,7 +661,7 @@ public abstract class BaseEntityViewControllerTest extends AbstractControllerTes
+ "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class); + "/entityView/" + savedEntityView.getId().getId().toString(), EntityView.class);
pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?", pageData = doGetTypedWithPageLink("/api/edge/" + savedEdge.getId().getId().toString() + "/entityViews?",
new TypeReference<PageData<EntityView>>() {}, new PageLink(100)); PAGE_DATA_ENTITY_VIEW_TYPE_REF, new PageLink(100));
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
} }

244
application/src/test/java/org/thingsboard/server/controller/BaseTenantControllerTest.java

@ -16,24 +16,55 @@
package org.thingsboard.server.controller; package org.thingsboard.server.controller;
import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.web.servlet.ResultActions;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.TenantInfo; import org.thingsboard.server.common.data.TenantInfo;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@TestPropertySource(properties = {
"js.evaluator=mock",
})
@Slf4j
public abstract class BaseTenantControllerTest extends AbstractControllerTest { public abstract class BaseTenantControllerTest extends AbstractControllerTest {
private IdComparator<Tenant> idComparator = new IdComparator<>(); static final TypeReference<PageData<Tenant>> PAGE_DATA_TENANT_TYPE_REF = new TypeReference<>() {
};
static final TypeReference<PageData<TenantInfo>> PAGE_DATA_TENANT_INFO_TYPE_REF = new TypeReference<>() {
};
ListeningExecutorService executor;
@Before
public void setUp() throws Exception {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
}
@After
public void tearDown() throws Exception {
executor.shutdownNow();
}
@Test @Test
public void testSaveTenant() throws Exception { public void testSaveTenant() throws Exception {
@ -47,10 +78,10 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle()); Assert.assertEquals(tenant.getTitle(), savedTenant.getTitle());
savedTenant.setTitle("My new tenant"); savedTenant.setTitle("My new tenant");
doPost("/api/tenant", savedTenant, Tenant.class); doPost("/api/tenant", savedTenant, Tenant.class);
Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle()); Assert.assertEquals(foundTenant.getTitle(), savedTenant.getTitle());
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
@ -60,18 +91,18 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
tenant.setTitle(RandomStringUtils.randomAlphanumeric(300)); tenant.setTitle(RandomStringUtils.randomAlphanumeric(300));
doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255"))); doPost("/api/tenant", tenant).andExpect(statusReason(containsString("length of title must be equal or less than 255")));
} }
@Test @Test
public void testFindTenantById() throws Exception { public void testFindTenantById() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Tenant foundTenant = doGet("/api/tenant/"+savedTenant.getId().getId().toString(), Tenant.class); Tenant foundTenant = doGet("/api/tenant/" + savedTenant.getId().getId().toString(), Tenant.class);
Assert.assertNotNull(foundTenant); Assert.assertNotNull(foundTenant);
Assert.assertEquals(savedTenant, foundTenant); Assert.assertEquals(savedTenant, foundTenant);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
@ -80,22 +111,22 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
TenantInfo foundTenant = doGet("/api/tenant/info/"+savedTenant.getId().getId().toString(), TenantInfo.class); TenantInfo foundTenant = doGet("/api/tenant/info/" + savedTenant.getId().getId().toString(), TenantInfo.class);
Assert.assertNotNull(foundTenant); Assert.assertNotNull(foundTenant);
Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant); Assert.assertEquals(new TenantInfo(savedTenant, "Default"), foundTenant);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testSaveTenantWithEmptyTitle() throws Exception { public void testSaveTenantWithEmptyTitle() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
doPost("/api/tenant", tenant) doPost("/api/tenant", tenant)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Tenant title should be specified"))); .andExpect(statusReason(containsString("Tenant title should be specified")));
} }
@Test @Test
public void testSaveTenantWithInvalidEmail() throws Exception { public void testSaveTenantWithInvalidEmail() throws Exception {
loginSysAdmin(); loginSysAdmin();
@ -103,140 +134,147 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
tenant.setEmail("invalid@mail"); tenant.setEmail("invalid@mail");
doPost("/api/tenant", tenant) doPost("/api/tenant", tenant)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Invalid email address format"))); .andExpect(statusReason(containsString("Invalid email address format")));
} }
@Test @Test
public void testDeleteTenant() throws Exception { public void testDeleteTenant() throws Exception {
loginSysAdmin(); loginSysAdmin();
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
doDelete("/api/tenant/"+savedTenant.getId().getId().toString()) doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
doGet("/api/tenant/"+savedTenant.getId().getId().toString()) doGet("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isNotFound()); .andExpect(status().isNotFound());
} }
@Test @Test
public void testFindTenants() throws Exception { public void testFindTenants() throws Exception {
loginSysAdmin(); loginSysAdmin();
List<Tenant> tenants = new ArrayList<>(); List<Tenant> tenants = new ArrayList<>();
PageLink pageLink = new PageLink(17); PageLink pageLink = new PageLink(17);
PageData<Tenant> pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); PageData<Tenant> pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
tenants.addAll(pageData.getData()); tenants.addAll(pageData.getData());
for (int i=0;i<56;i++) { List<ListenableFuture<Tenant>> createFutures = new ArrayList<>(56);
for (int i = 0; i < 56; i++) {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Tenant"+i); tenant.setTitle("Tenant" + i);
tenants.add(doPost("/api/tenant", tenant, Tenant.class)); createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
} }
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
List<Tenant> loadedTenants = new ArrayList<>(); List<Tenant> loadedTenants = new ArrayList<>();
pageLink = new PageLink(17); pageLink = new PageLink(17);
do { do {
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
loadedTenants.addAll(pageData.getData()); loadedTenants.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenants, idComparator); assertThat(tenants).containsExactlyInAnyOrderElementsOf(loadedTenants);
Collections.sort(loadedTenants, idComparator);
deleteEntitiesAsync("/api/tenant/", loadedTenants.stream()
Assert.assertEquals(tenants, loadedTenants); .filter((t) -> !TEST_TENANT_NAME.equals(t.getTitle()))
.collect(Collectors.toList()), executor).get(TIMEOUT, TimeUnit.SECONDS);
for (Tenant tenant : loadedTenants) {
if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17); pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
} }
@Test @Test
public void testFindTenantsByTitle() throws Exception { public void testFindTenantsByTitle() throws Exception {
log.debug("login sys admin");
loginSysAdmin(); loginSysAdmin();
log.debug("test started");
String title1 = "Tenant title 1"; String title1 = "Tenant title 1";
List<Tenant> tenantsTitle1 = new ArrayList<>(); List<ListenableFuture<Tenant>> createFutures = new ArrayList<>(134);
for (int i=0;i<134;i++) { for (int i = 0; i < 134; i++) {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title1+suffix; String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title); tenant.setTitle(title);
tenantsTitle1.add(doPost("/api/tenant", tenant, Tenant.class)); createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
} }
List<Tenant> tenantsTitle1 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("saved '{}', qty {}", title1, tenantsTitle1.size());
String title2 = "Tenant title 2"; String title2 = "Tenant title 2";
List<Tenant> tenantsTitle2 = new ArrayList<>(); createFutures = new ArrayList<>(127);
for (int i=0;i<127;i++) { for (int i = 0; i < 127; i++) {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title2+suffix; String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
tenant.setTitle(title); tenant.setTitle(title);
tenantsTitle2.add(doPost("/api/tenant", tenant, Tenant.class)); createFutures.add(executor.submit(() ->
doPost("/api/tenant", tenant, Tenant.class)));
} }
List<Tenant> loadedTenantsTitle1 = new ArrayList<>(); List<Tenant> tenantsTitle2 = Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("saved '{}', qty {}", title2, tenantsTitle2.size());
List<Tenant> loadedTenantsTitle1 = new ArrayList<>(134);
PageLink pageLink = new PageLink(15, 0, title1); PageLink pageLink = new PageLink(15, 0, title1);
PageData<Tenant> pageData = null; PageData<Tenant> pageData = null;
do { do {
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
loadedTenantsTitle1.addAll(pageData.getData()); loadedTenantsTitle1.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantsTitle1, idComparator); log.debug("found by name '{}', step 15 {}", title1, loadedTenantsTitle1.size());
Collections.sort(loadedTenantsTitle1, idComparator);
assertThat(tenantsTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedTenantsTitle1);
Assert.assertEquals(tenantsTitle1, loadedTenantsTitle1); log.debug("asserted");
List<Tenant> loadedTenantsTitle2 = new ArrayList<>(); List<Tenant> loadedTenantsTitle2 = new ArrayList<>(127);
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
do { do {
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
loadedTenantsTitle2.addAll(pageData.getData()); loadedTenantsTitle2.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenantsTitle2, idComparator); log.debug("found by name '{}', step 4 {}", title1, loadedTenantsTitle2.size());
Collections.sort(loadedTenantsTitle2, idComparator); assertThat(tenantsTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedTenantsTitle2);
log.debug("asserted");
Assert.assertEquals(tenantsTitle2, loadedTenantsTitle2);
deleteEntitiesAsync("/api/tenant/", loadedTenantsTitle1, executor).get(TIMEOUT, TimeUnit.SECONDS);
log.debug("deleted '{}', size {}", title1, loadedTenantsTitle1.size());
for (Tenant tenant : loadedTenantsTitle1) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
pageLink = new PageLink(4, 0, title1); pageLink = new PageLink(4, 0, title1);
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
for (Tenant tenant : loadedTenantsTitle2) { log.debug("tried to search another '{}', step 4", title1);
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk()); deleteEntitiesAsync("/api/tenant/", loadedTenantsTitle2, executor).get(TIMEOUT, TimeUnit.SECONDS);
} log.debug("deleted '{}', size {}", title2, loadedTenantsTitle2.size());
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
pageData = doGetTypedWithPageLink("/api/tenants?", new TypeReference<PageData<Tenant>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenants?", PAGE_DATA_TENANT_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
log.debug("tried to search another '{}', step 4", title2);
} }
@Test @Test
@ -244,42 +282,50 @@ public abstract class BaseTenantControllerTest extends AbstractControllerTest {
loginSysAdmin(); loginSysAdmin();
List<TenantInfo> tenants = new ArrayList<>(); List<TenantInfo> tenants = new ArrayList<>();
PageLink pageLink = new PageLink(17); PageLink pageLink = new PageLink(17);
PageData<TenantInfo> pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink); PageData<TenantInfo> pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
tenants.addAll(pageData.getData()); tenants.addAll(pageData.getData());
for (int i=0;i<56;i++) { List<ListenableFuture<TenantInfo>> createFutures = new ArrayList<>(56);
for (int i = 0; i < 56; i++) {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Tenant"+i); tenant.setTitle("Tenant" + i);
tenants.add(new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default")); createFutures.add(executor.submit(() ->
new TenantInfo(doPost("/api/tenant", tenant, Tenant.class), "Default")));
} }
tenants.addAll(Futures.allAsList(createFutures).get(TIMEOUT, TimeUnit.SECONDS));
List<TenantInfo> loadedTenants = new ArrayList<>(); List<TenantInfo> loadedTenants = new ArrayList<>();
pageLink = new PageLink(17); pageLink = new PageLink(17);
do { do {
pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink);
loadedTenants.addAll(pageData.getData()); loadedTenants.addAll(pageData.getData());
if (pageData.hasNext()) { if (pageData.hasNext()) {
pageLink = pageLink.nextPageLink(); pageLink = pageLink.nextPageLink();
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(tenants, idComparator); assertThat(tenants).containsExactlyInAnyOrderElementsOf(loadedTenants);
Collections.sort(loadedTenants, idComparator);
Assert.assertEquals(tenants, loadedTenants); deleteEntitiesAsync("/api/tenant/", loadedTenants.stream()
.filter((t) -> !TEST_TENANT_NAME.equals(t.getTitle()))
for (TenantInfo tenant : loadedTenants) { .collect(Collectors.toList()), executor).get(TIMEOUT, TimeUnit.SECONDS);
if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
}
pageLink = new PageLink(17); pageLink = new PageLink(17);
pageData = doGetTypedWithPageLink("/api/tenantInfos?", new TypeReference<PageData<TenantInfo>>(){}, pageLink); pageData = doGetTypedWithPageLink("/api/tenantInfos?", PAGE_DATA_TENANT_INFO_TYPE_REF, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
} }
ListenableFuture<List<ResultActions>> deleteTenantsAsync(String urlTemplate, List<Tenant> tenants) {
List<ListenableFuture<ResultActions>> futures = new ArrayList<>(tenants.size());
for (Tenant device : tenants) {
futures.add(executor.submit(() ->
doDelete(urlTemplate + device.getId().getId())
.andExpect(status().isOk())));
}
return Futures.allAsList(futures);
}
} }

382
application/src/test/java/org/thingsboard/server/controller/BaseWebsocketApiTest.java

@ -24,10 +24,6 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
@ -46,19 +42,12 @@ import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.common.data.query.TsValue;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope; import org.thingsboard.server.service.subscription.TbAttributeSubscriptionScope;
import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd; import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate; import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -68,77 +57,38 @@ import java.util.concurrent.TimeUnit;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@Slf4j @Slf4j
public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest { public abstract class BaseWebsocketApiTest extends AbstractControllerTest {
private Tenant savedTenant;
private User tenantAdmin;
private TbTestWebSocketClient wsClient;
@Autowired @Autowired
private TelemetrySubscriptionService tsService; private TelemetrySubscriptionService tsService;
@Before Device device;
public void beforeTest() throws Exception { DeviceTypeFilter dtf;
loginSysAdmin();
Tenant tenant = new Tenant();
tenant.setTitle("My tenant");
savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantAdmin = new User();
tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
tenantAdmin.setTenantId(savedTenant.getId());
tenantAdmin.setEmail("tenant2@thingsboard.org");
tenantAdmin.setFirstName("Joe");
tenantAdmin.setLastName("Downs");
tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); @Before
public void setUp() throws Exception {
loginTenantAdmin();
wsClient = buildAndConnectWebSocketClient(); device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
dtf = new DeviceTypeFilter(device.getType(), device.getName());
} }
@After @After
public void afterTest() throws Exception { public void tearDown() throws Exception {
wsClient.close(); doDelete("/api/device/" + device.getId().getId())
loginSysAdmin();
doDelete("/api/tenant/" + savedTenant.getId().getId().toString())
.andExpect(status().isOk()); .andExpect(status().isOk());
} }
@Test @Test
public void testEntityDataHistoryWsCmd() throws Exception { public void testEntityDataHistoryWsCmd() throws Exception {
Device device = new Device(); List<String> keys = List.of("temperature");
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
DeviceTypeFilter dtf = new DeviceTypeFilter(); EntityDataUpdate update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf);
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf,
new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityHistoryCmd historyCmd = new EntityHistoryCmd();
historyCmd.setKeys(Arrays.asList("temperature"));
historyCmd.setAgg(Aggregation.NONE);
historyCmd.setLimit(1000);
historyCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1));
historyCmd.setEndTs(now);
EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
String msg = wsClient.waitForReply();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
@ -154,9 +104,8 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100); Thread.sleep(100);
wsClient.send(mapper.writeValueAsString(wrapper)); update = getWsClient().sendHistoryCmd(keys, now, TimeUnit.HOURS.toMillis(1), dtf);
msg = wsClient.waitForReply();
update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> dataList = update.getUpdate(); List<EntityData> dataList = update.getUpdate();
Assert.assertNotNull(dataList); Assert.assertNotNull(dataList);
@ -171,41 +120,16 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
@Test @Test
public void testEntityDataTimeSeriesWsCmd() throws Exception { public void testEntityDataTimeSeriesWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
DeviceTypeFilter dtf = new DeviceTypeFilter(); EntityDataUpdate update = getWsClient().sendEntityDataQuery(dtf);
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
String msg = wsClient.waitForReply();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
Assert.assertEquals(1, pageData.getData().size()); Assert.assertEquals(1, pageData.getData().size());
Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId()); Assert.assertEquals(device.getId(), pageData.getData().get(0).getEntityId());
TimeSeriesCmd tsCmd = new TimeSeriesCmd();
tsCmd.setKeys(Arrays.asList("temperature"));
tsCmd.setAgg(Aggregation.NONE);
tsCmd.setLimit(1000);
tsCmd.setStartTs(now - TimeUnit.HOURS.toMillis(1));
tsCmd.setTimeWindow(TimeUnit.HOURS.toMillis(1));
TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L)); TsKvEntry dataPoint1 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("temperature", 42L));
TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(2), new LongDataEntry("temperature", 43L));
TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 44L)); TsKvEntry dataPoint3 = new BasicTsKvEntry(now - TimeUnit.MINUTES.toMillis(3), new LongDataEntry("temperature", 44L));
@ -214,12 +138,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100); Thread.sleep(100);
cmd = new EntityDataCmd(1, null, null, null, tsCmd); update = getWsClient().subscribeTsUpdate(List.of("temperature"), now, TimeUnit.HOURS.toMillis(1));
wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
msg = wsClient.waitForReply();
update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> listData = update.getUpdate(); List<EntityData> listData = update.getUpdate();
Assert.assertNotNull(listData); Assert.assertNotNull(listData);
@ -233,10 +152,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L)); TsKvEntry dataPoint4 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 45L));
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(100); Thread.sleep(100);
sendTelemetry(device, Arrays.asList(dataPoint4)); sendTelemetry(device, Arrays.asList(dataPoint4));
msg = wsClient.waitForUpdate(); String msg = getWsClient().waitForUpdate();
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -252,44 +171,26 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
@Test @Test
public void testEntityCountWsCmd() throws Exception { public void testEntityCountWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(System.currentTimeMillis(), new LongDataEntry("temperature", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Collections.singletonList(dataPoint1));
DeviceTypeFilter dtf1 = new DeviceTypeFilter(); EntityCountQuery edq1 = new EntityCountQuery(dtf, Collections.emptyList());
dtf1.setDeviceNameFilter("D");
dtf1.setDeviceType("default");
EntityCountQuery edq1 = new EntityCountQuery(dtf1, Collections.emptyList());
EntityCountCmd cmd1 = new EntityCountCmd(1, edq1); EntityCountCmd cmd1 = new EntityCountCmd(1, edq1);
TelemetryPluginCmdsWrapper wrapper1 = new TelemetryPluginCmdsWrapper(); getWsClient().send(cmd1);
wrapper1.setEntityCountCmds(Collections.singletonList(cmd1));
wsClient.send(mapper.writeValueAsString(wrapper1)); EntityCountUpdate update1 = getWsClient().parseCountReply(getWsClient().waitForReply());
String msg1 = wsClient.waitForReply();
EntityCountUpdate update1 = mapper.readValue(msg1, EntityCountUpdate.class);
Assert.assertEquals(1, update1.getCmdId()); Assert.assertEquals(1, update1.getCmdId());
Assert.assertEquals(1, update1.getCount()); Assert.assertEquals(1, update1.getCount());
DeviceTypeFilter dtf2 = new DeviceTypeFilter(); DeviceTypeFilter dtf2 = new DeviceTypeFilter("non-existing-device-type", "D");
dtf2.setDeviceNameFilter("D");
dtf2.setDeviceType("non-existing-device-type");
EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList()); EntityCountQuery edq2 = new EntityCountQuery(dtf2, Collections.emptyList());
EntityCountCmd cmd2 = new EntityCountCmd(2, edq2); EntityCountCmd cmd2 = new EntityCountCmd(2, edq2);
TelemetryPluginCmdsWrapper wrapper2 = new TelemetryPluginCmdsWrapper(); getWsClient().send(cmd2);
wrapper2.setEntityCountCmds(Collections.singletonList(cmd2));
wsClient.send(mapper.writeValueAsString(wrapper2));
String msg2 = wsClient.waitForReply(); String msg2 = getWsClient().waitForReply();
EntityCountUpdate update2 = mapper.readValue(msg2, EntityCountUpdate.class); EntityCountUpdate update2 = getWsClient().parseCountReply(getWsClient().waitForReply());
Assert.assertEquals(2, update2.getCmdId()); Assert.assertEquals(2, update2.getCmdId());
Assert.assertEquals(0, update2.getCount()); Assert.assertEquals(0, update2.getCount());
@ -301,19 +202,12 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
highTemperatureFilter.setPredicate(predicate); highTemperatureFilter.setPredicate(predicate);
highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC); highTemperatureFilter.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf3 = new DeviceTypeFilter(); DeviceTypeFilter dtf3 = new DeviceTypeFilter("default", "D");
dtf3.setDeviceNameFilter("D");
dtf3.setDeviceType("default");
EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter)); EntityCountQuery edq3 = new EntityCountQuery(dtf3, Collections.singletonList(highTemperatureFilter));
EntityCountCmd cmd3 = new EntityCountCmd(3, edq3); EntityCountCmd cmd3 = new EntityCountCmd(3, edq3);
getWsClient().send(cmd3);
TelemetryPluginCmdsWrapper wrapper3 = new TelemetryPluginCmdsWrapper(); EntityCountUpdate update3 = getWsClient().parseCountReply(getWsClient().waitForReply());
wrapper3.setEntityCountCmds(Collections.singletonList(cmd3));
wsClient.send(mapper.writeValueAsString(wrapper3));
String msg3 = wsClient.waitForReply();
EntityCountUpdate update3 = mapper.readValue(msg3, EntityCountUpdate.class);
Assert.assertEquals(3, update3.getCmdId()); Assert.assertEquals(3, update3.getCmdId());
Assert.assertEquals(1, update3.getCount()); Assert.assertEquals(1, update3.getCount());
@ -325,47 +219,26 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
highTemperatureFilter2.setPredicate(predicate2); highTemperatureFilter2.setPredicate(predicate2);
highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC); highTemperatureFilter2.setValueType(EntityKeyValueType.NUMERIC);
DeviceTypeFilter dtf4 = new DeviceTypeFilter(); DeviceTypeFilter dtf4 = new DeviceTypeFilter("default", "D");
dtf4.setDeviceNameFilter("D");
dtf4.setDeviceType("default");
EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2)); EntityCountQuery edq4 = new EntityCountQuery(dtf4, Collections.singletonList(highTemperatureFilter2));
EntityCountCmd cmd4 = new EntityCountCmd(4, edq4); EntityCountCmd cmd4 = new EntityCountCmd(4, edq4);
TelemetryPluginCmdsWrapper wrapper4 = new TelemetryPluginCmdsWrapper(); getWsClient().send(cmd4);
wrapper4.setEntityCountCmds(Collections.singletonList(cmd4));
wsClient.send(mapper.writeValueAsString(wrapper4));
String msg4 = wsClient.waitForReply(); EntityCountUpdate update4 = getWsClient().parseCountReply(getWsClient().waitForReply());
EntityCountUpdate update4 = mapper.readValue(msg4, EntityCountUpdate.class);
Assert.assertEquals(4, update4.getCmdId()); Assert.assertEquals(4, update4.getCmdId());
Assert.assertEquals(0, update4.getCount()); Assert.assertEquals(0, update4.getCount());
} }
@Test @Test
public void testEntityDataLatestWidgetFlow() throws Exception { public void testEntityDataLatestWidgetFlow() throws Exception {
Device device = new Device(); List<EntityKey> keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
device.setName("Device"); long now = System.currentTimeMillis() - 100;
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis();
DeviceTypeFilter dtf = new DeviceTypeFilter();
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(),
Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")), Collections.emptyList());
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null),
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); Collections.emptyList(), keys, Collections.emptyList());
wsClient.send(mapper.writeValueAsString(wrapper)); EntityDataUpdate update = getWsClient().sendEntityDataQuery(edq);
String msg = wsClient.waitForReply();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
@ -379,17 +252,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
List<TsKvEntry> tsData = Arrays.asList(dataPoint1); List<TsKvEntry> tsData = Arrays.asList(dataPoint1);
sendTelemetry(device, tsData); sendTelemetry(device, tsData);
Thread.sleep(100); update = getWsClient().subscribeLatestUpdate(keys);
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")));
cmd = new EntityDataCmd(1, null, null, latestCmd, null);
wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
msg = wsClient.waitForReply();
update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -404,11 +267,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate();
update = mapper.readValue(msg, EntityDataUpdate.class); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
@ -419,43 +281,25 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint1)); sendTelemetry(device, Arrays.asList(dataPoint1));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@Test @Test
public void testEntityDataLatestTsWsCmd() throws Exception { public void testEntityDataLatestTsWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<EntityKey> keys = List.of(new EntityKey(EntityKeyType.TIME_SERIES, "temperature"));
DeviceTypeFilter dtf = new DeviceTypeFilter(); EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf);
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null), Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.TIME_SERIES, "temperature")));
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
String msg = wsClient.waitForReply();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
@ -471,13 +315,7 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Thread.sleep(100); Thread.sleep(100);
cmd = new EntityDataCmd(1, edq, null, latestCmd, null); update = getWsClient().subscribeLatestUpdate(keys, dtf);
wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
msg = wsClient.waitForReply();
update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -492,11 +330,9 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L)); TsKvEntry dataPoint2 = new BasicTsKvEntry(now, new LongDataEntry("temperature", 52L));
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(); update = getWsClient().parseDataReply(getWsClient().waitForUpdate());
update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
List<EntityData> eData = update.getUpdate(); List<EntityData> eData = update.getUpdate();
Assert.assertNotNull(eData); Assert.assertNotNull(eData);
@ -507,45 +343,24 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint1)); sendTelemetry(device, Arrays.asList(dataPoint1));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); String msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendTelemetry(device, Arrays.asList(dataPoint2)); sendTelemetry(device, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@Test @Test
public void testEntityDataLatestAttrWsCmd() throws Exception { public void testEntityDataLatestAttrWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<EntityKey> keys = List.of(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"));
DeviceTypeFilter dtf = new DeviceTypeFilter(); EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf);
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(Collections.singletonList(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey")));
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
String msg = wsClient.waitForReply();
Assert.assertNotNull(msg);
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
@ -555,15 +370,14 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.SERVER_ATTRIBUTE).get("serverAttributeKey").getValue());
getWsClient().registerWaitForUpdate();
wsClient.registerWaitForUpdate();
Thread.sleep(500); Thread.sleep(500);
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData);
msg = wsClient.waitForUpdate(); String msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg); Assert.assertNotNull(msg);
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
@ -579,10 +393,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
now = System.currentTimeMillis(); now = System.currentTimeMillis();
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L)); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("serverAttributeKey", 52L));
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500); Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(); msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg); Assert.assertNotNull(msg);
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
@ -596,51 +410,31 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue); Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), tsValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500); Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint1));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
Thread.sleep(500); Thread.sleep(500);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
} }
@Test @Test
public void testEntityDataLatestAttrTypesWsCmd() throws Exception { public void testEntityDataLatestAttrTypesWsCmd() throws Exception {
Device device = new Device();
device.setName("Device");
device.setType("default");
device.setLabel("testLabel" + (int) (Math.random() * 1000));
device = doPost("/api/device", device, Device.class);
long now = System.currentTimeMillis(); long now = System.currentTimeMillis();
List<EntityKey> keys = List.of(
new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"),
new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, "clientAttributeKey"),
new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, "sharedAttributeKey"),
new EntityKey(EntityKeyType.ATTRIBUTE, "anyAttributeKey"));
EntityDataUpdate update = getWsClient().subscribeLatestUpdate(keys, dtf);
DeviceTypeFilter dtf = new DeviceTypeFilter();
dtf.setDeviceNameFilter("D");
dtf.setDeviceType("default");
EntityDataQuery edq = new EntityDataQuery(dtf, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
LatestValueCmd latestCmd = new LatestValueCmd();
List<EntityKey> keys = new ArrayList<>();
keys.add(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "serverAttributeKey"));
keys.add(new EntityKey(EntityKeyType.CLIENT_ATTRIBUTE, "clientAttributeKey"));
keys.add(new EntityKey(EntityKeyType.SHARED_ATTRIBUTE, "sharedAttributeKey"));
keys.add(new EntityKey(EntityKeyType.ATTRIBUTE, "anyAttributeKey"));
latestCmd.setKeys(keys);
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper));
String msg = wsClient.waitForReply();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
PageData<EntityData> pageData = update.getData(); PageData<EntityData> pageData = update.getData();
Assert.assertNotNull(pageData); Assert.assertNotNull(pageData);
@ -659,14 +453,14 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs()); Assert.assertEquals(0, pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getTs());
Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue()); Assert.assertEquals("", pageData.getData().get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey").getValue());
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L)); AttributeKvEntry dataPoint1 = new BaseAttributeKvEntry(now - TimeUnit.MINUTES.toMillis(1), new LongDataEntry("serverAttributeKey", 42L));
List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1); List<AttributeKvEntry> tsData = Arrays.asList(dataPoint1);
Thread.sleep(100); Thread.sleep(100);
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, tsData);
msg = wsClient.waitForUpdate(); String msg = getWsClient().waitForUpdate();
Assert.assertNotNull(msg); Assert.assertNotNull(msg);
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -679,22 +473,22 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint1.getLastUpdateTs(), dataPoint1.getValueAsString()), attrValue);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint1));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending duplicate update again //Sending duplicate update again
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint1)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint1));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
Assert.assertNull(msg); Assert.assertNull(msg);
//Sending update from the past, while latest value has new timestamp; //Sending update from the past, while latest value has new timestamp;
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L)); AttributeKvEntry dataPoint2 = new BaseAttributeKvEntry(now, new LongDataEntry("sharedAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint2)); sendAttributes(device, TbAttributeSubscriptionScope.SHARED_SCOPE, Arrays.asList(dataPoint2));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
@ -705,10 +499,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey"); attrValue = eData.get(0).getLatest().get(EntityKeyType.SHARED_ATTRIBUTE).get("sharedAttributeKey");
Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint2.getLastUpdateTs(), dataPoint2.getValueAsString()), attrValue);
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L)); AttributeKvEntry dataPoint3 = new BaseAttributeKvEntry(now, new LongDataEntry("clientAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint3)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint3));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
@ -719,10 +513,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey"); attrValue = eData.get(0).getLatest().get(EntityKeyType.CLIENT_ATTRIBUTE).get("clientAttributeKey");
Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint3.getLastUpdateTs(), dataPoint3.getValueAsString()), attrValue);
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L)); AttributeKvEntry dataPoint4 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 42L));
sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint4)); sendAttributes(device, TbAttributeSubscriptionScope.CLIENT_SCOPE, Arrays.asList(dataPoint4));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();
@ -733,10 +527,10 @@ public abstract class BaseWebsocketApiTest extends AbstractWebsocketTest {
attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey"); attrValue = eData.get(0).getLatest().get(EntityKeyType.ATTRIBUTE).get("anyAttributeKey");
Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue); Assert.assertEquals(new TsValue(dataPoint4.getLastUpdateTs(), dataPoint4.getValueAsString()), attrValue);
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L)); AttributeKvEntry dataPoint5 = new BaseAttributeKvEntry(now, new LongDataEntry("anyAttributeKey", 43L));
sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint5)); sendAttributes(device, TbAttributeSubscriptionScope.SERVER_SCOPE, Arrays.asList(dataPoint5));
msg = wsClient.waitForUpdate(TimeUnit.SECONDS.toMillis(1)); msg = getWsClient().waitForUpdate(TimeUnit.SECONDS.toMillis(1));
update = mapper.readValue(msg, EntityDataUpdate.class); update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
eData = update.getUpdate(); eData = update.getUpdate();

119
application/src/test/java/org/thingsboard/server/controller/TbTestWebSocketClient.java

@ -18,9 +18,25 @@ package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshake;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery;
import org.thingsboard.server.common.data.query.EntityFilter;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityCountUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataUpdate;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityHistoryCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.LatestValueCmd;
import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd;
import java.net.URI; import java.net.URI;
import java.nio.channels.NotYetConnectedException; import java.nio.channels.NotYetConnectedException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -73,6 +89,18 @@ public class TbTestWebSocketClient extends WebSocketClient {
super.send(text); super.send(text);
} }
public void send(EntityDataCmd cmd) throws NotYetConnectedException {
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd));
this.send(JacksonUtil.toString(wrapper));
}
public void send(EntityCountCmd cmd) throws NotYetConnectedException {
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityCountCmds(Collections.singletonList(cmd));
this.send(JacksonUtil.toString(wrapper));
}
public String waitForUpdate() { public String waitForUpdate() {
return waitForUpdate(TimeUnit.SECONDS.toMillis(3)); return waitForUpdate(TimeUnit.SECONDS.toMillis(3));
} }
@ -94,4 +122,95 @@ public class TbTestWebSocketClient extends WebSocketClient {
} }
return lastMsg; return lastMsg;
} }
public EntityDataUpdate parseDataReply(String msg) {
return JacksonUtil.fromString(msg, EntityDataUpdate.class);
}
public EntityCountUpdate parseCountReply(String msg) {
return JacksonUtil.fromString(msg, EntityCountUpdate.class);
}
public EntityDataUpdate subscribeLatestUpdate(List<EntityKey> keys, EntityFilter entityFilter) {
EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
return subscribeLatestUpdate(keys, edq);
}
public EntityDataUpdate subscribeLatestUpdate(List<EntityKey> keys) {
return subscribeLatestUpdate(keys, (EntityDataQuery) null);
}
public EntityDataUpdate subscribeLatestUpdate(List<EntityKey> keys, EntityDataQuery edq) {
LatestValueCmd latestCmd = new LatestValueCmd();
latestCmd.setKeys(keys);
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, latestCmd, null);
send(cmd);
return parseDataReply(waitForReply());
}
public EntityDataUpdate subscribeTsUpdate(List<String> keys, long startTs, long timeWindow) {
return subscribeTsUpdate(keys, startTs, timeWindow, (EntityDataQuery) null);
}
public EntityDataUpdate subscribeTsUpdate(List<String> keys, long startTs, long timeWindow, EntityFilter entityFilter) {
EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
return subscribeTsUpdate(keys, startTs, timeWindow, edq);
}
public EntityDataUpdate subscribeTsUpdate(List<String> keys, long startTs, long timeWindow, EntityDataQuery edq) {
TimeSeriesCmd tsCmd = new TimeSeriesCmd();
tsCmd.setKeys(keys);
tsCmd.setAgg(Aggregation.NONE);
tsCmd.setLimit(1000);
tsCmd.setStartTs(startTs - timeWindow);
tsCmd.setTimeWindow(timeWindow);
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, tsCmd);
send(cmd);
return parseDataReply(waitForReply());
}
public EntityDataUpdate sendHistoryCmd(List<String> keys, long startTs, long timeWindow) {
return sendHistoryCmd(keys, startTs, timeWindow, (EntityDataQuery) null);
}
public EntityDataUpdate sendHistoryCmd(List<String> keys, long startTs, long timeWindow, EntityFilter entityFilter) {
EntityDataQuery edq = new EntityDataQuery(entityFilter,
new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
return sendHistoryCmd(keys, startTs, timeWindow, edq);
}
public EntityDataUpdate sendHistoryCmd(List<String> keys, long startTs, long timeWindow, EntityDataQuery edq) {
EntityHistoryCmd historyCmd = new EntityHistoryCmd();
historyCmd.setKeys(keys);
historyCmd.setAgg(Aggregation.NONE);
historyCmd.setLimit(1000);
historyCmd.setStartTs(startTs - timeWindow);
historyCmd.setEndTs(startTs);
EntityDataCmd cmd = new EntityDataCmd(1, edq, historyCmd, null, null);
send(cmd);
return parseDataReply(this.waitForReply());
}
public EntityDataUpdate sendEntityDataQuery(EntityDataQuery edq) {
log.warn("sendEntityDataQuery {}", edq);
EntityDataCmd cmd = new EntityDataCmd(1, edq, null, null, null);
send(cmd);
String msg = this.waitForReply();
return parseDataReply(msg);
}
public EntityDataUpdate sendEntityDataQuery(EntityFilter entityFilter) {
log.warn("sendEntityDataQuery {}", entityFilter);
EntityDataQuery edq = new EntityDataQuery(entityFilter, new EntityDataPageLink(1, 0, null, null),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
return sendEntityDataQuery(edq);
}
} }

50
application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2022 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.script;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
import java.util.UUID;
@Slf4j
@Service
@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "mock")
public class MockJsInvokeService implements JsInvokeService {
@Override
public ListenableFuture<UUID> eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) {
log.warn("eval {} {} {} {}", tenantId, scriptType, scriptBody, argNames);
return Futures.immediateFuture(UUID.randomUUID());
}
@Override
public ListenableFuture<Object> invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) {
log.warn("invokeFunction {} {} {} {}", tenantId, customerId, scriptId, args);
return Futures.immediateFuture("{}");
}
@Override
public ListenableFuture<Void> release(UUID scriptId) {
log.warn("release {}", scriptId);
return Futures.immediateFuture(null);
}
}

2
application/src/test/java/org/thingsboard/server/service/sql/SequentialTimeseriesPersistenceTest.java

@ -56,8 +56,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@DaoSqlTest @DaoSqlTest
public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest { public class SequentialTimeseriesPersistenceTest extends AbstractControllerTest {
static final int TIMEOUT = 30;
final String TOTALIZER = "Totalizer"; final String TOTALIZER = "Totalizer";
final int TTL = 99999; final int TTL = 99999;
final String GENERIC_CUMULATIVE_OBJ = "genericCumulativeObj"; final String GENERIC_CUMULATIVE_OBJ = "genericCumulativeObj";

24
application/src/test/java/org/thingsboard/server/transport/lwm2m/AbstractLwM2MIntegrationTest.java

@ -60,8 +60,7 @@ import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.SingleEntityFilter; import org.thingsboard.server.common.data.query.SingleEntityFilter;
import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType; import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.controller.AbstractWebsocketTest; import org.thingsboard.server.controller.AbstractControllerTest;
import org.thingsboard.server.controller.TbTestWebSocketClient;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper; import org.thingsboard.server.service.telemetry.cmd.TelemetryPluginCmdsWrapper;
import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd; import org.thingsboard.server.service.telemetry.cmd.v2.EntityDataCmd;
@ -104,7 +103,7 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil
}) })
@Slf4j @Slf4j
@DaoSqlTest @DaoSqlTest
public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest { public abstract class AbstractLwM2MIntegrationTest extends AbstractControllerTest {
@SpyBean @SpyBean
DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest; DefaultLwM2mUplinkMsgHandler defaultLwM2mUplinkMsgHandlerTest;
@ -176,7 +175,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS)); protected final Set<Lwm2mTestHelper.LwM2MClientState> expectedStatusesRegistrationBsSuccess = new HashSet<>(Arrays.asList(ON_INIT, ON_BOOTSTRAP_STARTED, ON_BOOTSTRAP_SUCCESS, ON_REGISTRATION_STARTED, ON_REGISTRATION_SUCCESS));
protected DeviceProfile deviceProfile; protected DeviceProfile deviceProfile;
protected ScheduledExecutorService executor; protected ScheduledExecutorService executor;
protected TbTestWebSocketClient wsClient;
protected LwM2MTestClient lwM2MTestClient; protected LwM2MTestClient lwM2MTestClient;
private String[] resources; private String[] resources;
@ -187,17 +185,16 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
@After @After
public void after() { public void after() {
wsClient.close();
clientDestroy(); clientDestroy();
executor.shutdownNow(); executor.shutdownNow();
} }
@AfterClass @AfterClass
public static void afterClass () { public static void afterClass() {
awaitServersDestroy(); awaitServersDestroy();
} }
private void init () throws Exception { private void init() throws Exception {
executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled")); executor = Executors.newScheduledThreadPool(10, ThingsBoardThreadFactory.forName("test-lwm2m-scheduled"));
loginTenantAdmin(); loginTenantAdmin();
for (String resourceName : this.resources) { for (String resourceName : this.resources) {
@ -212,7 +209,6 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
}); });
Assert.assertNotNull(lwModel); Assert.assertNotNull(lwModel);
} }
wsClient = buildAndConnectWebSocketClient();
} }
public void basicTestConnectionObserveTelemetry(Security security, public void basicTestConnectionObserveTelemetry(Security security,
@ -234,12 +230,12 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper(); TelemetryPluginCmdsWrapper wrapper = new TelemetryPluginCmdsWrapper();
wrapper.setEntityDataCmds(Collections.singletonList(cmd)); wrapper.setEntityDataCmds(Collections.singletonList(cmd));
wsClient.send(mapper.writeValueAsString(wrapper)); getWsClient().send(mapper.writeValueAsString(wrapper));
wsClient.waitForReply(); getWsClient().waitForReply();
wsClient.registerWaitForUpdate(); getWsClient().registerWaitForUpdate();
createNewClient(security, coapConfig, false, endpoint, false, null); createNewClient(security, coapConfig, false, endpoint, false, null);
String msg = wsClient.waitForUpdate(); String msg = getWsClient().waitForUpdate();
EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class); EntityDataUpdate update = mapper.readValue(msg, EntityDataUpdate.class);
Assert.assertEquals(1, update.getCmdId()); Assert.assertEquals(1, update.getCmdId());
@ -371,7 +367,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
return credentials; return credentials;
} }
private static void awaitServersDestroy() { private static void awaitServersDestroy() {
await("One of servers ports number is not free") await("One of servers ports number is not free")
.atMost(3000, TimeUnit.MILLISECONDS) .atMost(3000, TimeUnit.MILLISECONDS)
.until(() -> isServerPortsAvailable() == null); .until(() -> isServerPortsAvailable() == null);
@ -390,7 +386,7 @@ public abstract class AbstractLwM2MIntegrationTest extends AbstractWebsocketTest
return null; return null;
} }
private static void awaitClientDestroy(LeshanClient leshanClient) { private static void awaitClientDestroy(LeshanClient leshanClient) {
await("Destroy LeshanClient: delete All is registered Servers.") await("Destroy LeshanClient: delete All is registered Servers.")
.atMost(2000, TimeUnit.MILLISECONDS) .atMost(2000, TimeUnit.MILLISECONDS)
.until(() -> leshanClient.getRegisteredServers().size() == 0); .until(() -> leshanClient.getRegisteredServers().size() == 0);

2
application/src/test/java/org/thingsboard/server/transport/lwm2m/ota/sql/OtaLwM2MIntegrationTest.java

@ -52,8 +52,6 @@ import static org.thingsboard.server.transport.lwm2m.Lwm2mTestHelper.LwM2MProfil
@Slf4j @Slf4j
public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest { public class OtaLwM2MIntegrationTest extends AbstractOtaLwM2MIntegrationTest {
public static final int TIMEOUT = 30;
protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA = protected final String OBSERVE_ATTRIBUTES_WITH_PARAMS_OTA =
" {\n" + " {\n" +

35
application/src/test/resources/application-test.properties

@ -20,3 +20,38 @@ transport.mqtt.enabled=false
transport.coap.enabled=false transport.coap.enabled=false
transport.lwm2m.enabled=false transport.lwm2m.enabled=false
transport.snmp.enabled=false transport.snmp.enabled=false
# Low latency settings to perform tests as fast as possible
sql.attributes.batch_max_delay=5
sql.attributes.batch_threads=2
sql.ts.batch_max_delay=5
sql.ts.batch_threads=2
sql.ts_latest.batch_max_delay=5
sql.ts_latest.batch_threads=2
sql.events.batch_max_delay=5
sql.events.batch_threads=2
actors.system.tenant_dispatcher_pool_size=4
actors.system.device_dispatcher_pool_size=8
actors.system.rule_dispatcher_pool_size=12
transport.sessions.report_timeout=10000
queue.transport_api.request_poll_interval=5
queue.transport_api.response_poll_interval=5
queue.transport.poll_interval=5
queue.core.poll-interval=5
queue.core.partitions=2
queue.rule-engine.poll-interval=5
queue.rule-engine.queues[0].poll-interval=5
queue.rule-engine.queues[0].partitions=2
queue.rule-engine.queues[0].processing-strategy.retries=1
queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[1].poll-interval=5
queue.rule-engine.queues[1].partitions=2
queue.rule-engine.queues[1].processing-strategy.retries=1
queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[2].poll-interval=5
queue.rule-engine.queues[2].partitions=2
queue.rule-engine.queues[2].processing-strategy.retries=1
queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0

4
common/data/src/main/java/org/thingsboard/server/common/data/query/DeviceTypeFilter.java

@ -15,8 +15,12 @@
*/ */
package org.thingsboard.server.common.data.query; package org.thingsboard.server.common.data.query;
import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@AllArgsConstructor
@Data @Data
public class DeviceTypeFilter implements EntityFilter { public class DeviceTypeFilter implements EntityFilter {

2
common/message/src/main/java/org/thingsboard/server/common/msg/TbRuleEngineActorMsg.java

@ -17,7 +17,9 @@ package org.thingsboard.server.common.msg;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.ToString;
@ToString
@EqualsAndHashCode @EqualsAndHashCode
public abstract class TbRuleEngineActorMsg implements TbActorMsg { public abstract class TbRuleEngineActorMsg implements TbActorMsg {

2
common/message/src/main/java/org/thingsboard/server/common/msg/queue/QueueToRuleEngineMsg.java

@ -29,7 +29,7 @@ import java.util.Set;
/** /**
* Created by ashvayka on 15.03.18. * Created by ashvayka on 15.03.18.
*/ */
@ToString @ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
public final class QueueToRuleEngineMsg extends TbRuleEngineActorMsg { public final class QueueToRuleEngineMsg extends TbRuleEngineActorMsg {

76
dao/src/test/java/org/thingsboard/server/dao/service/BaseCustomerServiceTest.java

@ -16,11 +16,16 @@
package org.thingsboard.server.dao.service; package org.thingsboard.server.dao.service;
import com.datastax.oss.driver.api.core.uuid.Uuids; import com.datastax.oss.driver.api.core.uuid.Uuids;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import org.apache.commons.lang3.RandomStringUtils; import org.apache.commons.lang3.RandomStringUtils;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.thingsboard.common.util.ThingsBoardExecutors;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
@ -29,17 +34,22 @@ import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat;
public abstract class BaseCustomerServiceTest extends AbstractServiceTest { public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
static final int TIMEOUT = 30;
private IdComparator<Customer> idComparator = new IdComparator<>(); ListeningExecutorService executor;
private TenantId tenantId; private TenantId tenantId;
@Before @Before
public void before() { public void before() {
executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(8, getClass()));
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("My tenant"); tenant.setTitle("My tenant");
Tenant savedTenant = tenantService.saveTenant(tenant); Tenant savedTenant = tenantService.saveTenant(tenant);
@ -49,6 +59,8 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
@After @After
public void after() { public void after() {
executor.shutdownNow();
tenantService.deleteTenant(tenantId); tenantService.deleteTenant(tenantId);
} }
@ -130,22 +142,24 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testFindCustomersByTenantId() { public void testFindCustomersByTenantId() throws Exception {
Tenant tenant = new Tenant(); Tenant tenant = new Tenant();
tenant.setTitle("Test tenant"); tenant.setTitle("Test tenant");
tenant = tenantService.saveTenant(tenant); tenant = tenantService.saveTenant(tenant);
TenantId tenantId = tenant.getId(); TenantId tenantId = tenant.getId();
List<Customer> customers = new ArrayList<>(); List<ListenableFuture<Customer>> futures = new ArrayList<>(135);
for (int i = 0; i < 135; i++) { for (int i = 0; i < 135; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
customer.setTitle("Customer" + i); customer.setTitle("Customer" + i);
customers.add(customerService.saveCustomer(customer)); futures.add(executor.submit(() ->
customerService.saveCustomer(customer)));
} }
List<Customer> customers = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Customer> loadedCustomers = new ArrayList<>(); List<Customer> loadedCustomers = new ArrayList<>(135);
PageLink pageLink = new PageLink(23); PageLink pageLink = new PageLink(23);
PageData<Customer> pageData = null; PageData<Customer> pageData = null;
do { do {
@ -156,10 +170,7 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customers, idComparator); assertThat(customers).containsExactlyInAnyOrderElementsOf(loadedCustomers);
Collections.sort(loadedCustomers, idComparator);
Assert.assertEquals(customers, loadedCustomers);
customerService.deleteCustomersByTenantId(tenantId); customerService.deleteCustomersByTenantId(tenantId);
@ -172,31 +183,34 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testFindCustomersByTenantIdAndTitle() { public void testFindCustomersByTenantIdAndTitle() throws Exception {
String title1 = "Customer title 1"; String title1 = "Customer title 1";
List<Customer> customersTitle1 = new ArrayList<>(); List<ListenableFuture<Customer>> futures = new ArrayList<>(143);
for (int i = 0; i < 143; i++) { for (int i = 0; i < 143; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title1 + suffix; String title = title1 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle1.add(customerService.saveCustomer(customer)); futures.add(executor.submit(() -> customerService.saveCustomer(customer)));
} }
List<Customer> customersTitle1 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
String title2 = "Customer title 2"; String title2 = "Customer title 2";
List<Customer> customersTitle2 = new ArrayList<>(); futures = new ArrayList<>(175);
for (int i = 0; i < 175; i++) { for (int i = 0; i < 175; i++) {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10)); String suffix = RandomStringUtils.randomAlphanumeric((int) (5 + Math.random() * 10));
String title = title2 + suffix; String title = title2 + suffix;
title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
customer.setTitle(title); customer.setTitle(title);
customersTitle2.add(customerService.saveCustomer(customer)); futures.add(executor.submit(() -> customerService.saveCustomer(customer)));
} }
List<Customer> customersTitle2 = Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
List<Customer> loadedCustomersTitle1 = new ArrayList<>(); List<Customer> loadedCustomersTitle1 = new ArrayList<>(143);
PageLink pageLink = new PageLink(15, 0, title1); PageLink pageLink = new PageLink(15, 0, title1);
PageData<Customer> pageData = null; PageData<Customer> pageData = null;
do { do {
@ -207,12 +221,9 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle1, idComparator); assertThat(customersTitle1).as(title1).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle1);
Collections.sort(loadedCustomersTitle1, idComparator);
Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
List<Customer> loadedCustomersTitle2 = new ArrayList<>(); List<Customer> loadedCustomersTitle2 = new ArrayList<>(175);
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
do { do {
pageData = customerService.findCustomersByTenantId(tenantId, pageLink); pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
@ -222,23 +233,30 @@ public abstract class BaseCustomerServiceTest extends AbstractServiceTest {
} }
} while (pageData.hasNext()); } while (pageData.hasNext());
Collections.sort(customersTitle2, idComparator); assertThat(customersTitle2).as(title2).containsExactlyInAnyOrderElementsOf(loadedCustomersTitle2);
Collections.sort(loadedCustomersTitle2, idComparator);
Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
List<ListenableFuture<Void>> deleteFutures = new ArrayList<>(143);
for (Customer customer : loadedCustomersTitle1) { for (Customer customer : loadedCustomersTitle1) {
customerService.deleteCustomer(tenantId, customer.getId()); deleteFutures.add(executor.submit(() -> {
customerService.deleteCustomer(tenantId, customer.getId());
return null;
}));
} }
Futures.allAsList(deleteFutures).get(TIMEOUT, TimeUnit.SECONDS);
pageLink = new PageLink(4, 0, title1); pageLink = new PageLink(4, 0, title1);
pageData = customerService.findCustomersByTenantId(tenantId, pageLink); pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
Assert.assertFalse(pageData.hasNext()); Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size()); Assert.assertEquals(0, pageData.getData().size());
deleteFutures = new ArrayList<>(175);
for (Customer customer : loadedCustomersTitle2) { for (Customer customer : loadedCustomersTitle2) {
customerService.deleteCustomer(tenantId, customer.getId()); deleteFutures.add(executor.submit(() -> {
customerService.deleteCustomer(tenantId, customer.getId());
return null;
}));
} }
Futures.allAsList(deleteFutures).get(TIMEOUT, TimeUnit.SECONDS);
pageLink = new PageLink(4, 0, title2); pageLink = new PageLink(4, 0, title2);
pageData = customerService.findCustomersByTenantId(tenantId, pageLink); pageData = customerService.findCustomersByTenantId(tenantId, pageLink);

45
dao/src/test/resources/application-test.properties

@ -60,4 +60,47 @@ database.ts_max_intervals=700
sql.remove_null_chars=true sql.remove_null_chars=true
edges.enabled=true # Edge disabled to speed up the context init. Will be enabled by @TestPropertySource in respective tests
edges.enabled=false
# Transports disabled to speed up the context init. Particular transport will be enabled with @TestPropertySource in respective tests
transport.http.enabled=false
transport.mqtt.enabled=false
transport.coap.enabled=false
transport.lwm2m.enabled=false
transport.snmp.enabled=false
# Low latency settings to perform tests as fast as possible
sql.attributes.batch_max_delay=5
sql.attributes.batch_threads=2
sql.ts.batch_max_delay=5
sql.ts.batch_threads=2
sql.ts_latest.batch_max_delay=5
sql.ts_latest.batch_threads=2
sql.events.batch_max_delay=5
sql.events.batch_threads=2
actors.system.tenant_dispatcher_pool_size=4
actors.system.device_dispatcher_pool_size=8
actors.system.rule_dispatcher_pool_size=12
transport.sessions.report_timeout=10000
queue.transport_api.request_poll_interval=5
queue.transport_api.response_poll_interval=5
queue.transport.poll_interval=5
queue.core.poll-interval=5
queue.core.partitions=2
queue.rule-engine.poll-interval=5
queue.rule-engine.queues[0].poll-interval=5
queue.rule-engine.queues[0].partitions=2
queue.rule-engine.queues[0].processing-strategy.retries=1
queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[1].poll-interval=5
queue.rule-engine.queues[1].partitions=2
queue.rule-engine.queues[1].processing-strategy.retries=1
queue.rule-engine.queues[1].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[1].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[2].poll-interval=5
queue.rule-engine.queues[2].partitions=2
queue.rule-engine.queues[2].processing-strategy.retries=1
queue.rule-engine.queues[2].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0

11
dao/src/test/resources/nosql-test.properties

@ -17,3 +17,14 @@ spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb spring.datasource.url=jdbc:tc:postgresql:12.8:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb
spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver spring.datasource.driverClassName=org.testcontainers.jdbc.ContainerDatabaseDriver
spring.datasource.hikari.maximumPoolSize = 50 spring.datasource.hikari.maximumPoolSize = 50
queue.rule-engine.queues[0].name=Main
queue.rule-engine.queues[0].topic=tb_rule_engine.main
queue.rule-engine.queues[0].poll-interval=5
queue.rule-engine.queues[0].partitions=2
queue.rule-engine.queues[0].pack-processing-timeout=3000
queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES
queue.rule-engine.queues[0].processing-strategy.retries=1
queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[0].submit-strategy.type=BURST

7
dao/src/test/resources/sql-test.properties

@ -45,10 +45,13 @@ queue.rule-engine.pack-processing-timeout=3000
queue.rule-engine.queues[0].name=Main queue.rule-engine.queues[0].name=Main
queue.rule-engine.queues[0].topic=tb_rule_engine.main queue.rule-engine.queues[0].topic=tb_rule_engine.main
queue.rule-engine.queues[0].poll-interval=25 queue.rule-engine.queues[0].poll-interval=5
queue.rule-engine.queues[0].partitions=3 queue.rule-engine.queues[0].partitions=2
queue.rule-engine.queues[0].pack-processing-timeout=3000 queue.rule-engine.queues[0].pack-processing-timeout=3000
queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES queue.rule-engine.queues[0].processing-strategy.type=SKIP_ALL_FAILURES
queue.rule-engine.queues[0].processing-strategy.retries=1
queue.rule-engine.queues[0].processing-strategy.pause-between-retries=0
queue.rule-engine.queues[0].processing-strategy.max-pause-between-retries=0
queue.rule-engine.queues[0].submit-strategy.type=BURST queue.rule-engine.queues[0].submit-strategy.type=BURST
sql.log_entity_queries=true sql.log_entity_queries=true

32
msa/black-box-tests/src/test/resources/logback.xml

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!--
Copyright © 2016-2022 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.
-->
<!DOCTYPE configuration>
<configuration scan="true" scanPeriod="10 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="STDOUT"/>
</root>
</configuration>

23
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.ScriptEngine;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
@ -30,8 +31,6 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsg;
import static org.thingsboard.common.util.DonAsynchron.withCallback;
@Slf4j @Slf4j
@RuleNode( @RuleNode(
type = ComponentType.ACTION, type = ComponentType.ACTION,
@ -49,15 +48,22 @@ public class TbLogNode implements TbNode {
private TbLogNodeConfiguration config; private TbLogNodeConfiguration config;
private ScriptEngine jsEngine; private ScriptEngine jsEngine;
private boolean standard;
@Override @Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class); this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class);
this.jsEngine = ctx.createJsScriptEngine(config.getJsScript()); this.standard = new TbLogNodeConfiguration().defaultConfiguration().getJsScript().equals(config.getJsScript());
this.jsEngine = this.standard ? null : ctx.createJsScriptEngine(config.getJsScript());
} }
@Override @Override
public void onMsg(TbContext ctx, TbMsg msg) { public void onMsg(TbContext ctx, TbMsg msg) {
if (standard) {
logStandard(ctx, msg);
return;
}
ctx.logJsEvalRequest(); ctx.logJsEvalRequest();
Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() { Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback<String>() {
@Override @Override
@ -75,6 +81,17 @@ public class TbLogNode implements TbNode {
}, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor }, MoreExecutors.directExecutor()); //usually js responses runs on js callback executor
} }
void logStandard(TbContext ctx, TbMsg msg) {
log.info(toLogMessage(msg));
ctx.tellSuccess(msg);
}
String toLogMessage(TbMsg msg) {
return "\n" +
"Incoming message:\n" + msg.getData() + "\n" +
"Incoming metadata:\n" + JacksonUtil.toString(msg.getMetaData().getData());
}
@Override @Override
public void destroy() { public void destroy() {
if (jsEngine != null) { if (jsEngine != null) {

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java

@ -26,7 +26,7 @@ public class TbLogNodeConfiguration implements NodeConfiguration {
@Override @Override
public TbLogNodeConfiguration defaultConfiguration() { public TbLogNodeConfiguration defaultConfiguration() {
TbLogNodeConfiguration configuration = new TbLogNodeConfiguration(); TbLogNodeConfiguration configuration = new TbLogNodeConfiguration();
configuration.setJsScript("return 'Incoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); configuration.setJsScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);");
return configuration; return configuration;
} }
} }

80
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbLogNodeTest.java

@ -0,0 +1,80 @@
/**
* Copyright © 2016-2022 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.rule.engine.action;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.Collections;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
@Slf4j
public class TbLogNodeTest {
@Test
void givenMsg_whenToLog_thenReturnString() {
TbLogNode node = new TbLogNode();
String data = "{\"key\": \"value\"}";
TbMsgMetaData metaData = new TbMsgMetaData(Map.of("mdKey1", "mdValue1", "mdKey2", "23"));
TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, data);
String logMessage = node.toLogMessage(msg);
log.info(logMessage);
assertThat(logMessage).isEqualTo("\n" +
"Incoming message:\n" +
"{\"key\": \"value\"}\n" +
"Incoming metadata:\n" +
"{\"mdKey1\":\"mdValue1\",\"mdKey2\":\"23\"}");
}
@Test
void givenEmptyDataMsg_whenToLog_thenReturnString() {
TbLogNode node = new TbLogNode();
TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap());
TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, "");
String logMessage = node.toLogMessage(msg);
log.info(logMessage);
assertThat(logMessage).isEqualTo("\n" +
"Incoming message:\n" +
"\n" +
"Incoming metadata:\n" +
"{}");
}
@Test
void givenNullDataMsg_whenToLog_thenReturnString() {
TbLogNode node = new TbLogNode();
TbMsgMetaData metaData = new TbMsgMetaData(Collections.emptyMap());
TbMsg msg = TbMsg.newMsg("POST_TELEMETRY", TenantId.SYS_TENANT_ID, metaData, null);
String logMessage = node.toLogMessage(msg);
log.info(logMessage);
assertThat(logMessage).isEqualTo("\n" +
"Incoming message:\n" +
"null\n" +
"Incoming metadata:\n" +
"{}");
}
}
Loading…
Cancel
Save