Browse Source

Merge pull request #14827 from dskarzh/related-agg-cf/dx-improvements

Improvements to scheduled update interval handling in related entities aggregation CF
pull/14999/head
Viacheslav Klimov 8 months ago
committed by GitHub
parent
commit
bbd3a1a438
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java
  2. 8
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfiguration.java
  3. 2
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java
  4. 5
      common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java
  5. 2
      common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java
  6. 4
      common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfigurationTest.java
  7. 14
      common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfigurationTest.java
  8. 14
      dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java
  9. 4
      dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
  10. 232
      dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java
  11. 56
      dao/src/test/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidatorTest.java

2
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/RelatedEntitiesAggregationCalculatedFieldStateTest.java

@ -234,6 +234,8 @@ public class RelatedEntitiesAggregationCalculatedFieldStateTest {
config.setUseLatestTs(true);
config.setScheduledUpdateInterval(10);
calculatedField.setConfiguration(config);
calculatedField.setVersion(1L);
return calculatedField;

8
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfiguration.java

@ -22,14 +22,14 @@ public interface ScheduledUpdateSupportedCalculatedFieldConfiguration extends Ca
boolean isScheduledUpdateEnabled();
@PositiveOrZero
int getScheduledUpdateInterval();
Integer getScheduledUpdateInterval();
void setScheduledUpdateInterval(int interval);
void setScheduledUpdateInterval(Integer interval);
default void validate(long minAllowedScheduledUpdateInterval) {
if (getScheduledUpdateInterval() < minAllowedScheduledUpdateInterval) {
throw new IllegalArgumentException("Scheduled update interval is less than configured " +
"minimum allowed interval in tenant profile: " + minAllowedScheduledUpdateInterval);
throw new IllegalArgumentException("Scheduled update interval (" + getScheduledUpdateInterval() +
" seconds) is less than minimum allowed interval in tenant profile: " + minAllowedScheduledUpdateInterval + " seconds");
}
}
}

2
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/aggregation/RelatedEntitiesAggregationCalculatedFieldConfiguration.java

@ -45,7 +45,7 @@ public class RelatedEntitiesAggregationCalculatedFieldConfiguration implements A
private Output output;
private boolean useLatestTs;
private int scheduledUpdateInterval;
private Integer scheduledUpdateInterval;
@Override
public CalculatedFieldType getType() {

5
common/data/src/main/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfiguration.java

@ -48,7 +48,7 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
private Map<String, ZoneGroupConfiguration> zoneGroups;
private boolean scheduledUpdateEnabled;
private int scheduledUpdateInterval;
private Integer scheduledUpdateInterval;
@NotNull
private Output output;
@ -88,6 +88,9 @@ public class GeofencingCalculatedFieldConfiguration implements ArgumentsBasedCal
@Override
public void validate() {
if (scheduledUpdateEnabled && scheduledUpdateInterval == null) {
throw new IllegalArgumentException("Refresh interval is required when periodic zone group refresh is enabled.");
}
zoneGroups.forEach((key, value) -> value.validate(key));
}

2
common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.tenant.profile;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
@ -173,6 +174,7 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura
@Schema(example = "10")
private long maxArgumentsPerCF = 10;
@Schema(example = "10")
@PositiveOrZero
private int minAllowedScheduledUpdateIntervalInSecForCF = 10;
@Builder.Default
@Schema(example = "2")

4
common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/ScheduledUpdateSupportedCalculatedFieldConfigurationTest.java

@ -47,8 +47,8 @@ public class ScheduledUpdateSupportedCalculatedFieldConfigurationTest {
assertThatThrownBy(() -> cfg.validate(minAllowedInterval))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Scheduled update interval is less than configured " +
"minimum allowed interval in tenant profile: " + minAllowedInterval);
.hasMessage("Scheduled update interval (1 seconds) is less than " +
"minimum allowed interval in tenant profile: " + minAllowedInterval + " seconds");
}
}

14
common/data/src/test/java/org/thingsboard/server/common/data/cf/configuration/geofencing/GeofencingCalculatedFieldConfigurationTest.java

@ -28,6 +28,7 @@ import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
@ -103,4 +104,17 @@ public class GeofencingCalculatedFieldConfigurationTest {
assertThat(allowedZonesArgument.getRefEntityKey()).isEqualTo(new ReferencedEntityKey("perimeter", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE));
}
@Test
void validateShouldThrowWhenScheduledUpdateEnabledButIntervalNotSet() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setEntityCoordinates(mock(EntityCoordinates.class));
cfg.setZoneGroups(Map.of("zone", mock(ZoneGroupConfiguration.class)));
cfg.setScheduledUpdateEnabled(true);
cfg.setScheduledUpdateInterval(null);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Refresh interval is required when periodic zone group refresh is enabled.");
}
}

14
dao/src/main/java/org/thingsboard/server/dao/cf/BaseCalculatedFieldService.java

@ -26,18 +26,21 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldFilter;
import org.thingsboard.server.common.data.cf.CalculatedFieldInfo;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.id.CalculatedFieldId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.HasId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.validator.CalculatedFieldDataValidator;
import org.thingsboard.server.dao.usagerecord.ApiLimitService;
import java.util.EnumSet;
import java.util.List;
@ -62,6 +65,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
private final EntityService entityService;
private final CalculatedFieldDao calculatedFieldDao;
private final CalculatedFieldDataValidator calculatedFieldDataValidator;
private final ApiLimitService apiLimitService;
@Override
public CalculatedField save(CalculatedField calculatedField) {
@ -70,6 +74,7 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
@Override
public CalculatedField save(CalculatedField calculatedField, boolean doValidate) {
setConfigurationDefaults(calculatedField);
CalculatedField oldCalculatedField = null;
if (doValidate) {
oldCalculatedField = calculatedFieldDataValidator.validate(calculatedField, CalculatedField::getTenantId);
@ -79,6 +84,15 @@ public class BaseCalculatedFieldService extends AbstractEntityService implements
return doSave(calculatedField, oldCalculatedField);
}
private void setConfigurationDefaults(CalculatedField calculatedField) {
if (calculatedField.getConfiguration() instanceof RelatedEntitiesAggregationCalculatedFieldConfiguration config
&& config.getScheduledUpdateInterval() == null) {
int minScheduledUpdateInterval = (int) apiLimitService.getLimit(
calculatedField.getTenantId(), DefaultTenantProfileConfiguration::getMinAllowedScheduledUpdateIntervalInSecForCF
);
config.setScheduledUpdateInterval(minScheduledUpdateInterval);
}
}
private CalculatedField doSave(CalculatedField calculatedField, CalculatedField oldCalculatedField) {
try {

4
dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java

@ -93,11 +93,13 @@ public abstract class AbstractServiceTest {
@Autowired
protected EntityServiceRegistry entityServiceRegistry;
protected Tenant tenant;
protected TenantId tenantId;
@Before
public void beforeAbstractService() {
tenantId = createTenant().getId();
tenant = createTenant();
tenantId = tenant.getId();
}
@After

232
dao/src/test/java/org/thingsboard/server/dao/service/CalculatedFieldServiceTest.java

@ -15,10 +15,12 @@
*/
package org.thingsboard.server.dao.service;
import org.apache.commons.lang3.RandomUtils;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.thingsboard.server.common.data.AttributeScope;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.cf.CalculatedField;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.Argument;
@ -29,6 +31,10 @@ import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
import org.thingsboard.server.common.data.cf.configuration.RelationPathQueryDynamicSourceConfiguration;
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.TimeSeriesOutput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggFunction;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggKeyInput;
import org.thingsboard.server.common.data.cf.configuration.aggregation.AggMetric;
import org.thingsboard.server.common.data.cf.configuration.aggregation.RelatedEntitiesAggregationCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.EntityCoordinates;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
@ -39,6 +45,7 @@ import org.thingsboard.server.common.data.relation.RelationPathLevel;
import org.thingsboard.server.dao.cf.CalculatedFieldService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.exception.DataValidationException;
import java.util.ArrayList;
@ -59,6 +66,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
private DeviceService deviceService;
@Autowired
private TbTenantProfileCache tbTenantProfileCache;
@Autowired
private TenantProfileService tenantProfileService;
@Test
public void testSaveCalculatedField() {
@ -82,8 +91,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
assertThat(updatedCalculatedField.getName()).isEqualTo(savedCalculatedField.getName());
assertThat(updatedCalculatedField.getVersion()).isEqualTo(savedCalculatedField.getVersion() + 1);
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
}
@Test
@ -113,11 +120,11 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
int min = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int valueFromConfig = min - 10;
// Enable scheduling with an interval below tenant min
cfg.setScheduledUpdateEnabled(true);
cfg.setScheduledUpdateInterval(valueFromConfig);
int invalidInterval = RandomUtils.insecure().randomInt(1, min);
cfg.setScheduledUpdateInterval(invalidInterval);
// Create & save Calculated Field
CalculatedField cf = new CalculatedField();
@ -131,8 +138,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
assertThatThrownBy(() -> calculatedFieldService.save(cf))
.isInstanceOf(DataValidationException.class)
.hasCauseInstanceOf(IllegalArgumentException.class)
.hasMessageStartingWith("Scheduled update interval is less than configured " +
"minimum allowed interval in tenant profile: ");
.hasMessage("Scheduled update interval (" + invalidInterval +
" seconds) is less than minimum allowed interval in tenant profile: " + min + " seconds");
}
@Test
@ -233,8 +240,67 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
int savedInterval = geofencingCalculatedFieldConfiguration.getScheduledUpdateInterval();
assertThat(savedInterval).isEqualTo(valueFromConfig);
}
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId());
@Test
public void testSaveGeofencingCalculatedField_shouldAcceptZeroScheduledUpdateIntervalWhenTenantProfileAllows() {
// GIVEN
var device = createTestDevice();
// Store original value and update tenant profile to allow 0 as min scheduled update interval
TenantProfile tenantProfile = tenantProfileService.findTenantProfileById(tenantId, tenant.getTenantProfileId());
int originalMinScheduledUpdateInterval = tenantProfile.getDefaultProfileConfiguration().getMinAllowedScheduledUpdateIntervalInSecForCF();
tenantProfile.getDefaultProfileConfiguration().setMinAllowedScheduledUpdateIntervalInSecForCF(0);
tenantProfileService.saveTenantProfile(tenantId, tenantProfile);
tbTenantProfileCache.evict(tenantProfile.getId());
try {
// Build a valid Geofencing configuration
var cfg = new GeofencingCalculatedFieldConfiguration();
// Coordinates: TS_LATEST, no dynamic source
var entityCoordinates = new EntityCoordinates("latitude", "longitude");
cfg.setEntityCoordinates(entityCoordinates);
// Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
var zoneGroupConfiguration = new ZoneGroupConfiguration("allowed", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, false);
var dynamicSourceConfiguration = new RelationPathQueryDynamicSourceConfiguration();
dynamicSourceConfiguration.setLevels(List.of(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE)));
zoneGroupConfiguration.setRefDynamicSourceConfiguration(dynamicSourceConfiguration);
cfg.setZoneGroups(Map.of("allowed", zoneGroupConfiguration));
// Enable scheduling with interval = 0
cfg.setScheduledUpdateEnabled(true);
cfg.setScheduledUpdateInterval(0);
// Create Calculated Field
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.GEOFENCING);
cf.setName("GF zero scheduled update interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
var out = new AttributesOutput();
out.setScope(AttributeScope.SERVER_SCOPE);
cfg.setOutput(out);
// WHEN
CalculatedField saved = calculatedFieldService.save(cf);
// THEN
assertThat(saved).isNotNull();
assertThat(saved.getConfiguration()).isInstanceOf(GeofencingCalculatedFieldConfiguration.class);
var savedConfig = (GeofencingCalculatedFieldConfiguration) saved.getConfiguration();
assertThat(savedConfig.getScheduledUpdateInterval()).isEqualTo(0);
} finally {
// Restore original tenant profile value
tenantProfile.getProfileConfiguration().orElseThrow().setMinAllowedScheduledUpdateIntervalInSecForCF(originalMinScheduledUpdateInterval);
tenantProfileService.saveTenantProfile(tenantId, tenantProfile);
tbTenantProfileCache.evict(tenantProfile.getId());
}
}
@Test
@ -254,8 +320,6 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
CalculatedField fetchedCalculatedField = calculatedFieldService.findById(tenantId, savedCalculatedField.getId());
assertThat(fetchedCalculatedField).isEqualTo(savedCalculatedField);
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId());
}
@Test
@ -267,6 +331,156 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest {
assertThat(calculatedFieldService.findById(tenantId, savedCalculatedField.getId())).isNull();
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldUseMinScheduledUpdateIntervalFromTenantProfileWhenNotSet() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
// Do NOT set scheduledUpdateInterval - it should default to tenant profile min value
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - default scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN
CalculatedField saved = calculatedFieldService.save(cf);
// THEN
assertThat(saved).isNotNull();
assertThat(saved.getConfiguration()).isInstanceOf(RelatedEntitiesAggregationCalculatedFieldConfiguration.class);
var savedConfig = (RelatedEntitiesAggregationCalculatedFieldConfiguration) saved.getConfiguration();
int expectedMinScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
assertThat(savedConfig.getScheduledUpdateInterval()).isEqualTo(expectedMinScheduledUpdateInterval);
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldThrowWhenScheduledUpdateIntervalLessThanMinAllowed() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
int minScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int invalidInterval = RandomUtils.insecure().randomInt(1, minScheduledUpdateInterval);
cfg.setScheduledUpdateInterval(invalidInterval);
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - invalid scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN-THEN
assertThatThrownBy(() -> calculatedFieldService.save(cf))
.isInstanceOf(DataValidationException.class)
.hasCauseInstanceOf(IllegalArgumentException.class)
.hasMessage("Scheduled update interval (" + invalidInterval +
" seconds) is less than minimum allowed interval in tenant profile: " + minScheduledUpdateInterval + " seconds");
}
@Test
public void testSaveRelatedEntitiesAggregationCF_shouldAcceptValidScheduledUpdateInterval() {
// GIVEN
var device = createTestDevice();
var cfg = new RelatedEntitiesAggregationCalculatedFieldConfiguration();
cfg.setRelation(new RelationPathLevel(EntitySearchDirection.FROM, EntityRelation.CONTAINS_TYPE));
var argument = new Argument();
argument.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null));
cfg.setArguments(Map.of("temp", argument));
var metric = new AggMetric();
metric.setFunction(AggFunction.AVG);
metric.setInput(new AggKeyInput("temp"));
cfg.setMetrics(Map.of("avgTemp", metric));
var output = new TimeSeriesOutput();
output.setName("avgTemperature");
cfg.setOutput(output);
int minDeduplicationInterval = (int) tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedDeduplicationIntervalInSecForCF();
cfg.setDeduplicationIntervalInSec(minDeduplicationInterval);
int minScheduledUpdateInterval = tbTenantProfileCache.get(tenantId)
.getDefaultProfileConfiguration()
.getMinAllowedScheduledUpdateIntervalInSecForCF();
int customScheduledUpdateInterval = minScheduledUpdateInterval + 100;
cfg.setScheduledUpdateInterval(customScheduledUpdateInterval);
var cf = new CalculatedField();
cf.setTenantId(tenantId);
cf.setEntityId(device.getId());
cf.setType(CalculatedFieldType.RELATED_ENTITIES_AGGREGATION);
cf.setName("Related Entities Aggregation CF - valid scheduled interval test");
cf.setConfigurationVersion(0);
cf.setConfiguration(cfg);
// WHEN
CalculatedField saved = calculatedFieldService.save(cf);
// THEN
assertThat(saved).isNotNull();
assertThat(saved.getConfiguration()).isInstanceOf(RelatedEntitiesAggregationCalculatedFieldConfiguration.class);
var savedConfig = (RelatedEntitiesAggregationCalculatedFieldConfiguration) saved.getConfiguration();
assertThat(savedConfig.getScheduledUpdateInterval()).isEqualTo(customScheduledUpdateInterval);
}
private CalculatedField saveValidCalculatedField() {
Device device = createTestDevice();
CalculatedField calculatedField = getCalculatedField(device.getId(), device.getId());

56
dao/src/test/java/org/thingsboard/server/dao/service/validator/TenantProfileDataValidatorTest.java

@ -16,29 +16,35 @@
package org.thingsboard.server.dao.service.validator;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.springframework.test.context.bean.override.mockito.MockitoSpyBean;
import org.thingsboard.server.common.data.TenantProfile;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
import org.thingsboard.server.common.data.tenant.profile.TenantProfileData;
import org.thingsboard.server.dao.tenant.TenantProfileDao;
import org.thingsboard.server.dao.tenant.TenantProfileService;
import org.thingsboard.server.exception.DataValidationException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.verify;
@SpringBootTest(classes = TenantProfileDataValidator.class)
class TenantProfileDataValidatorTest {
@MockBean
@MockitoBean
TenantProfileDao tenantProfileDao;
@MockBean
@MockitoBean
TenantProfileService tenantProfileService;
@SpyBean
@MockitoSpyBean
TenantProfileDataValidator validator;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da"));
@Test
@ -53,4 +59,44 @@ class TenantProfileDataValidatorTest {
verify(validator).validateString("Tenant profile name", tenantProfile.getName());
}
@ParameterizedTest
@ValueSource(ints = {-1, -100, Integer.MIN_VALUE})
void minAllowedScheduledUpdateIntervalInSecForCF_shouldRejectNegativeValues(int value) {
// GIVEN
var config = new DefaultTenantProfileConfiguration();
config.setMinAllowedScheduledUpdateIntervalInSecForCF(value);
var tenantProfileData = new TenantProfileData();
tenantProfileData.setConfiguration(config);
var tenantProfile = new TenantProfile();
tenantProfile.setName("Test");
tenantProfile.setProfileData(tenantProfileData);
// WHEN/THEN
assertThatThrownBy(() -> validator.validate(tenantProfile, __ -> TenantId.SYS_TENANT_ID))
.isInstanceOf(DataValidationException.class)
.hasMessageContaining("minAllowedScheduledUpdateIntervalInSecForCF")
.hasMessageContaining("must be greater than or equal to 0");
}
@ParameterizedTest
@ValueSource(ints = {0, 1, 60, Integer.MAX_VALUE})
void minAllowedScheduledUpdateIntervalInSecForCF_shouldAcceptValidValues(int value) {
// GIVEN
var config = new DefaultTenantProfileConfiguration();
config.setMinAllowedScheduledUpdateIntervalInSecForCF(value);
var tenantProfileData = new TenantProfileData();
tenantProfileData.setConfiguration(config);
var tenantProfile = new TenantProfile();
tenantProfile.setName("Test");
tenantProfile.setProfileData(tenantProfileData);
// WHEN/THEN
assertThatCode(() -> validator.validate(tenantProfile, __ -> TenantId.SYS_TENANT_ID))
.doesNotThrowAnyException();
}
}

Loading…
Cancel
Save