|
|
|
@ -28,18 +28,24 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.Argument; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.ArgumentType; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingEvent; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingZoneGroupConfiguration; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.Output; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.OutputType; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.RelationQueryDynamicSourceConfiguration; |
|
|
|
import org.thingsboard.server.common.data.cf.configuration.SimpleCalculatedFieldConfiguration; |
|
|
|
import org.thingsboard.server.common.data.id.CalculatedFieldId; |
|
|
|
import org.thingsboard.server.common.data.id.EntityId; |
|
|
|
import org.thingsboard.server.common.data.relation.EntityRelation; |
|
|
|
import org.thingsboard.server.common.data.relation.EntitySearchDirection; |
|
|
|
import org.thingsboard.server.dao.cf.CalculatedFieldService; |
|
|
|
import org.thingsboard.server.dao.device.DeviceService; |
|
|
|
import org.thingsboard.server.dao.exception.DataValidationException; |
|
|
|
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; |
|
|
|
|
|
|
|
import java.util.Arrays; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.UUID; |
|
|
|
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat; |
|
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
|
|
|
@ -51,6 +57,8 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest { |
|
|
|
private CalculatedFieldService calculatedFieldService; |
|
|
|
@Autowired |
|
|
|
private DeviceService deviceService; |
|
|
|
@Autowired |
|
|
|
private TbTenantProfileCache tbTenantProfileCache; |
|
|
|
|
|
|
|
private ListeningExecutorService executor; |
|
|
|
|
|
|
|
@ -90,6 +98,187 @@ public class CalculatedFieldServiceTest extends AbstractServiceTest { |
|
|
|
calculatedFieldService.deleteCalculatedField(tenantId, savedCalculatedField.getId()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void testSaveGeofencingCalculatedField_shouldNotChangeScheduledInterval() { |
|
|
|
// Arrange a device
|
|
|
|
Device device = createTestDevice(); |
|
|
|
|
|
|
|
// Build a valid Geofencing configuration
|
|
|
|
GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration(); |
|
|
|
|
|
|
|
// Coordinates: TS_LATEST, no dynamic source
|
|
|
|
Argument lat = new Argument(); |
|
|
|
lat.setRefEntityId(device.getId()); |
|
|
|
lat.setRefEntityKey(new ReferencedEntityKey("latitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
Argument lon = new Argument(); |
|
|
|
lon.setRefEntityId(device.getId()); |
|
|
|
lon.setRefEntityKey(new ReferencedEntityKey("longitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
// Zone-group argument (ATTRIBUTE) — no DYNAMIC configuration, so no scheduling even if the scheduled interval is set
|
|
|
|
Argument allowed = new Argument(); |
|
|
|
lat.setRefEntityId(device.getId()); |
|
|
|
allowed.setRefEntityKey(new ReferencedEntityKey("allowed", ArgumentType.ATTRIBUTE, null)); |
|
|
|
|
|
|
|
cfg.setArguments(Map.of( |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY, lat, |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY, lon, |
|
|
|
"allowed", allowed |
|
|
|
)); |
|
|
|
|
|
|
|
// Matching zone-group configuration
|
|
|
|
var geofencingZoneGroupConfiguration = new GeofencingZoneGroupConfiguration("gf_allowed", Arrays.asList(GeofencingEvent.values())); |
|
|
|
cfg.setZoneGroupConfigurations(Map.of("allowed", geofencingZoneGroupConfiguration)); |
|
|
|
|
|
|
|
// Set a scheduled interval to some value
|
|
|
|
cfg.setScheduledUpdateIntervalSec(600); |
|
|
|
|
|
|
|
// Create & save Calculated Field
|
|
|
|
CalculatedField cf = new CalculatedField(); |
|
|
|
cf.setTenantId(tenantId); |
|
|
|
cf.setEntityId(device.getId()); |
|
|
|
cf.setType(CalculatedFieldType.GEOFENCING); |
|
|
|
cf.setName("GF clamp test"); |
|
|
|
cf.setConfigurationVersion(0); |
|
|
|
cf.setConfiguration(cfg); |
|
|
|
|
|
|
|
CalculatedField saved = calculatedFieldService.save(cf); |
|
|
|
|
|
|
|
// Assert: the interval is saved, but scheduling is not enabled
|
|
|
|
int savedInterval = saved.getConfiguration().getScheduledUpdateIntervalSec(); |
|
|
|
boolean scheduledUpdateEnabled = saved.getConfiguration().isScheduledUpdateEnabled(); |
|
|
|
|
|
|
|
assertThat(savedInterval).isEqualTo(600); |
|
|
|
assertThat(scheduledUpdateEnabled).isFalse(); |
|
|
|
|
|
|
|
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void testSaveGeofencingCalculatedField_shouldClampScheduledIntervalToTenantMin() { |
|
|
|
// Arrange a device
|
|
|
|
Device device = createTestDevice(); |
|
|
|
|
|
|
|
// Build a valid Geofencing configuration
|
|
|
|
GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration(); |
|
|
|
|
|
|
|
// Coordinates: TS_LATEST, no dynamic source
|
|
|
|
Argument lat = new Argument(); |
|
|
|
lat.setRefEntityId(device.getId()); |
|
|
|
lat.setRefEntityKey(new ReferencedEntityKey("latitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
Argument lon = new Argument(); |
|
|
|
lon.setRefEntityId(device.getId()); |
|
|
|
lon.setRefEntityKey(new ReferencedEntityKey("longitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
// Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
|
|
|
|
Argument allowed = new Argument(); |
|
|
|
allowed.setRefEntityKey(new ReferencedEntityKey("allowed", ArgumentType.ATTRIBUTE, null)); |
|
|
|
var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); |
|
|
|
dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); |
|
|
|
dynamicSourceConfiguration.setMaxLevel(1); |
|
|
|
dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); |
|
|
|
allowed.setRefDynamicSourceConfiguration(dynamicSourceConfiguration); |
|
|
|
|
|
|
|
cfg.setArguments(Map.of( |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY, lat, |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY, lon, |
|
|
|
"allowed", allowed |
|
|
|
)); |
|
|
|
|
|
|
|
// Matching zone-group configuration
|
|
|
|
var geofencingZoneGroupConfiguration = new GeofencingZoneGroupConfiguration("gf_allowed", Arrays.asList(GeofencingEvent.values())); |
|
|
|
cfg.setZoneGroupConfigurations(Map.of("allowed", geofencingZoneGroupConfiguration)); |
|
|
|
|
|
|
|
// Enable scheduling with an interval below tenant min
|
|
|
|
cfg.setScheduledUpdateIntervalSec(600); |
|
|
|
|
|
|
|
// Create & save Calculated Field
|
|
|
|
CalculatedField cf = new CalculatedField(); |
|
|
|
cf.setTenantId(tenantId); |
|
|
|
cf.setEntityId(device.getId()); |
|
|
|
cf.setType(CalculatedFieldType.GEOFENCING); |
|
|
|
cf.setName("GF clamp test"); |
|
|
|
cf.setConfigurationVersion(0); |
|
|
|
cf.setConfiguration(cfg); |
|
|
|
|
|
|
|
CalculatedField saved = calculatedFieldService.save(cf); |
|
|
|
|
|
|
|
// Assert: the interval is clamped up to tenant profile min
|
|
|
|
int savedInterval = saved.getConfiguration().getScheduledUpdateIntervalSec(); |
|
|
|
|
|
|
|
int min = tbTenantProfileCache.get(tenantId) |
|
|
|
.getDefaultProfileConfiguration() |
|
|
|
.getMinAllowedScheduledUpdateIntervalInSecForCF(); |
|
|
|
assertThat(savedInterval).isEqualTo(min); |
|
|
|
|
|
|
|
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void testSaveGeofencingCalculatedField_shouldUseScheduledIntervalFromConfig() { |
|
|
|
// Arrange a device
|
|
|
|
Device device = createTestDevice(); |
|
|
|
|
|
|
|
// Build a valid Geofencing configuration
|
|
|
|
GeofencingCalculatedFieldConfiguration cfg = new GeofencingCalculatedFieldConfiguration(); |
|
|
|
|
|
|
|
// Coordinates: TS_LATEST, no dynamic source
|
|
|
|
Argument lat = new Argument(); |
|
|
|
lat.setRefEntityId(device.getId()); |
|
|
|
lat.setRefEntityKey(new ReferencedEntityKey("latitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
Argument lon = new Argument(); |
|
|
|
lon.setRefEntityId(device.getId()); |
|
|
|
lon.setRefEntityKey(new ReferencedEntityKey("longitude", ArgumentType.TS_LATEST, null)); |
|
|
|
|
|
|
|
// Zone-group argument (ATTRIBUTE) — make it DYNAMIC so scheduling is enabled
|
|
|
|
Argument allowed = new Argument(); |
|
|
|
allowed.setRefEntityKey(new ReferencedEntityKey("allowed", ArgumentType.ATTRIBUTE, null)); |
|
|
|
var dynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration(); |
|
|
|
dynamicSourceConfiguration.setDirection(EntitySearchDirection.FROM); |
|
|
|
dynamicSourceConfiguration.setMaxLevel(1); |
|
|
|
dynamicSourceConfiguration.setRelationType(EntityRelation.CONTAINS_TYPE); |
|
|
|
allowed.setRefDynamicSourceConfiguration(dynamicSourceConfiguration); |
|
|
|
|
|
|
|
cfg.setArguments(Map.of( |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY, lat, |
|
|
|
GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY, lon, |
|
|
|
"allowed", allowed |
|
|
|
)); |
|
|
|
|
|
|
|
// Matching zone-group configuration
|
|
|
|
var geofencingZoneGroupConfiguration = new GeofencingZoneGroupConfiguration("gf_allowed", Arrays.asList(GeofencingEvent.values())); |
|
|
|
cfg.setZoneGroupConfigurations(Map.of("allowed", geofencingZoneGroupConfiguration)); |
|
|
|
|
|
|
|
// Get tenant profile min.
|
|
|
|
int min = tbTenantProfileCache.get(tenantId) |
|
|
|
.getDefaultProfileConfiguration() |
|
|
|
.getMinAllowedScheduledUpdateIntervalInSecForCF(); |
|
|
|
|
|
|
|
|
|
|
|
// Enable scheduling with an interval greater than tenant min
|
|
|
|
int valueFromConfig = min + 100; |
|
|
|
cfg.setScheduledUpdateIntervalSec(valueFromConfig); |
|
|
|
|
|
|
|
// Create & save Calculated Field
|
|
|
|
CalculatedField cf = new CalculatedField(); |
|
|
|
cf.setTenantId(tenantId); |
|
|
|
cf.setEntityId(device.getId()); |
|
|
|
cf.setType(CalculatedFieldType.GEOFENCING); |
|
|
|
cf.setName("GF no clamp test"); |
|
|
|
cf.setConfigurationVersion(0); |
|
|
|
cf.setConfiguration(cfg); |
|
|
|
|
|
|
|
CalculatedField saved = calculatedFieldService.save(cf); |
|
|
|
|
|
|
|
// Assert: the interval is clamped up to tenant profile min (or stays >= original if already >= min)
|
|
|
|
int savedInterval = saved.getConfiguration().getScheduledUpdateIntervalSec(); |
|
|
|
assertThat(savedInterval).isEqualTo(valueFromConfig); |
|
|
|
|
|
|
|
calculatedFieldService.deleteCalculatedField(tenantId, saved.getId()); |
|
|
|
} |
|
|
|
|
|
|
|
@Test |
|
|
|
public void testSaveCalculatedFieldWithExistingName() { |
|
|
|
Device device = createTestDevice(); |
|
|
|
|