Browse Source

Merge branch 'lts-4.2' into feature/iot-hub

pull/15539/head
Igor Kulikov 5 months ago
parent
commit
e211e15fbb
  1. 2
      application/src/main/resources/thingsboard.yml
  2. 9
      application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java
  3. 18
      application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java
  4. 7
      application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java
  5. 9
      application/src/test/java/org/thingsboard/server/service/resource/DefaultResourceDataCacheTest.java
  6. 5
      common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java
  7. 2
      dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java
  8. 14
      dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java
  9. 47
      dao/src/test/java/org/thingsboard/server/dao/TbTimescaleDBContainerProvider.java
  10. 1
      dao/src/test/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider
  11. 2
      dao/src/test/resources/nosql-test.properties
  12. 2
      dao/src/test/resources/sql-test.properties
  13. 2
      dao/src/test/resources/timescale-test.properties
  14. 57
      edqs/src/test/java/org/thingsboard/server/edqs/repo/DeviceTypeFilterTest.java
  15. 4
      pom.xml

2
application/src/main/resources/thingsboard.yml

@ -1542,7 +1542,7 @@ device:
pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}" pem_cert_file: "${DEVICE_CONNECTIVITY_COAPS_CA_ROOT_CERT:cafile.pem}"
gateway: gateway:
# The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch # The docker tag for thingsboard/tb-gateway image used in docker-compose file for gateway launch
image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.7-stable}" image_version: "${DEVICE_CONNECTIVITY_GATEWAY_IMAGE_VERSION:3.8-stable}"
# Edges parameters # Edges parameters
edges: edges:

9
application/src/test/java/org/thingsboard/server/controller/DeviceConnectivityControllerTest.java

@ -95,7 +95,7 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
private DeviceProfileId mqttDeviceProfileId; private DeviceProfileId mqttDeviceProfileId;
private DeviceProfileId coapDeviceProfileId; private DeviceProfileId coapDeviceProfileId;
@Value("${device.connectivity.gateway.image_version:3.7-stable}") @Value("${device.connectivity.gateway.image_version:3.8-stable}")
private String gatewayImageVersion; private String gatewayImageVersion;
@Before @Before
@ -321,9 +321,10 @@ public class DeviceConnectivityControllerTest extends AbstractControllerTest {
"\n" + "\n" +
" # Environment variables\n" + " # Environment variables\n" +
" environment:\n" + " environment:\n" +
" - host=host.docker.internal\n" + " - TB_GW_HOST=host.docker.internal\n" +
" - port=1883\n" + " - TB_GW_PORT=1883\n" +
" - accessToken=" + credentials.getCredentialsId() + "\n" + " - TB_GW_SECURITY_TYPE=accessToken\n" +
" - TB_GW_ACCESS_TOKEN=" + credentials.getCredentialsId() + "\n" +
"\n" + "\n" +
" # Volumes bind\n" + " # Volumes bind\n" +
" volumes:\n" + " volumes:\n" +

18
application/src/test/java/org/thingsboard/server/controller/DeviceProfileControllerTest.java

@ -54,11 +54,13 @@ import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.dao.device.DeviceProfileDao; import org.thingsboard.server.dao.device.DeviceProfileDao;
import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import org.awaitility.Awaitility;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
@ -1028,11 +1030,17 @@ public class DeviceProfileControllerTest extends AbstractControllerTest {
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false); MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = this.createMqttDeviceProfileTransportConfiguration(protoTransportPayloadConfiguration, false);
DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration); DeviceProfile deviceProfile = this.createDeviceProfile("Device Profile", mqttDeviceProfileTransportConfiguration);
Mockito.reset(tbClusterService, auditLogService); // The request may hit a transient TenantNotFoundException right after the @Before tenant creation
// if the tenant profile cache is not yet warmed up for the newly created tenant. Retry until the
doPost("/api/deviceProfile", deviceProfile) // request returns the expected 400 Bad Request for the invalid schema. Mockito.reset is inside the
.andExpect(status().isBadRequest()) // retry loop so the subsequent verify* assertions see only the invocations from the last attempt.
.andExpect(statusReason(containsString(errorMsg))); Awaitility.await().atMost(10, TimeUnit.SECONDS).pollInterval(500, TimeUnit.MILLISECONDS)
.ignoreExceptions().untilAsserted(() -> {
Mockito.reset(tbClusterService, auditLogService);
doPost("/api/deviceProfile", deviceProfile)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString(errorMsg)));
});
testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(), testNotifyEntityEqualsOneTimeServiceNeverError(deviceProfile, savedTenant.getId(),
tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg)); tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg));

7
application/src/test/java/org/thingsboard/server/edge/UserEdgeTest.java

@ -57,7 +57,12 @@ public class UserEdgeTest extends AbstractEdgeTest {
User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); User savedTenantAdmin = createUser(newTenantAdmin, "tenant");
Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user) Assert.assertTrue(edgeImitator.waitForMessages()); // wait 3 messages - x1 user update msg and x2 user credentials update msgs (create + authenticate user)
Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size()); Assert.assertEquals(1, edgeImitator.findAllMessagesByType(UserUpdateMsg.class).size());
Assert.assertEquals(2, edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size()); // The initial USER ADDED edge event may bundle a UserCredentialsUpdateMsg when
// user activation completes before the event is processed, in addition to the 2
// messages from the CREDENTIALS_UPDATED events fired during activation. Accept 2 or 3.
int credMsgCount = edgeImitator.findAllMessagesByType(UserCredentialsUpdateMsg.class).size();
Assert.assertTrue("Expected 2 or 3 UserCredentialsUpdateMsg (ADDED/activation race), got " + credMsgCount,
credMsgCount == 2 || credMsgCount == 3);
Optional<UserUpdateMsg> userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); Optional<UserUpdateMsg> userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class);
Assert.assertTrue(userUpdateMsgOpt.isPresent()); Assert.assertTrue(userUpdateMsgOpt.isPresent());
UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get();

9
application/src/test/java/org/thingsboard/server/service/resource/DefaultResourceDataCacheTest.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.resource; package org.thingsboard.server.service.resource;
import org.awaitility.Awaitility;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean; import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
@ -29,6 +30,8 @@ import org.thingsboard.server.dao.resource.ResourceService;
import org.thingsboard.server.dao.resource.TbResourceDataCache; import org.thingsboard.server.dao.resource.TbResourceDataCache;
import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.service.DaoSqlTest;
import java.util.concurrent.TimeUnit;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.timeout;
@ -61,6 +64,8 @@ public class DefaultResourceDataCacheTest extends AbstractControllerTest {
TbResourceInfo savedResource = tbResourceService.save(resource); TbResourceInfo savedResource = tbResourceService.save(resource);
verify(resourceDataCache, timeout(2000).times(1)).evictResourceData(tenantId, savedResource.getId()); verify(resourceDataCache, timeout(2000).times(1)).evictResourceData(tenantId, savedResource.getId());
Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() ->
assertThat(resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get()).isNotNull());
TbResourceDataInfo cachedData = resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get(); TbResourceDataInfo cachedData = resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get();
assertThat(cachedData.getData()).isEqualTo(data); assertThat(cachedData.getData()).isEqualTo(data);
assertThat(JacksonUtil.treeToValue(cachedData.getDescriptor(), GeneralFileDescriptor.class)).isEqualTo(descriptor); assertThat(JacksonUtil.treeToValue(cachedData.getDescriptor(), GeneralFileDescriptor.class)).isEqualTo(descriptor);
@ -76,8 +81,8 @@ public class DefaultResourceDataCacheTest extends AbstractControllerTest {
TbResource resourceById = resourceService.findResourceById(tenantId, savedResource.getId()); TbResource resourceById = resourceService.findResourceById(tenantId, savedResource.getId());
tbResourceService.delete(resourceById, true, null); tbResourceService.delete(resourceById, true, null);
verify(resourceDataCache, timeout(2000).times(2)).evictResourceData(tenantId, savedResource.getId()); verify(resourceDataCache, timeout(2000).times(2)).evictResourceData(tenantId, savedResource.getId());
TbResourceDataInfo cachedDataAfterDeletion = resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get(); Awaitility.await().atMost(2, TimeUnit.SECONDS).untilAsserted(() ->
assertThat(cachedDataAfterDeletion).isEqualTo(null); assertThat(resourceDataCache.getResourceDataInfoAsync(tenantId, savedResource.getId()).get()).isNull());
} }
} }

5
common/edqs/src/main/java/org/thingsboard/server/edqs/data/dp/StringDataPoint.java

@ -33,6 +33,11 @@ public class StringDataPoint extends AbstractDataPoint {
this.value = deduplicate ? TbStringPool.intern(value) : value; this.value = deduplicate ? TbStringPool.intern(value) : value;
} }
@Override
public boolean getBool() {
return Boolean.parseBoolean(value);
}
@Override @Override
public double getDouble() { public double getDouble() {
return Double.parseDouble(value); return Double.parseDouble(value);

2
dao/src/main/java/org/thingsboard/server/dao/device/DeviceConnectivityServiceImpl.java

@ -86,7 +86,7 @@ public class DeviceConnectivityServiceImpl implements DeviceConnectivityService
private String mqttsPemCertFile; private String mqttsPemCertFile;
@Value("${device.connectivity.coaps.pem_cert_file:}") @Value("${device.connectivity.coaps.pem_cert_file:}")
private String coapsPemCertFile; private String coapsPemCertFile;
@Value("${device.connectivity.gateway.image_version:3.7-stable}") @Value("${device.connectivity.gateway.image_version:3.8-stable}")
private String gatewayImageVersion; private String gatewayImageVersion;
@Override @Override

14
dao/src/main/java/org/thingsboard/server/dao/util/DeviceConnectivityUtil.java

@ -124,24 +124,26 @@ public class DeviceConnectivityUtil {
dockerComposeBuilder.append("\n"); dockerComposeBuilder.append("\n");
dockerComposeBuilder.append(" # Environment variables\n"); dockerComposeBuilder.append(" # Environment variables\n");
dockerComposeBuilder.append(" environment:\n"); dockerComposeBuilder.append(" environment:\n");
dockerComposeBuilder.append(" - host=").append(isLocalhost(host) ? HOST_DOCKER_INTERNAL : host).append("\n"); dockerComposeBuilder.append(" - TB_GW_HOST=").append(isLocalhost(host) ? HOST_DOCKER_INTERNAL : host).append("\n");
dockerComposeBuilder.append(" - port=1883\n"); dockerComposeBuilder.append(" - TB_GW_PORT=1883\n");
switch (deviceCredentials.getCredentialsType()) { switch (deviceCredentials.getCredentialsType()) {
case ACCESS_TOKEN: case ACCESS_TOKEN:
dockerComposeBuilder.append(" - accessToken=").append(deviceCredentials.getCredentialsId()).append("\n"); dockerComposeBuilder.append(" - TB_GW_SECURITY_TYPE=accessToken\n");
dockerComposeBuilder.append(" - TB_GW_ACCESS_TOKEN=").append(deviceCredentials.getCredentialsId()).append("\n");
break; break;
case MQTT_BASIC: case MQTT_BASIC:
dockerComposeBuilder.append(" - TB_GW_SECURITY_TYPE=usernamePassword\n");
BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(), BasicMqttCredentials credentials = JacksonUtil.fromString(deviceCredentials.getCredentialsValue(),
BasicMqttCredentials.class); BasicMqttCredentials.class);
if (credentials != null) { if (credentials != null) {
if (StringUtils.isNotEmpty(credentials.getClientId())) { if (StringUtils.isNotEmpty(credentials.getClientId())) {
dockerComposeBuilder.append(" - clientId=").append(credentials.getClientId()).append("\n"); dockerComposeBuilder.append(" - TB_GW_CLIENT_ID=").append(credentials.getClientId()).append("\n");
} }
if (StringUtils.isNotEmpty(credentials.getUserName())) { if (StringUtils.isNotEmpty(credentials.getUserName())) {
dockerComposeBuilder.append(" - username=").append(credentials.getUserName()).append("\n"); dockerComposeBuilder.append(" - TB_GW_USERNAME=").append(credentials.getUserName()).append("\n");
} }
if (StringUtils.isNotEmpty(credentials.getPassword())) { if (StringUtils.isNotEmpty(credentials.getPassword())) {
dockerComposeBuilder.append(" - password=").append(credentials.getPassword()).append("\n"); dockerComposeBuilder.append(" - TB_GW_PASSWORD=").append(credentials.getPassword()).append("\n");
} }
} }
break; break;

47
dao/src/test/java/org/thingsboard/server/dao/TbTimescaleDBContainerProvider.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2026 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.dao;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.TimescaleDBContainerProvider;
/**
* Extends the upstream {@link TimescaleDBContainerProvider} to disable the
* timescaledb-tune entrypoint script via NO_TS_TUNE=true.
*
* Works around a shell bug in /docker-entrypoint-initdb.d/001_timescaledb_tune.sh
* that crashes the container entrypoint on cgroup v2 hosts (including CI agents)
* when the kernel reports the 64-bit max for memory.max.
*
* Activated by the jdbc:tc:tbtimescaledb:&lt;tag&gt;:///... URL prefix
* registered via META-INF/services.
*/
public class TbTimescaleDBContainerProvider extends TimescaleDBContainerProvider {
private static final String NAME = "tbtimescaledb";
@Override
public boolean supports(String databaseType) {
return NAME.equals(databaseType);
}
@Override
public JdbcDatabaseContainer newInstance(String tag) {
JdbcDatabaseContainer container = super.newInstance(tag);
container.withEnv("NO_TS_TUNE", "true");
return container;
}
}

1
dao/src/test/resources/META-INF/services/org.testcontainers.containers.JdbcDatabaseContainerProvider

@ -0,0 +1 @@
org.thingsboard.server.dao.TbTimescaleDBContainerProvider

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

@ -13,6 +13,6 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres spring.datasource.username=postgres
spring.datasource.password=postgres spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:16.6:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb spring.datasource.url=jdbc:tc:postgresql:18:///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=16 spring.datasource.hikari.maximumPoolSize=16

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

@ -14,7 +14,7 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres spring.datasource.username=postgres
spring.datasource.password=postgres spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:postgresql:16.6:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.PostgreSqlInitializer::initDb spring.datasource.url=jdbc:tc:postgresql:18:///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=16 spring.datasource.hikari.maximumPoolSize=16

2
dao/src/test/resources/timescale-test.properties

@ -13,6 +13,6 @@ spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=none spring.jpa.hibernate.ddl-auto=none
spring.datasource.username=postgres spring.datasource.username=postgres
spring.datasource.password=postgres spring.datasource.password=postgres
spring.datasource.url=jdbc:tc:timescaledb:latest-pg12:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.TimescaleSqlInitializer::initDb spring.datasource.url=jdbc:tc:tbtimescaledb:latest-pg18:///thingsboard?TC_DAEMON=true&TC_TMPFS=/testtmpfs:rw&?TC_INITFUNCTION=org.thingsboard.server.dao.TimescaleSqlInitializer::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

57
edqs/src/test/java/org/thingsboard/server/edqs/repo/DeviceTypeFilterTest.java

@ -19,15 +19,20 @@ 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.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceProfileType;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.edqs.AttributeKv;
import org.thingsboard.server.common.data.edqs.LatestTsKv; import org.thingsboard.server.common.data.edqs.LatestTsKv;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.DeviceTypeFilter; import org.thingsboard.server.common.data.query.DeviceTypeFilter;
import org.thingsboard.server.common.data.query.EntityDataPageLink; import org.thingsboard.server.common.data.query.EntityDataPageLink;
import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityDataQuery;
@ -39,8 +44,10 @@ 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.StringFilterPredicate; import org.thingsboard.server.common.data.query.StringFilterPredicate;
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.UUID; import java.util.UUID;
public class DeviceTypeFilterTest extends AbstractEDQTest { public class DeviceTypeFilterTest extends AbstractEDQTest {
@ -119,7 +126,50 @@ public class DeviceTypeFilterTest extends AbstractEDQTest {
Assert.assertEquals("42", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("createdTime").getValue()); Assert.assertEquals("42", first.getLatest().get(EntityKeyType.ENTITY_FIELD).get("createdTime").getValue());
} }
@Test
public void testFindDeviceByBooleanAttributeWithMixedTypes() {
DeviceId device1Id = createLoraDevice("LoRa-1");
DeviceId device2Id = createLoraDevice("LoRa-2");
DeviceId device3Id = createLoraDevice("LoRa-3");
long ts = System.currentTimeMillis();
addOrUpdate(new AttributeKv(device1Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new BooleanDataEntry("active", true), ts), 1L));
addOrUpdate(new AttributeKv(device2Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new BooleanDataEntry("active", false), ts), 1L));
addOrUpdate(new AttributeKv(device3Id, AttributeScope.SERVER_SCOPE,
new BaseAttributeKvEntry(new StringDataEntry("active", "true"), ts), 1L));
KeyFilter activeFilter = new KeyFilter();
activeFilter.setKey(new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, "active"));
activeFilter.setValueType(EntityKeyValueType.BOOLEAN);
BooleanFilterPredicate predicate = new BooleanFilterPredicate();
predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL);
predicate.setValue(FilterPredicateValue.fromBoolean(true));
activeFilter.setPredicate(predicate);
var result = repository.countEntitiesByQuery(tenantId, null,
getDeviceTypeQuery("LoRa", List.of(activeFilter)), false);
Assert.assertEquals(2, result);
}
private DeviceId createLoraDevice(String name) {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
Device device = new Device();
device.setId(deviceId);
device.setTenantId(tenantId);
device.setDeviceProfileId(loraProfileId);
device.setName(name);
device.setCreatedTime(42L);
addOrUpdate(EntityType.DEVICE, device);
return deviceId;
}
private static EntityDataQuery getDeviceTypeQuery(String deviceType) { private static EntityDataQuery getDeviceTypeQuery(String deviceType) {
return getDeviceTypeQuery(deviceType, null);
}
private static EntityDataQuery getDeviceTypeQuery(String deviceType, List<KeyFilter> extraFilters) {
DeviceTypeFilter filter = new DeviceTypeFilter(); DeviceTypeFilter filter = new DeviceTypeFilter();
filter.setDeviceTypes(Collections.singletonList(deviceType)); filter.setDeviceTypes(Collections.singletonList(deviceType));
var pageLink = new EntityDataPageLink(20, 0, null, new EntityDataSortOrder(new EntityKey(EntityKeyType.TIME_SERIES, "state"), EntityDataSortOrder.Direction.DESC), false); var pageLink = new EntityDataPageLink(20, 0, null, new EntityDataSortOrder(new EntityKey(EntityKeyType.TIME_SERIES, "state"), EntityDataSortOrder.Direction.DESC), false);
@ -135,7 +185,12 @@ public class DeviceTypeFilterTest extends AbstractEDQTest {
nameFilter.setPredicate(predicate); nameFilter.setPredicate(predicate);
nameFilter.setValueType(EntityKeyValueType.STRING); nameFilter.setValueType(EntityKeyValueType.STRING);
return new EntityDataQuery(filter, pageLink, entityFields, latestValues, Arrays.asList(nameFilter)); List<KeyFilter> keyFilters = new ArrayList<>();
keyFilters.add(nameFilter);
if (extraFilters != null) {
keyFilters.addAll(extraFilters);
}
return new EntityDataQuery(filter, pageLink, entityFields, latestValues, keyFilters);
} }
} }

4
pom.xml

@ -619,7 +619,6 @@
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId> <artifactId>maven-assembly-plugin</artifactId>
<configuration> <configuration>
<skipAssembly>${pkg.skip.zip}</skipAssembly>
<finalName>${pkg.name}</finalName> <finalName>${pkg.name}</finalName>
<descriptors> <descriptors>
<descriptor>${main.dir}/packaging/${pkg.type}/assembly/windows.xml</descriptor> <descriptor>${main.dir}/packaging/${pkg.type}/assembly/windows.xml</descriptor>
@ -632,6 +631,9 @@
<goals> <goals>
<goal>single</goal> <goal>single</goal>
</goals> </goals>
<configuration>
<skipAssembly>${pkg.skip.zip}</skipAssembly>
</configuration>
</execution> </execution>
</executions> </executions>
</plugin> </plugin>

Loading…
Cancel
Save