Browse Source

bugfixes for propagation and geofencing CFs

pull/14414/head
dshvaika 6 months ago
parent
commit
9e416e7dee
  1. 5
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java
  2. 16
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java
  3. 26
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java
  4. 2
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingEvalResult.java
  5. 10
      application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java
  6. 21
      application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java
  7. 52
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java
  8. 22
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java
  9. 33
      application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java
  10. 7
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

5
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/BaseCalculatedFieldState.java

@ -25,6 +25,8 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.service.cf.ctx.CalculatedFieldEntityCtxId;
import org.thingsboard.server.service.cf.ctx.state.aggregation.RelatedEntitiesArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.aggregation.single.EntityAggregationArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingArgumentEntry;
import org.thingsboard.server.service.cf.ctx.state.geofencing.GeofencingZoneState;
import org.thingsboard.server.utils.CalculatedFieldUtils;
import java.io.Closeable;
@ -164,6 +166,9 @@ public abstract class BaseCalculatedFieldState implements CalculatedFieldState,
.mapToLong(e -> (e instanceof SingleValueArgumentEntry s) ? s.getTs() : 0L)
.max()
.orElse(0L);
} else if (entry instanceof GeofencingArgumentEntry geofencingArgumentEntry) {
newTs = geofencingArgumentEntry.getZoneStates().values().stream()
.mapToLong(GeofencingZoneState::getTs).max().orElse(0L);
}
this.latestTimestamp = Math.max(this.latestTimestamp, newTs);
}

16
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java

@ -38,6 +38,7 @@ import java.io.Closeable;
import java.util.List;
import java.util.Map;
import static org.thingsboard.server.common.data.cf.configuration.PropagationCalculatedFieldConfiguration.PROPAGATION_CONFIG_ARGUMENT;
import static org.thingsboard.server.utils.CalculatedFieldUtils.toSingleValueArgumentProto;
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
@ -102,14 +103,25 @@ public interface CalculatedFieldState extends Closeable {
record ReadinessStatus(boolean ready, String errorMsg) {
private static final String ERROR_MESSAGE = "Required arguments are missing: ";
private static final String MISSING_REQUIRED_ARGUMENTS_ERROR = "Required arguments are missing: ";
private static final String MISSING_PROPAGATION_TARGETS_ERROR = "No entities found via 'Propagation path to related entities'. " +
"Verify the relation type and direction configured.";
private static final String MISSING_PROPAGATION_TARGETS_AND_ARGUMENTS_ERROR = MISSING_PROPAGATION_TARGETS_ERROR + " Missing arguments to propagate: ";
private static final ReadinessStatus READY = new ReadinessStatus(true, null);
public static ReadinessStatus from(List<String> emptyOrMissingArguments) {
if (CollectionsUtil.isEmpty(emptyOrMissingArguments)) {
return ReadinessStatus.READY;
}
return new ReadinessStatus(false, ERROR_MESSAGE + String.join(", ", emptyOrMissingArguments));
boolean propagationCtxIsEmpty = emptyOrMissingArguments.remove(PROPAGATION_CONFIG_ARGUMENT);
if (!propagationCtxIsEmpty) {
return new ReadinessStatus(false, MISSING_REQUIRED_ARGUMENTS_ERROR + String.join(", ", emptyOrMissingArguments));
}
if (emptyOrMissingArguments.isEmpty()) {
return new ReadinessStatus(false, MISSING_PROPAGATION_TARGETS_ERROR);
}
return new ReadinessStatus(false, MISSING_PROPAGATION_TARGETS_AND_ARGUMENTS_ERROR +
String.join(", ", emptyOrMissingArguments));
}
}

26
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingCalculatedFieldState.java

@ -29,6 +29,7 @@ import org.thingsboard.common.util.geo.Coordinates;
import org.thingsboard.server.common.data.cf.CalculatedFieldType;
import org.thingsboard.server.common.data.cf.configuration.OutputType;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingCalculatedFieldConfiguration;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingPresenceStatus;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingReportStrategy;
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingTransitionEvent;
import org.thingsboard.server.common.data.cf.configuration.geofencing.ZoneGroupConfiguration;
@ -112,7 +113,12 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
if (createRelationsWithMatchedZones) {
GeofencingTransitionEvent transitionEvent = eval.transition();
if (transitionEvent == null) {
return;
if (!eval.firstEvaluation()) {
return;
}
transitionEvent = eval.status() == GeofencingPresenceStatus.INSIDE ?
GeofencingTransitionEvent.ENTERED :
GeofencingTransitionEvent.LEFT;
}
EntityRelation relation = switch (zoneGroupCfg.getDirection()) {
case TO -> new EntityRelation(zoneId, entityId, zoneGroupCfg.getRelationType());
@ -178,15 +184,27 @@ public class GeofencingCalculatedFieldState extends BaseCalculatedFieldState {
private GeofencingEvalResult aggregateZoneGroup(List<GeofencingEvalResult> zoneResults) {
boolean nowInside = zoneResults.stream().anyMatch(r -> INSIDE.equals(r.status()));
boolean prevInside = zoneResults.stream()
.anyMatch(r -> GeofencingTransitionEvent.LEFT.equals(r.transition()) || r.transition() == null && r.status() == INSIDE);
boolean firstEvaluation = zoneResults.stream().allMatch(GeofencingEvalResult::firstEvaluation);
if (firstEvaluation) {
return new GeofencingEvalResult(null, nowInside ? INSIDE : OUTSIDE, true);
}
boolean prevInside = zoneResults.stream().anyMatch(r -> {
if (r.firstEvaluation()) {
return false;
}
return GeofencingTransitionEvent.LEFT.equals(r.transition())
|| (r.transition() == null && r.status() == INSIDE);
});
GeofencingTransitionEvent transition = null;
if (!prevInside && nowInside) {
transition = GeofencingTransitionEvent.ENTERED;
} else if (prevInside && !nowInside) {
transition = GeofencingTransitionEvent.LEFT;
}
return new GeofencingEvalResult(transition, nowInside ? INSIDE : OUTSIDE);
return new GeofencingEvalResult(transition, nowInside ? INSIDE : OUTSIDE, false);
}
private void addTransitionEventIfExists(ObjectNode resultNode, GeofencingEvalResult aggregationResult, String eventKey) {

2
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingEvalResult.java

@ -20,5 +20,5 @@ import org.thingsboard.server.common.data.cf.configuration.geofencing.Geofencing
import org.thingsboard.server.common.data.cf.configuration.geofencing.GeofencingTransitionEvent;
public record GeofencingEvalResult(@Nullable GeofencingTransitionEvent transition,
GeofencingPresenceStatus status) {
GeofencingPresenceStatus status, boolean firstEvaluation) {
}

10
application/src/main/java/org/thingsboard/server/service/cf/ctx/state/geofencing/GeofencingZoneState.java

@ -86,21 +86,17 @@ public class GeofencingZoneState {
// first evaluation
if (this.lastPresence == null) {
this.lastPresence = status;
GeofencingTransitionEvent transition = null;
if (status == GeofencingPresenceStatus.INSIDE) {
transition = GeofencingTransitionEvent.ENTERED;
}
return new GeofencingEvalResult(transition, status);
return new GeofencingEvalResult(null, status, true);
}
// State changed
if (this.lastPresence != status) {
this.lastPresence = status;
GeofencingTransitionEvent transition = (status == GeofencingPresenceStatus.INSIDE) ?
GeofencingTransitionEvent.ENTERED : GeofencingTransitionEvent.LEFT;
return new GeofencingEvalResult(transition, status);
return new GeofencingEvalResult(transition, status, false);
}
// State unchanged
return new GeofencingEvalResult(null, status);
return new GeofencingEvalResult(null, status, false);
}
}

21
application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java

@ -714,7 +714,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/calculatedField", cf, CalculatedField.class);
// --- Assert initial evaluation (ENTERED / OUTSIDE) ---
// --- Assert initial evaluation (INSIDE / OUTSIDE) ---
await().alias("initial geofencing evaluation")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
@ -722,10 +722,9 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
ArrayNode attrs = getServerAttributes(device.getId(),
"allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus", "restrictedZonesEvent");
// --- no restrictedZonesEvent as no transition happened yet
assertThat(attrs).isNotNull().isNotEmpty().hasSize(3);
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED")
.containsEntry("allowedZonesStatus", "INSIDE")
assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE");
});
@ -737,8 +736,6 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
JacksonUtil.toJsonNode("{\"restrictedZone\":" + restrictedPolygon + "}")).andExpect(status().isOk());
// --- Assert no transition ---
// --- Assert attributes updated with the same values for restrictedZones ---
// --- Assert attributes updated with the new values for allowedZones ---
await().alias("evaluation after version bump of geo argument")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
@ -824,17 +821,16 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
doPost("/api/calculatedField", cf, CalculatedField.class);
// --- Assert initial evaluation (ENTERED / OUTSIDE) ---
// --- Assert initial evaluation (INSIDE / OUTSIDE) ---
await().alias("initial geofencing evaluation")
.atMost(TIMEOUT, TimeUnit.SECONDS)
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ArrayNode attrs = getServerAttributes(device.getId(),
"allowedZonesEvent", "allowedZonesStatus", "restrictedZonesStatus");
assertThat(attrs).isNotNull().isNotEmpty().hasSize(3);
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED")
.containsEntry("allowedZonesStatus", "INSIDE")
assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE");
});
@ -935,10 +931,9 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ArrayNode attrs = getServerAttributes(device.getId(), "allowedZonesEvent", "allowedZonesStatus");
assertThat(attrs).isNotNull().isNotEmpty().hasSize(2);
assertThat(attrs).isNotNull().isNotEmpty().hasSize(1);
Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED")
.containsEntry("allowedZonesStatus", "INSIDE");
assertThat(m).containsEntry("allowedZonesStatus", "INSIDE");
});
// --- Move device OUTSIDE Zone A (expect LEFT) ---

52
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingCalculatedFieldStateTest.java

@ -221,7 +221,7 @@ public class GeofencingCalculatedFieldStateTest {
ENTITY_ID_LATITUDE_ARGUMENT_KEY, latitudeArgEntry,
ENTITY_ID_LONGITUDE_ARGUMENT_KEY, longitudeArgEntry,
"allowedZones", geofencingAllowedZoneArgEntry,
"restrictedZones", new GeofencingArgumentEntry()
"restrictedZones", new GeofencingArgumentEntry(Collections.emptyMap())
), ctx);
assertThat(state.isReady()).isFalse();
assertThat(state.getReadinessStatus().errorMsg()).contains("restrictedZones");
@ -249,7 +249,6 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(result.getScope()).isEqualTo(output.getScope());
assertThat(result.getResult()).isEqualTo(
JacksonUtil.newObjectNode()
.put("allowedZonesEvent", "ENTERED")
.put("allowedZonesStatus", "INSIDE")
.put("restrictedZonesStatus", "OUTSIDE")
);
@ -290,10 +289,17 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(relationFromSecondIteration.getType()).isEqualTo("CurrentZone");
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());
verify(relationService, times(2)).deleteRelationAsync(eq(ctx.getTenantId()), deleteCaptor.capture());
List<EntityRelation> deleteValues = deleteCaptor.getAllValues();
assertThat(deleteValues).hasSize(2);
EntityRelation deleteRelationFromFirstIteration = deleteValues.get(0);
assertThat(deleteRelationFromFirstIteration.getFrom()).isEqualTo(ZONE_2_ID);
assertThat(deleteRelationFromFirstIteration.getTo()).isEqualTo(ctx.getEntityId());
EntityRelation deleteRelationFromSecondIteration = deleteValues.get(1);
assertThat(deleteRelationFromSecondIteration.getFrom()).isEqualTo(ZONE_1_ID);
assertThat(deleteRelationFromSecondIteration.getTo()).isEqualTo(ctx.getEntityId());
}
@Test
@ -322,9 +328,7 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(result).isNotNull();
assertThat(result.getType()).isEqualTo(output.getType());
assertThat(result.getScope()).isEqualTo(output.getScope());
assertThat(result.getResult()).isEqualTo(
JacksonUtil.newObjectNode().put("allowedZonesEvent", "ENTERED")
);
assertThat(result.getResult()).isEqualTo(JacksonUtil.newObjectNode());
SingleValueArgumentEntry newLatitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("latitude", 50.4760), 146L);
SingleValueArgumentEntry newLongitude = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("longitude", 30.5110), 166L);
@ -360,10 +364,17 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(relationFromSecondIteration.getType()).isEqualTo("CurrentZone");
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());
verify(relationService, times(2)).deleteRelationAsync(eq(ctx.getTenantId()), deleteCaptor.capture());
List<EntityRelation> deleteValues = deleteCaptor.getAllValues();
assertThat(deleteValues).hasSize(2);
EntityRelation deleteRelationFromFirstIteration = deleteValues.get(0);
assertThat(deleteRelationFromFirstIteration.getFrom()).isEqualTo(ZONE_2_ID);
assertThat(deleteRelationFromFirstIteration.getTo()).isEqualTo(ctx.getEntityId());
EntityRelation deleteRelationFromSecondIteration = deleteValues.get(1);
assertThat(deleteRelationFromSecondIteration.getFrom()).isEqualTo(ZONE_1_ID);
assertThat(deleteRelationFromSecondIteration.getTo()).isEqualTo(ctx.getEntityId());
}
@Test
@ -432,10 +443,17 @@ public class GeofencingCalculatedFieldStateTest {
assertThat(relationFromSecondIteration.getType()).isEqualTo("CurrentZone");
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());
verify(relationService, times(2)).deleteRelationAsync(eq(ctx.getTenantId()), deleteCaptor.capture());
List<EntityRelation> deleteValues = deleteCaptor.getAllValues();
assertThat(deleteValues).hasSize(2);
EntityRelation deleteRelationFromFirstIteration = deleteValues.get(0);
assertThat(deleteRelationFromFirstIteration.getFrom()).isEqualTo(ZONE_2_ID);
assertThat(deleteRelationFromFirstIteration.getTo()).isEqualTo(ctx.getEntityId());
EntityRelation deleteRelationFromSecondIteration = deleteValues.get(1);
assertThat(deleteRelationFromSecondIteration.getFrom()).isEqualTo(ZONE_1_ID);
assertThat(deleteRelationFromSecondIteration.getTo()).isEqualTo(ctx.getEntityId());
}
private CalculatedField getCalculatedField() {

22
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/GeofencingZoneStateTest.java

@ -48,30 +48,30 @@ public class GeofencingZoneStateTest {
void evaluate_initialInside_thenInsideAgain() {
var inside = new Coordinates(50.4730, 30.5050);
// first evaluation: no prior state -> ENTERED
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE));
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE, true));
// same position again -> INSIDE (steady state)
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE, false));
}
@Test
void evaluate_initialOutside_thenOutsideAgain() {
var outside = new Coordinates(50.4760, 30.5110);
// first evaluation: no prior state -> OUTSIDE
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE));
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE, true));
// same position again -> OUTSIDE (steady state)
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE));
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE, false));
}
@Test
void evaluate_inside_thenLeave() {
var inside = new Coordinates(50.4730, 30.5050);
var outside = new Coordinates(50.4760, 30.5110);
// enter
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE));
// inside
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE, true));
// leave -> LEFT
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(LEFT, OUTSIDE));
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(LEFT, OUTSIDE, false));
// still outside -> OUTSIDE
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE));
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE, false));
}
@Test
@ -79,11 +79,11 @@ public class GeofencingZoneStateTest {
var outside = new Coordinates(50.4760, 30.5110);
var inside = new Coordinates(50.4730, 30.5050);
// start outside
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE));
assertThat(state.evaluate(outside)).isEqualTo(new GeofencingEvalResult(null, OUTSIDE, true));
// cross boundary -> ENTERED
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE));
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(ENTERED, INSIDE, false));
// remain inside -> INSIDE
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE));
assertThat(state.evaluate(inside)).isEqualTo(new GeofencingEvalResult(null, INSIDE, false));
}
@Test

33
application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java

@ -19,6 +19,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
@ -52,10 +54,12 @@ import org.thingsboard.server.service.cf.ctx.state.propagation.PropagationCalcul
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
@ -126,21 +130,28 @@ public class PropagationCalculatedFieldStateTest {
assertThat(state.isReady()).isFalse();
}
@Test
void testIsReadyWhenPropagationArgIsNull() {
initCtxAndState(false);
state.update(Map.of(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry), ctx);
assertThat(state.isReady()).isFalse();
assertThat(state.getReadinessStatus().errorMsg()).contains(PROPAGATION_CONFIG_ARGUMENT);
private static Stream<ArgumentEntry> provideInvalidPropagationArgs() {
return Stream.of(
null,
new PropagationArgumentEntry(Collections.emptyList())
);
}
@Test
void testIsReadyWhenPropagationArgIsEmpty() {
@ParameterizedTest
@MethodSource("provideInvalidPropagationArgs")
void testIsReadyWhenPropagationArgIsNullOrEmpty(ArgumentEntry propagationEntry) {
initCtxAndState(false);
state.update(Map.of(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry,
PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList())), ctx);
Map<String, ArgumentEntry> args = new HashMap<>();
args.put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); // Valid user arg
if (propagationEntry != null) {
args.put(PROPAGATION_CONFIG_ARGUMENT, propagationEntry);
}
state.update(args, ctx);
assertThat(state.isReady()).isFalse();
assertThat(state.getReadinessStatus().errorMsg()).contains(PROPAGATION_CONFIG_ARGUMENT);
assertThat(state.getReadinessStatus().errorMsg())
.isEqualTo("No entities found via 'Propagation path to related entities'. Verify the relation type and direction configured.");
}
@Test

7
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/cf/CalculatedFieldTest.java

@ -409,11 +409,10 @@ public class CalculatedFieldTest extends AbstractContainerTest {
.pollInterval(POLL_INTERVAL, TimeUnit.SECONDS)
.untilAsserted(() -> {
ArrayNode attrs = testRestClient.getAttributes(device.getId(), SERVER_SCOPE,
"allowedZonesEvent,allowedZonesStatus,restrictedZonesStatus");
assertThat(attrs).isNotNull().hasSize(3);
"allowedZonesEvent,allowedZonesStatus,restrictedZonesEvent,restrictedZonesStatus");
assertThat(attrs).isNotNull().hasSize(2);
Map<String, String> m = kv(attrs);
assertThat(m).containsEntry("allowedZonesEvent", "ENTERED")
.containsEntry("allowedZonesStatus", "INSIDE")
assertThat(m).containsEntry("allowedZonesStatus", "INSIDE")
.containsEntry("restrictedZonesStatus", "OUTSIDE");
});

Loading…
Cancel
Save