Added geofencing CF configuration test

This commit is contained in:
dshvaika 2025-08-13 12:55:21 +03:00
parent 4e9b5cf927
commit d2b9e1066f
7 changed files with 503 additions and 39 deletions

View File

@ -135,7 +135,7 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
Coordinates entityCoordinates,
GeofencingCalculatedFieldConfiguration configuration) {
var geofencingZoneGroupConfigurations = configuration.getGeofencingZoneGroupConfigurations();
var geofencingZoneGroupConfigurations = configuration.getZoneGroupConfigurations();
Map<EntityId, GeofencingEvent> zoneEventMap = new HashMap<>();
ObjectNode resultNode = JacksonUtil.newObjectNode();
@ -184,7 +184,7 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
Coordinates entityCoordinates,
GeofencingCalculatedFieldConfiguration configuration) {
var geofencingZoneGroupConfigurations = configuration.getGeofencingZoneGroupConfigurations();
var geofencingZoneGroupConfigurations = configuration.getZoneGroupConfigurations();
ObjectNode resultNode = JacksonUtil.newObjectNode();
getGeofencingArguments().forEach((argumentKey, argumentEntry) -> {

View File

@ -32,7 +32,7 @@ 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.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.ZoneGroupConfiguration;
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;
@ -701,10 +701,10 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
// Zone group reporting config
List<GeofencingEvent> reportEvents = Arrays.stream(GeofencingEvent.values()).toList();
GeofencingZoneGroupConfiguration allowedCfg = new GeofencingZoneGroupConfiguration("allowedZone", reportEvents);
GeofencingZoneGroupConfiguration restrictedCfg = new GeofencingZoneGroupConfiguration("restrictedZone", reportEvents);
ZoneGroupConfiguration allowedCfg = new ZoneGroupConfiguration("allowedZone", reportEvents);
ZoneGroupConfiguration restrictedCfg = new ZoneGroupConfiguration("restrictedZone", reportEvents);
cfg.setGeofencingZoneGroupConfigurations(Map.of(
cfg.setZoneGroupConfigurations(Map.of(
"allowedZones", allowedCfg,
"restrictedZones", restrictedCfg
));

View File

@ -30,7 +30,7 @@ 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.ZoneGroupConfiguration;
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;
@ -337,9 +337,9 @@ public class GeofencingCalculatedFieldStateTest {
config.setArguments(Map.of("latitude", argument1, "longitude", argument2, "allowedZones", argument3, "restrictedZones", argument4));
List<GeofencingEvent> reportEvents = Arrays.stream(GeofencingEvent.values()).toList();
GeofencingZoneGroupConfiguration allowedZoneGroupConfiguration = new GeofencingZoneGroupConfiguration("allowedZone", reportEvents);
GeofencingZoneGroupConfiguration restrictedZoneGroupConfiguration = new GeofencingZoneGroupConfiguration("restrictedZone", reportEvents);
config.setGeofencingZoneGroupConfigurations(Map.of("allowedZones", allowedZoneGroupConfiguration, "restrictedZones", restrictedZoneGroupConfiguration));
ZoneGroupConfiguration allowedZoneGroupConfiguration = new ZoneGroupConfiguration("allowedZone", reportEvents);
ZoneGroupConfiguration restrictedZoneGroupConfiguration = new ZoneGroupConfiguration("restrictedZone", reportEvents);
config.setZoneGroupConfigurations(Map.of("allowedZones", allowedZoneGroupConfiguration, "restrictedZones", restrictedZoneGroupConfiguration));
config.setCreateRelationsWithMatchedZones(true);
config.setZoneRelationType("CurrentZone");

View File

@ -30,14 +30,14 @@ import static org.assertj.core.api.Assertions.assertThat;
public class GeofencingZoneStateTest {
private final AssetId ZONE_ID = new AssetId(UUID.fromString("628730fd-d625-417f-9c6d-ae9fe4addbdb"));
private final String POLYGON = """
{"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
""";
private GeofencingZoneState state;
@BeforeEach
void setUp() {
String POLYGON = """
{"type":"POLYGON","polygonsDefinition":"[[50.472000, 30.504000], [50.472000, 30.506000], [50.474000, 30.506000], [50.474000, 30.504000]]"}
""";
state = new GeofencingZoneState(ZONE_ID, new BaseAttributeKvEntry(new JsonDataEntry("zone", POLYGON), 100L, 1L));
}

View File

@ -44,7 +44,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
private boolean createRelationsWithMatchedZones;
private String zoneRelationType;
private EntitySearchDirection zoneRelationDirection;
private Map<String, GeofencingZoneGroupConfiguration> geofencingZoneGroupConfigurations;
private Map<String, ZoneGroupConfiguration> zoneGroupConfigurations;
@Override
public CalculatedFieldType getType() {
@ -60,13 +60,9 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
@Override
public void validate() {
if (arguments == null) {
throw new IllegalArgumentException("Geofencing calculated field arguments are empty!");
}
if (arguments.size() < 3) {
throw new IllegalArgumentException("Geofencing calculated field must contain at least 3 arguments!");
throw new IllegalArgumentException("Geofencing calculated field arguments must be specified!");
}
validateCoordinateArguments();
Map<String, Argument> zoneGroupsArguments = getZoneGroupArguments();
if (zoneGroupsArguments.isEmpty()) {
throw new IllegalArgumentException("Geofencing calculated field must contain at least one geofencing zone group defined!");
@ -81,32 +77,30 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
return;
}
if (StringUtils.isBlank(zoneRelationType)) {
throw new IllegalArgumentException("Zone relation type must be specified when to maintain relations with matched zones!");
throw new IllegalArgumentException("Zone relation type must be specified to create relations with matched zones!");
}
if (zoneRelationDirection == null) {
throw new IllegalArgumentException("Zone relation direction must be specified to maintain relations with matched zones!");
throw new IllegalArgumentException("Zone relation direction must be specified to create relations with matched zones!");
}
}
private void validateZoneGroupConfigurations(Map<String, Argument> zoneGroupsArguments) {
if (geofencingZoneGroupConfigurations == null) {
throw new IllegalArgumentException("Geofencing calculated field zone group configurations are empty!");
if (zoneGroupConfigurations == null || zoneGroupConfigurations.isEmpty()) {
throw new IllegalArgumentException("Zone groups configuration should be specified!");
}
Set<String> usedPrefixes = new HashSet<>();
geofencingZoneGroupConfigurations.forEach((zoneGroupName, config) -> {
Argument zoneGroupArgument = zoneGroupsArguments.get(zoneGroupName);
if (zoneGroupArgument == null) {
throw new IllegalArgumentException("Geofencing calculated field zone group configuration is not configured for zone group: " + zoneGroupName);
}
zoneGroupsArguments.forEach((zoneGroupName, zoneGroupArgument) -> {
ZoneGroupConfiguration config = zoneGroupConfigurations.get(zoneGroupName);
if (config == null) {
throw new IllegalArgumentException("Zone group configuration is not configured for zone group: " + zoneGroupName);
throw new IllegalArgumentException("Zone group configuration is not configured for '" + zoneGroupName + "' argument!");
}
if (CollectionsUtil.isEmpty(config.getReportEvents())) {
throw new IllegalArgumentException("Zone group configuration report events must be specified for zone group: " + zoneGroupName);
throw new IllegalArgumentException("Zone group configuration report events must be specified for '" + zoneGroupName + "' argument!");
}
String prefix = config.getReportTelemetryPrefix();
if (StringUtils.isBlank(prefix)) {
throw new IllegalArgumentException("Report telemetry prefix should be specified for zone group: " + zoneGroupName);
throw new IllegalArgumentException("Report telemetry prefix should be specified for '" + zoneGroupName + "' argument!");
}
if (!usedPrefixes.add(prefix)) {
throw new IllegalArgumentException("Duplicate report telemetry prefix found: '" + prefix + "'. Must be unique!");
@ -118,26 +112,23 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
for (String coordinateKey : coordinateKeys) {
Argument argument = arguments.get(coordinateKey);
if (argument == null) {
throw new IllegalArgumentException("Missing required coordinates argument: " + coordinateKey);
throw new IllegalArgumentException("Missing required coordinates argument: " + coordinateKey + "!");
}
ReferencedEntityKey refEntityKey = validateAndGetRefEntityKey(argument, coordinateKey);
if (!ArgumentType.TS_LATEST.equals(refEntityKey.getType())) {
throw new IllegalArgumentException("Argument '" + coordinateKey + "' must be of type TS_LATEST.");
throw new IllegalArgumentException("Argument '" + coordinateKey + "' must be of type TS_LATEST!");
}
if (argument.hasDynamicSource()) {
throw new IllegalArgumentException("Dynamic source is not allowed for argument: '" + coordinateKey + "'.");
throw new IllegalArgumentException("Dynamic source is not allowed for '" + coordinateKey + "' argument!");
}
}
}
private void validateZoneGroupAruguments(Map<String, Argument> zoneGroupsArguments) {
zoneGroupsArguments.forEach((argumentKey, argument) -> {
if (argument == null) {
throw new IllegalArgumentException("Zone group argument is not configured: " + argumentKey);
}
ReferencedEntityKey refEntityKey = validateAndGetRefEntityKey(argument, argumentKey);
if (!ArgumentType.ATTRIBUTE.equals(refEntityKey.getType())) {
throw new IllegalArgumentException("Argument '" + argumentKey + "' must be of type ATTRIBUTE.");
throw new IllegalArgumentException("Argument '" + argumentKey + "' must be of type ATTRIBUTE!");
}
if (argument.hasDynamicSource()) {
argument.getRefDynamicSourceConfiguration().validate();
@ -148,6 +139,7 @@ public class GeofencingCalculatedFieldConfiguration extends BaseCalculatedFieldC
private Map<String, Argument> getZoneGroupArguments() {
return arguments.entrySet()
.stream()
.filter(entry -> entry.getValue() != null)
.filter(entry -> !coordinateKeys.contains(entry.getKey()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}

View File

@ -20,7 +20,7 @@ import lombok.Data;
import java.util.List;
@Data
public class GeofencingZoneGroupConfiguration {
public class ZoneGroupConfiguration {
private final String reportTelemetryPrefix;
private final List<GeofencingEvent> reportEvents;

View File

@ -0,0 +1,472 @@
/**
* Copyright © 2016-2025 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.common.data.cf.configuration;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.NullAndEmptySource;
import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.junit.jupiter.MockitoExtension;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
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.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LATITUDE_ARGUMENT_KEY;
import static org.thingsboard.server.common.data.cf.configuration.GeofencingCalculatedFieldConfiguration.ENTITY_ID_LONGITUDE_ARGUMENT_KEY;
@ExtendWith(MockitoExtension.class)
public class GeofencingCalculatedFieldConfigurationTest {
@Test
void typeShouldBeGeofencing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
assertThat(cfg.getType()).isEqualTo(CalculatedFieldType.GEOFENCING);
}
@Test
void validateShouldThrowWhenArgumentsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(null);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Geofencing calculated field arguments must be specified!");
}
@Test
void validateShouldThrowWhenLatitudeArgIsMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, null);
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
cfg.setArguments(arguments);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing required coordinates argument: " + ENTITY_ID_LATITUDE_ARGUMENT_KEY + "!");
}
@Test
void validateShouldThrowWhenLongitudeArgIsMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, null);
cfg.setArguments(arguments);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing required coordinates argument: " + ENTITY_ID_LONGITUDE_ARGUMENT_KEY + "!");
}
@Test
void validateShouldThrowWhenLatitudeReferenceKeyIsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument(null),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST))
);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing or invalid reference entity key for argument: " + ENTITY_ID_LATITUDE_ARGUMENT_KEY);
}
@Test
void validateShouldThrowWhenLongitudeReferenceKeyIsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument(null))
);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing or invalid reference entity key for argument: " + ENTITY_ID_LONGITUDE_ARGUMENT_KEY);
}
@Test
void validateShouldThrowWhenLatitudeReferenceKeyTypeIsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument(new ReferencedEntityKey("latitude", null, null)),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST))
);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing or invalid reference entity key for argument: " + ENTITY_ID_LATITUDE_ARGUMENT_KEY);
}
@Test
void validateShouldThrowWhenReferenceKeyTypeIsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument(new ReferencedEntityKey("longitude", null, null)))
);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Missing or invalid reference entity key for argument: " + ENTITY_ID_LONGITUDE_ARGUMENT_KEY);
}
@Test
void validateShouldThrowWhenLatitudeArgHasWrongArgumentType() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.ATTRIBUTE),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST)
));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Argument '" + ENTITY_ID_LATITUDE_ARGUMENT_KEY + "' must be of type TS_LATEST!");
}
@Test
void validateShouldThrowWhenLongitudeArgHasWrongArgumentType() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.ATTRIBUTE)
));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Argument '" + ENTITY_ID_LONGITUDE_ARGUMENT_KEY + "' must be of type TS_LATEST!");
}
@Test
void validateShouldThrowWhenLatitudeArgHasDynamicSource() {
var cfg = new GeofencingCalculatedFieldConfiguration();
Argument latitudeArg = toArgument("latitude", ArgumentType.TS_LATEST);
var refDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
latitudeArg.setRefDynamicSourceConfiguration(refDynamicSourceConfiguration);
Argument longitudeArg = toArgument("longitude", ArgumentType.TS_LATEST);
cfg.setArguments(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArg, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, longitudeArg));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Dynamic source is not allowed for '" + ENTITY_ID_LATITUDE_ARGUMENT_KEY + "' argument!");
}
@Test
void validateShouldThrowWhenLongitudeArgHasDynamicSource() {
var cfg = new GeofencingCalculatedFieldConfiguration();
Argument latitudeArg = toArgument("latitude", ArgumentType.TS_LATEST);
Argument longitudeArg = toArgument("longitude", ArgumentType.TS_LATEST);
var refDynamicSourceConfiguration = new RelationQueryDynamicSourceConfiguration();
longitudeArg.setRefDynamicSourceConfiguration(refDynamicSourceConfiguration);
cfg.setArguments(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArg, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, longitudeArg));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Dynamic source is not allowed for '" + ENTITY_ID_LONGITUDE_ARGUMENT_KEY + "' argument!");
}
@Test
void validateShouldThrowWhenGeofencingArgumentsMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(
ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST),
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST)
));
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Geofencing calculated field must contain at least one geofencing zone group defined!");
}
@Test
void validateShouldThrowWhenZoneGroupArgumentIsNull() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
arguments.put("someZones", null);
cfg.setArguments(arguments);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Geofencing calculated field must contain at least one geofencing zone group defined!");
}
@Test
void validateShouldThrowWhenZoneGroupArgumentHasInvalidArgumentType() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
arguments.put("allowedZones", toArgument("allowedZone", ArgumentType.TS_LATEST));
cfg.setArguments(arguments);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Argument 'allowedZones' must be of type ATTRIBUTE!");
}
@Test
void validateShouldCallDynamicSourceConfigValidationWhenZoneGroupArgumentHasDynamicSourceConfiguration() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("allowedZone", Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.validate();
verify(refDynamicSourceConfigurationMock).validate();
}
@Test
void validateShouldThrowWhenZoneGroupConfigurationIsMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(null);
cfg.setCreateRelationsWithMatchedZones(false);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone groups configuration should be specified!");
}
@Test
void validateShouldThrowWhenReportTelemetryPrefixDuplicate() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
Argument restrictedZonesArg = toArgument("restrictedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
arguments.put("restrictedZones", restrictedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("theSamePrefixTest", Arrays.asList(GeofencingEvent.values()));
ZoneGroupConfiguration restrictedZoneConfiguration = new ZoneGroupConfiguration("theSamePrefixTest", Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration, "restrictedZones", restrictedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(false);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Duplicate report telemetry prefix found: 'theSamePrefixTest'. Must be unique!");
}
@Test
void validateShouldThrowWhenZoneGroupArgumentConfigurationIsMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("allowedZone", Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("someOtherZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(false);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone group configuration is not configured for 'allowedZones' argument!");
}
@Test
void validateShouldThrowWhenZoneGroupConfigurationReportEventsAreNotSpecified() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("allowedZone", null);
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(false);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone group configuration report events must be specified for 'allowedZones' argument!");
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = " ")
void validateShouldThrowWhenZoneGroupConfigurationTelemetryPrefixIsBlankOrNull(String reportTelemetryPrefix) {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration(reportTelemetryPrefix, Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(false);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Report telemetry prefix should be specified for 'allowedZones' argument!");
}
@ParameterizedTest
@NullAndEmptySource
@ValueSource(strings = " ")
void validateShouldThrowWhenHasBlankOrNullZoneRelationType(String zoneRelationType) {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("allowedZone", Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(true);
cfg.setZoneRelationType(zoneRelationType);
cfg.setZoneRelationDirection(EntitySearchDirection.TO);
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone relation type must be specified to create relations with matched zones!");
}
@Test
void validateShouldThrowWhenNoZoneRelationDirectionSpecified() {
var cfg = new GeofencingCalculatedFieldConfiguration();
var arguments = new HashMap<String, Argument>();
arguments.put(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST));
arguments.put(ENTITY_ID_LONGITUDE_ARGUMENT_KEY, toArgument("longitude", ArgumentType.TS_LATEST));
Argument allowedZonesArg = toArgument("allowedZone", ArgumentType.ATTRIBUTE);
var refDynamicSourceConfigurationMock = mock(RelationQueryDynamicSourceConfiguration.class);
allowedZonesArg.setRefDynamicSourceConfiguration(refDynamicSourceConfigurationMock);
arguments.put("allowedZones", allowedZonesArg);
ZoneGroupConfiguration allowedZoneConfiguration = new ZoneGroupConfiguration("allowedZone", Arrays.asList(GeofencingEvent.values()));
Map<String, ZoneGroupConfiguration> zoneGroupConfigurations = Map.of("allowedZones", allowedZoneConfiguration);
cfg.setArguments(arguments);
cfg.setZoneGroupConfigurations(zoneGroupConfigurations);
cfg.setCreateRelationsWithMatchedZones(true);
cfg.setZoneRelationType("SomeRelationType");
assertThatThrownBy(cfg::validate)
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("Zone relation direction must be specified to create relations with matched zones!");
}
@Test
void scheduledUpdateDisabledWhenIntervalIsZero() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setScheduledUpdateIntervalSec(0);
assertThat(cfg.isScheduledUpdateEnabled()).isFalse();
}
@Test
void scheduledUpdateDisabledWhenIntervalIsGreaterThanZeroButArgumentsAreEmpty() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of());
cfg.setScheduledUpdateIntervalSec(60);
assertThat(cfg.isScheduledUpdateEnabled()).isFalse();
}
@Test
void scheduledUpdateDisabledWhenIntervalIsGreaterThanZeroButDynamicArgumentsAreMissing() {
var cfg = new GeofencingCalculatedFieldConfiguration();
cfg.setArguments(Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, toArgument("latitude", ArgumentType.TS_LATEST)));
cfg.setScheduledUpdateIntervalSec(60);
assertThat(cfg.isScheduledUpdateEnabled()).isFalse();
}
@Test
void scheduledUpdateEnabledWhenIntervalIsGreaterThanZeroAndDynamicArgumentsPresent() {
var cfg = new GeofencingCalculatedFieldConfiguration();
Argument someDynamicArgument = toArgument("someDynamicArgument", ArgumentType.ATTRIBUTE);
someDynamicArgument.setRefDynamicSourceConfiguration(new RelationQueryDynamicSourceConfiguration());
cfg.setArguments(Map.of("someDynamicArugument", someDynamicArgument));
cfg.setScheduledUpdateIntervalSec(60);
assertThat(cfg.isScheduledUpdateEnabled()).isTrue();
}
private Argument toArgument(String key, ArgumentType type) {
var referencedEntityKey = new ReferencedEntityKey(key, type, null);
return toArgument(referencedEntityKey);
}
private Argument toArgument(ReferencedEntityKey referencedEntityKey) {
Argument argument = new Argument();
argument.setRefEntityKey(referencedEntityKey);
return argument;
}
}