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) {
|
||||
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.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.common.util.geo.Coordinates;
|
||||
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.relation.EntityRelation;
|
||||
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.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;
|
||||
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
|
||||
private List<String> requiredArguments;
|
||||
Map<String, ArgumentEntry> arguments;
|
||||
private boolean sizeExceedsLimit;
|
||||
|
||||
private long latestTimestamp = -1;
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
|
||||
private boolean dirty;
|
||||
|
||||
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) {
|
||||
this.requiredArguments = argNames;
|
||||
this.arguments = new HashMap<>();
|
||||
super(argNames);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -129,21 +121,6 @@ public class GeofencingCalculatedFieldState implements CalculatedFieldState {
|
||||
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(
|
||||
EntityId entityId,
|
||||
CalculatedFieldCtx ctx,
|
||||
|
||||
@ -20,4 +20,4 @@ import org.thingsboard.server.common.data.cf.configuration.GeofencingPresenceSta
|
||||
import org.thingsboard.server.common.data.cf.configuration.GeofencingTransitionEvent;
|
||||
|
||||
public record GeofencingEvalResult(@Nullable GeofencingTransitionEvent transition,
|
||||
GeofencingPresenceStatus status) {}
|
||||
GeofencingPresenceStatus status) {}
|
||||
|
||||
@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.thingsboard.script.api.tbel.TbelCfArg;
|
||||
@ -38,6 +39,7 @@ import java.util.Map;
|
||||
@Data
|
||||
@Slf4j
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
|
||||
public ScriptCalculatedFieldState(List<String> requiredArguments) {
|
||||
@ -49,10 +51,6 @@ public class ScriptCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
return CalculatedFieldType.SCRIPT;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateNewEntry(ArgumentEntry newEntry) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListenableFuture<CalculatedFieldResult> performCalculation(EntityId entityId, CalculatedFieldCtx ctx) {
|
||||
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.ListenableFuture;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.thingsboard.common.util.JacksonUtil;
|
||||
import org.thingsboard.script.api.tbel.TbUtils;
|
||||
@ -34,6 +35,7 @@ import java.util.Map;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class SimpleCalculatedFieldState extends BaseCalculatedFieldState {
|
||||
|
||||
public SimpleCalculatedFieldState(List<String> requiredArguments) {
|
||||
|
||||
@ -57,7 +57,7 @@
|
||||
<!-- <logger name="org.thingsboard.server.actors.device.DeviceActorMessageProcessor" level="DEBUG" />-->
|
||||
|
||||
<!-- 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" />
|
||||
|
||||
|
||||
@ -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.CalculatedFieldConfiguration;
|
||||
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.OutputType;
|
||||
import org.thingsboard.server.common.data.cf.configuration.ReferencedEntityKey;
|
||||
@ -217,7 +218,6 @@ public class GeofencingCalculatedFieldStateTest {
|
||||
assertThat(state.isReady()).isFalse();
|
||||
}
|
||||
|
||||
// TODO: test different reporting strategies
|
||||
@Test
|
||||
void testPerformCalculation() throws ExecutionException, InterruptedException {
|
||||
state.arguments = new HashMap<>(Map.of(
|
||||
@ -264,6 +264,147 @@ public class GeofencingCalculatedFieldStateTest {
|
||||
.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.
|
||||
ArgumentCaptor<EntityRelation> saveCaptor = ArgumentCaptor.forClass(EntityRelation.class);
|
||||
@ -289,18 +430,22 @@ public class GeofencingCalculatedFieldStateTest {
|
||||
}
|
||||
|
||||
private CalculatedField getCalculatedField() {
|
||||
return getCalculatedField(getCalculatedFieldConfig(REPORT_TRANSITION_EVENTS_AND_PRESENCE_STATUS));
|
||||
}
|
||||
|
||||
private CalculatedField getCalculatedField(CalculatedFieldConfiguration configuration) {
|
||||
CalculatedField calculatedField = new CalculatedField();
|
||||
calculatedField.setTenantId(TENANT_ID);
|
||||
calculatedField.setEntityId(DEVICE_ID);
|
||||
calculatedField.setType(CalculatedFieldType.GEOFENCING);
|
||||
calculatedField.setName("Test Geofencing Calculated Field");
|
||||
calculatedField.setConfigurationVersion(1);
|
||||
calculatedField.setConfiguration(getCalculatedFieldConfig());
|
||||
calculatedField.setConfiguration(configuration);
|
||||
calculatedField.setVersion(1L);
|
||||
return calculatedField;
|
||||
}
|
||||
|
||||
private CalculatedFieldConfiguration getCalculatedFieldConfig() {
|
||||
private CalculatedFieldConfiguration getCalculatedFieldConfig(GeofencingReportStrategy reportStrategy) {
|
||||
var config = new GeofencingCalculatedFieldConfiguration();
|
||||
|
||||
Argument argument1 = new Argument();
|
||||
@ -335,7 +480,7 @@ public class GeofencingCalculatedFieldStateTest {
|
||||
|
||||
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.setZoneRelationType("CurrentZone");
|
||||
|
||||
@ -84,4 +84,84 @@ public class GeofencingZoneStateTest {
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -35,4 +35,4 @@ public class ArgumentTest {
|
||||
assertThat(argument.hasDynamicSource()).isTrue();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user