Resolved TODOs, refactoring: make GeofencingCalculatedFieldState extends Base state class
This commit is contained in:
parent
29934d08bd
commit
1421f9cc9f
@ -93,7 +93,7 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract void validateNewEntry(ArgumentEntry newEntry);
|
protected void validateNewEntry(ArgumentEntry newEntry) {}
|
||||||
|
|
||||||
private void updateLastUpdateTimestamp(ArgumentEntry entry) {
|
private void updateLastUpdateTimestamp(ArgumentEntry entry) {
|
||||||
long newTs = this.latestTimestamp;
|
long newTs = this.latestTimestamp;
|
||||||
|
|||||||
@ -19,8 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import lombok.AllArgsConstructor;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.common.util.geo.Coordinates;
|
import org.thingsboard.common.util.geo.Coordinates;
|
||||||
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
|
||||||
@ -30,8 +30,6 @@ import org.thingsboard.server.common.data.cf.configuration.GeofencingTransitionE
|
|||||||
import org.thingsboard.server.common.data.id.EntityId;
|
import org.thingsboard.server.common.data.id.EntityId;
|
||||||
import org.thingsboard.server.common.data.relation.EntityRelation;
|
import org.thingsboard.server.common.data.relation.EntityRelation;
|
||||||
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
import org.thingsboard.server.service.cf.CalculatedFieldResult;
|
||||||
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
|
|
||||||
import org.thingsboard.server.utils.CalculatedFieldUtils;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
@ -45,24 +43,18 @@ import static org.thingsboard.server.common.data.cf.configuration.GeofencingPres
|
|||||||
import static org.thingsboard.server.common.data.cf.configuration.GeofencingPresenceStatus.OUTSIDE;
|
import static org.thingsboard.server.common.data.cf.configuration.GeofencingPresenceStatus.OUTSIDE;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
|
||||||
|
|
||||||
private List<String> requiredArguments;
|
|
||||||
Map<String, ArgumentEntry> arguments;
|
|
||||||
private boolean sizeExceedsLimit;
|
|
||||||
|
|
||||||
private long latestTimestamp = -1;
|
|
||||||
|
|
||||||
private boolean dirty;
|
private boolean dirty;
|
||||||
|
|
||||||
public GeofencingCalculatedFieldState() {
|
public GeofencingCalculatedFieldState() {
|
||||||
this(new ArrayList<>(), new HashMap<>(), false, -1, false);
|
super(new ArrayList<>(), new HashMap<>(), false, -1);
|
||||||
|
this.dirty = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeofencingCalculatedFieldState(List<String> argNames) {
|
public GeofencingCalculatedFieldState(List<String> argNames) {
|
||||||
this.requiredArguments = argNames;
|
super(argNames);
|
||||||
this.arguments = new HashMap<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -129,21 +121,6 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
|||||||
return calculateWithoutRelations(ctx, entityCoordinates, configuration);
|
return calculateWithoutRelations(ctx, entityCoordinates, configuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean isReady() {
|
|
||||||
return arguments.keySet().containsAll(requiredArguments) &&
|
|
||||||
arguments.values().stream().noneMatch(ArgumentEntry::isEmpty);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void checkStateSize(CalculatedFieldEntityCtxId ctxId, long maxStateSize) {
|
|
||||||
if (!sizeExceedsLimit && maxStateSize > 0 && CalculatedFieldUtils.toProto(ctxId, this).getSerializedSize() > maxStateSize) {
|
|
||||||
arguments.clear();
|
|
||||||
sizeExceedsLimit = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private ListenableFuture<CalculatedFieldResult> calculateWithRelations(
|
private ListenableFuture<CalculatedFieldResult> calculateWithRelations(
|
||||||
EntityId entityId,
|
EntityId entityId,
|
||||||
CalculatedFieldCtx ctx,
|
CalculatedFieldCtx ctx,
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
|
|||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import com.google.common.util.concurrent.MoreExecutors;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.thingsboard.script.api.tbel.TbelCfArg;
|
import org.thingsboard.script.api.tbel.TbelCfArg;
|
||||||
@ -38,6 +39,7 @@ import java.util.Map;
|
|||||||
@Data
|
@Data
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
||||||
|
|
||||||
public ScriptCalculatedFieldState(List<String> requiredArguments) {
|
public ScriptCalculatedFieldState(List<String> requiredArguments) {
|
||||||
@ -49,10 +51,6 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
|||||||
return CalculatedFieldType.SCRIPT;
|
return CalculatedFieldType.SCRIPT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void validateNewEntry(ArgumentEntry newEntry) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
||||||
Map<String, TbelCfArg> arguments = new LinkedHashMap<>();
|
Map<String, TbelCfArg> arguments = new LinkedHashMap<>();
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
|||||||
import com.google.common.util.concurrent.Futures;
|
import com.google.common.util.concurrent.Futures;
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import org.thingsboard.common.util.JacksonUtil;
|
import org.thingsboard.common.util.JacksonUtil;
|
||||||
import org.thingsboard.script.api.tbel.TbUtils;
|
import org.thingsboard.script.api.tbel.TbUtils;
|
||||||
@ -34,6 +35,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
@Data
|
@Data
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
||||||
|
|
||||||
public SimpleCalculatedFieldState(List<String> requiredArguments) {
|
public SimpleCalculatedFieldState(List<String> requiredArguments) {
|
||||||
|
|||||||
@ -57,7 +57,7 @@
|
|||||||
<!-- <logger name="org.thingsboard.server.actors.device.DeviceActorMessageProcessor" level="DEBUG" />-->
|
<!-- <logger name="org.thingsboard.server.actors.device.DeviceActorMessageProcessor" level="DEBUG" />-->
|
||||||
|
|
||||||
<!-- CF actors message processors trace -->
|
<!-- CF actors message processors trace -->
|
||||||
<!-- <logger name="org.thingsboard.server.actors.calculatedField" level="TRACE" />-->
|
<logger name="org.thingsboard.server.actors.calculatedField" level="TRACE" />
|
||||||
|
|
||||||
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
|
<logger name="com.microsoft.azure.servicebus.primitives.CoreMessageReceiver" level="OFF" />
|
||||||
|
|
||||||
|
|||||||
@ -29,6 +29,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.ArgumentType;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.CalculatedFieldConfiguration;
|
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.GeofencingCalculatedFieldConfiguration;
|
||||||
|
import org.thingsboard.server.common.data.cf.configuration.GeofencingReportStrategy;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.Output;
|
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.OutputType;
|
||||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
||||||
@ -217,7 +218,6 @@ public class GeofencingCalculatedFieldStateTest {
|
|||||||
assertThat(state.isReady()).isFalse();
|
assertThat(state.isReady()).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: test different reporting strategies
|
|
||||||
@Test
|
@Test
|
||||||
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
||||||
state.arguments = new HashMap<>(Map.of(
|
state.arguments = new HashMap<>(Map.of(
|
||||||
@ -264,6 +264,147 @@ public class GeofencingCalculatedFieldStateTest {
|
|||||||
.put("restrictedZonesStatus", "INSIDE")
|
.put("restrictedZonesStatus", "INSIDE")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Check relations are created and deleted correctly for both iterations.
|
||||||
|
ArgumentCaptor<EntityRelation> saveCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||||
|
verify(relationService, times(2)).saveRelationAsync(eq(ctx.getTenantId()), saveCaptor.capture());
|
||||||
|
List<EntityRelation> saveValues = saveCaptor.getAllValues();
|
||||||
|
assertThat(saveValues).hasSize(2);
|
||||||
|
|
||||||
|
EntityRelation relationFromFirstIteration = saveValues.get(0);
|
||||||
|
assertThat(relationFromFirstIteration.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
assertThat(relationFromFirstIteration.getFrom()).isEqualTo(ZONE_1_ID);
|
||||||
|
assertThat(relationFromFirstIteration.getType()).isEqualTo(configuration.getZoneRelationType());
|
||||||
|
|
||||||
|
EntityRelation relationFromSecondIteration = saveValues.get(1);
|
||||||
|
assertThat(relationFromSecondIteration.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
assertThat(relationFromSecondIteration.getFrom()).isEqualTo(ZONE_2_ID);
|
||||||
|
assertThat(relationFromSecondIteration.getType()).isEqualTo(configuration.getZoneRelationType());
|
||||||
|
|
||||||
|
ArgumentCaptor<EntityRelation> deleteCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||||
|
verify(relationService).deleteRelationAsync(eq(ctx.getTenantId()), deleteCaptor.capture());
|
||||||
|
EntityRelation leftRelation = deleteCaptor.getValue();
|
||||||
|
assertThat(leftRelation.getFrom()).isEqualTo(ZONE_1_ID);
|
||||||
|
assertThat(leftRelation.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPerformCalculationWithOnlyTransitionEventsReportingStrategy() throws ExecutionException, InterruptedException {
|
||||||
|
state.arguments = new HashMap<>(Map.of(
|
||||||
|
ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArgEntry,
|
||||||
|
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, longitudeArgEntry,
|
||||||
|
"allowedZones", geofencingAllowedZoneArgEntry,
|
||||||
|
"restrictedZones", geofencingRestrictedZoneArgEntry
|
||||||
|
));
|
||||||
|
|
||||||
|
Output output = ctx.getOutput();
|
||||||
|
|
||||||
|
var calculatedFieldConfig = getCalculatedFieldConfig(GeofencingReportStrategy.REPORT_TRANSITION_EVENTS_ONLY);
|
||||||
|
|
||||||
|
ctx.setCalculatedField(getCalculatedField(calculatedFieldConfig));
|
||||||
|
ctx.init();
|
||||||
|
|
||||||
|
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||||
|
|
||||||
|
when(relationService.saveRelationAsync(any(), any())).thenReturn(Futures.immediateFuture(true));
|
||||||
|
when(relationService.deleteRelationAsync(any(), any())).thenReturn(Futures.immediateFuture(true));
|
||||||
|
|
||||||
|
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||||
|
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
|
assertThat(result.getResult()).isEqualTo(
|
||||||
|
JacksonUtil.newObjectNode().put("allowedZonesEvent", "ENTERED")
|
||||||
|
);
|
||||||
|
|
||||||
|
SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L);
|
||||||
|
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
|
||||||
|
|
||||||
|
// move the device to new coordinates → leaves allowed, enters restricted
|
||||||
|
state.updateState(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude));
|
||||||
|
|
||||||
|
CalculatedFieldResult result2 = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||||
|
|
||||||
|
assertThat(result2).isNotNull();
|
||||||
|
assertThat(result2.getType()).isEqualTo(output.getType());
|
||||||
|
assertThat(result2.getScope()).isEqualTo(output.getScope());
|
||||||
|
assertThat(result2.getResult()).isEqualTo(
|
||||||
|
JacksonUtil.newObjectNode()
|
||||||
|
.put("allowedZonesEvent", "LEFT")
|
||||||
|
.put("restrictedZonesEvent", "ENTERED")
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check relations are created and deleted correctly for both iterations.
|
||||||
|
ArgumentCaptor<EntityRelation> saveCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||||
|
verify(relationService, times(2)).saveRelationAsync(eq(ctx.getTenantId()), saveCaptor.capture());
|
||||||
|
List<EntityRelation> saveValues = saveCaptor.getAllValues();
|
||||||
|
assertThat(saveValues).hasSize(2);
|
||||||
|
|
||||||
|
EntityRelation relationFromFirstIteration = saveValues.get(0);
|
||||||
|
assertThat(relationFromFirstIteration.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
assertThat(relationFromFirstIteration.getFrom()).isEqualTo(ZONE_1_ID);
|
||||||
|
assertThat(relationFromFirstIteration.getType()).isEqualTo(configuration.getZoneRelationType());
|
||||||
|
|
||||||
|
EntityRelation relationFromSecondIteration = saveValues.get(1);
|
||||||
|
assertThat(relationFromSecondIteration.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
assertThat(relationFromSecondIteration.getFrom()).isEqualTo(ZONE_2_ID);
|
||||||
|
assertThat(relationFromSecondIteration.getType()).isEqualTo(configuration.getZoneRelationType());
|
||||||
|
|
||||||
|
ArgumentCaptor<EntityRelation> deleteCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||||
|
verify(relationService).deleteRelationAsync(eq(ctx.getTenantId()), deleteCaptor.capture());
|
||||||
|
EntityRelation leftRelation = deleteCaptor.getValue();
|
||||||
|
assertThat(leftRelation.getFrom()).isEqualTo(ZONE_1_ID);
|
||||||
|
assertThat(leftRelation.getTo()).isEqualTo(ctx.getEntityId());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testPerformCalculationWithOnlyPresenceStatusReportingStrategy() throws ExecutionException, InterruptedException {
|
||||||
|
state.arguments = new HashMap<>(Map.of(
|
||||||
|
ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArgEntry,
|
||||||
|
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, longitudeArgEntry,
|
||||||
|
"allowedZones", geofencingAllowedZoneArgEntry,
|
||||||
|
"restrictedZones", geofencingRestrictedZoneArgEntry
|
||||||
|
));
|
||||||
|
|
||||||
|
Output output = ctx.getOutput();
|
||||||
|
|
||||||
|
var calculatedFieldConfig = getCalculatedFieldConfig(GeofencingReportStrategy.REPORT_PRESENCE_STATUS_ONLY);
|
||||||
|
|
||||||
|
ctx.setCalculatedField(getCalculatedField(calculatedFieldConfig));
|
||||||
|
ctx.init();
|
||||||
|
|
||||||
|
var configuration = (GeofencingCalculatedFieldConfiguration) ctx.getCalculatedField().getConfiguration();
|
||||||
|
|
||||||
|
when(relationService.saveRelationAsync(any(), any())).thenReturn(Futures.immediateFuture(true));
|
||||||
|
when(relationService.deleteRelationAsync(any(), any())).thenReturn(Futures.immediateFuture(true));
|
||||||
|
|
||||||
|
CalculatedFieldResult result = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||||
|
|
||||||
|
assertThat(result).isNotNull();
|
||||||
|
assertThat(result.getType()).isEqualTo(output.getType());
|
||||||
|
assertThat(result.getScope()).isEqualTo(output.getScope());
|
||||||
|
assertThat(result.getResult()).isEqualTo(
|
||||||
|
JacksonUtil.newObjectNode()
|
||||||
|
.put("allowedZonesStatus", "INSIDE")
|
||||||
|
.put("restrictedZonesStatus", "OUTSIDE")
|
||||||
|
);
|
||||||
|
|
||||||
|
SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L);
|
||||||
|
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
|
||||||
|
|
||||||
|
// move the device to new coordinates → leaves allowed, enters restricted
|
||||||
|
state.updateState(ctx, Map.of(ENTITY_ID_LATITUDE_ARGUMENT_KEY, newLatitude, ENTITY_ID_LONGITUDE_ARGUMENT_KEY, newLongitude));
|
||||||
|
|
||||||
|
CalculatedFieldResult result2 = state.performCalculation(ctx.getEntityId(), ctx).get();
|
||||||
|
|
||||||
|
assertThat(result2).isNotNull();
|
||||||
|
assertThat(result2.getType()).isEqualTo(output.getType());
|
||||||
|
assertThat(result2.getScope()).isEqualTo(output.getScope());
|
||||||
|
assertThat(result2.getResult()).isEqualTo(
|
||||||
|
JacksonUtil.newObjectNode()
|
||||||
|
.put("allowedZonesStatus", "OUTSIDE")
|
||||||
|
.put("restrictedZonesStatus", "INSIDE")
|
||||||
|
);
|
||||||
|
|
||||||
// Check relations are created and deleted correctly for both iterations.
|
// Check relations are created and deleted correctly for both iterations.
|
||||||
ArgumentCaptor<EntityRelation> saveCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
ArgumentCaptor<EntityRelation> saveCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||||
@ -289,18 +430,22 @@ public class GeofencingCalculatedFieldStateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CalculatedField getCalculatedField() {
|
private CalculatedField getCalculatedField() {
|
||||||
|
return getCalculatedField(getCalculatedFieldConfig(REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS));
|
||||||
|
}
|
||||||
|
|
||||||
|
private CalculatedField getCalculatedField(CalculatedFieldConfiguration configuration) {
|
||||||
CalculatedField calculatedField = new CalculatedField();
|
CalculatedField calculatedField = new CalculatedField();
|
||||||
calculatedField.setTenantId(TENANT_ID);
|
calculatedField.setTenantId(TENANT_ID);
|
||||||
calculatedField.setEntityId(DEVICE_ID);
|
calculatedField.setEntityId(DEVICE_ID);
|
||||||
calculatedField.setType(CalculatedFieldType.GEOFENCING);
|
calculatedField.setType(CalculatedFieldType.GEOFENCING);
|
||||||
calculatedField.setName("Test Geofencing Calculated Field");
|
calculatedField.setName("Test Geofencing Calculated Field");
|
||||||
calculatedField.setConfigurationVersion(1);
|
calculatedField.setConfigurationVersion(1);
|
||||||
calculatedField.setConfiguration(getCalculatedFieldConfig());
|
calculatedField.setConfiguration(configuration);
|
||||||
calculatedField.setVersion(1L);
|
calculatedField.setVersion(1L);
|
||||||
return calculatedField;
|
return calculatedField;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CalculatedFieldConfiguration getCalculatedFieldConfig() {
|
private CalculatedFieldConfiguration getCalculatedFieldConfig(GeofencingReportStrategy reportStrategy) {
|
||||||
var config = new GeofencingCalculatedFieldConfiguration();
|
var config = new GeofencingCalculatedFieldConfiguration();
|
||||||
|
|
||||||
Argument argument1 = new Argument();
|
Argument argument1 = new Argument();
|
||||||
@ -335,7 +480,7 @@ public class GeofencingCalculatedFieldStateTest {
|
|||||||
|
|
||||||
config.setArguments(Map.of("latitude", argument1, "longitude", argument2, "allowedZones", argument3, "restrictedZones", argument4));
|
config.setArguments(Map.of("latitude", argument1, "longitude", argument2, "allowedZones", argument3, "restrictedZones", argument4));
|
||||||
|
|
||||||
config.setZoneGroupReportStrategies(Map.of("allowedZones", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS, "restrictedZones", REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS));
|
config.setZoneGroupReportStrategies(Map.of("allowedZones", reportStrategy, "restrictedZones", reportStrategy));
|
||||||
|
|
||||||
config.setCreateRelationsWithMatchedZones(true);
|
config.setCreateRelationsWithMatchedZones(true);
|
||||||
config.setZoneRelationType("CurrentZone");
|
config.setZoneRelationType("CurrentZone");
|
||||||
|
|||||||
@ -84,4 +84,84 @@ public class GeofencingZoneStateTest {
|
|||||||
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
|
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_withNewerVersion_updatesState_andResetsPresence() {
|
||||||
|
// arrange: establish a prior presence to ensure it’s reset on update
|
||||||
|
var inside = new Coordinates(50.4730, 30.5050);
|
||||||
|
assertThat(state.evaluate(inside)).isNotNull(); // sets lastPresence internally
|
||||||
|
|
||||||
|
String NEW_POLYGON = "[[50.470000, 30.502000], [50.470000, 30.503000], [50.471000, 30.503000], [50.471000, 30.502000]]";
|
||||||
|
GeofencingZoneState newer = new GeofencingZoneState(
|
||||||
|
ZONE_ID,
|
||||||
|
new BaseAttributeKvEntry(new JsonDataEntry("zone", NEW_POLYGON), 200L, 2L)
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
boolean changed = state.update(newer);
|
||||||
|
|
||||||
|
// assert
|
||||||
|
assertThat(changed).isTrue();
|
||||||
|
assertThat(state.getTs()).isEqualTo(200L);
|
||||||
|
assertThat(state.getVersion()).isEqualTo(2L);
|
||||||
|
assertThat(state.getPerimeterDefinition()).isNotNull();
|
||||||
|
assertThat(state.getLastPresence()).isNull(); // must be reset on successful update
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_withEqualVersion_doesNothing() {
|
||||||
|
// arrange: same version (1L) but different ts/polygon should still be ignored
|
||||||
|
String SOME_POLYGON = "[[50.472500, 30.504500], [50.472500, 30.505500], [50.473500, 30.505500], [50.473500, 30.504500]]";
|
||||||
|
GeofencingZoneState sameVersion = new GeofencingZoneState(
|
||||||
|
ZONE_ID,
|
||||||
|
new BaseAttributeKvEntry(new JsonDataEntry("zone", SOME_POLYGON), 300L, 1L)
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
boolean changed = state.update(sameVersion);
|
||||||
|
|
||||||
|
// assert: nothing changes
|
||||||
|
assertThat(changed).isFalse();
|
||||||
|
assertThat(state.getTs()).isEqualTo(100L);
|
||||||
|
assertThat(state.getVersion()).isEqualTo(1L);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_withNullNewVersion_alwaysApplies_andCopiesNull() {
|
||||||
|
// arrange: the implementation updates if newVersion == null
|
||||||
|
String OTHER_POLYGON = "[[50.471000, 30.506000], [50.471000, 30.507000], [50.472000, 30.507000], [50.472000, 30.506000]]";
|
||||||
|
GeofencingZoneState nullVersion = new GeofencingZoneState(
|
||||||
|
ZONE_ID,
|
||||||
|
new BaseAttributeKvEntry(new JsonDataEntry("zone", OTHER_POLYGON), 400L, null)
|
||||||
|
);
|
||||||
|
|
||||||
|
// act
|
||||||
|
boolean changed = state.update(nullVersion);
|
||||||
|
|
||||||
|
// assert: applied and version copied as null
|
||||||
|
assertThat(changed).isTrue();
|
||||||
|
assertThat(state.getTs()).isEqualTo(400L);
|
||||||
|
assertThat(state.getVersion()).isNull();
|
||||||
|
assertThat(state.getLastPresence()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void update_withNewVersionWhenExistingIsNull_alwaysApplies_andCopiesNew() {
|
||||||
|
// arrange: the implementation updates if newVersion == null
|
||||||
|
String OTHER_POLYGON = "[[50.471000, 30.506000], [50.471000, 30.507000], [50.472000, 30.507000], [50.472000, 30.506000]]";
|
||||||
|
GeofencingZoneState newVersion = new GeofencingZoneState(
|
||||||
|
ZONE_ID,
|
||||||
|
new BaseAttributeKvEntry(new JsonDataEntry("zone", OTHER_POLYGON), 400L, 2L)
|
||||||
|
);
|
||||||
|
state.setVersion(null);
|
||||||
|
|
||||||
|
// act
|
||||||
|
boolean changed = state.update(newVersion);
|
||||||
|
|
||||||
|
// assert: applied and version copied as null
|
||||||
|
assertThat(changed).isTrue();
|
||||||
|
assertThat(state.getTs()).isEqualTo(400L);
|
||||||
|
assertThat(state.getVersion()).isEqualTo(2);
|
||||||
|
assertThat(state.getLastPresence()).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user