diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java index f254631491..542759df49 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/CalculatedFieldState.java @@ -110,7 +110,7 @@ public interface CalculatedFieldState extends Closeable { private static final String MISSING_PROPAGATION_TARGETS_ERROR = "No entities found via 'Propagation path to related entities'. " + "Verify the configured relation type and direction."; 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 final ReadinessStatus READY = new ReadinessStatus(true, null); public static ReadinessStatus from(List emptyOrMissingArguments) { if (CollectionsUtil.isEmpty(emptyOrMissingArguments)) { diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java index 5a7753c86a..c55a227ea0 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/propagation/PropagationCalculatedFieldState.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.cf.CalculatedFieldType; import org.thingsboard.server.common.data.cf.configuration.Output; import org.thingsboard.server.common.data.cf.configuration.OutputType; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.util.CollectionsUtil; import org.thingsboard.server.service.cf.CalculatedFieldResult; import org.thingsboard.server.service.cf.PropagationCalculatedFieldResult; import org.thingsboard.server.service.cf.TelemetryCalculatedFieldResult; @@ -68,8 +69,9 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState if (!(argumentEntry instanceof PropagationArgumentEntry propagationArgumentEntry)) { return Futures.immediateFuture(PropagationCalculatedFieldResult.builder().build()); } + boolean newEntityAdded = propagationArgumentEntry.getAdded() != null; List entityIds; - if (propagationArgumentEntry.getAdded() != null) { + if (newEntityAdded) { entityIds = List.of(propagationArgumentEntry.getAdded()); propagationArgumentEntry.setAdded(null); } else { @@ -86,13 +88,43 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState .build(), MoreExecutors.directExecutor()); } + if (newEntityAdded || CollectionsUtil.isEmpty(updatedArgs)) { + updatedArgs = arguments; + } return Futures.immediateFuture(PropagationCalculatedFieldResult.builder() .entityIds(entityIds) - .result(toTelemetryResult(ctx)) + .result(toTelemetryResult(ctx, updatedArgs)) .build()); } - private TelemetryCalculatedFieldResult toTelemetryResult(CalculatedFieldCtx ctx) { + @Override + protected ReadinessStatus checkReadiness(List requiredArguments, Map currentArguments) { + if (ctx.isApplyExpressionForResolvedArguments() || currentArguments == null) { + return super.checkReadiness(requiredArguments, currentArguments); + } + boolean propagationNotEmpty = false; + boolean hasOtherNonEmpty = false; + List emptyArguments = null; + for (String requiredArgumentKey : requiredArguments) { + ArgumentEntry argumentEntry = currentArguments.get(requiredArgumentKey); + if (argumentEntry == null || argumentEntry.isEmpty()) { + if (emptyArguments == null) { + emptyArguments = new ArrayList<>(); + } + emptyArguments.add(requiredArgumentKey); + } else if (PROPAGATION_CONFIG_ARGUMENT.equals(requiredArgumentKey)) { + propagationNotEmpty = true; + } else { + hasOtherNonEmpty = true; + } + } + if (propagationNotEmpty && hasOtherNonEmpty) { + return ReadinessStatus.READY; + } + return ReadinessStatus.from(emptyArguments); + } + + private TelemetryCalculatedFieldResult toTelemetryResult(CalculatedFieldCtx ctx, Map updatedArgs) { Output output = ctx.getOutput(); TelemetryCalculatedFieldResult.TelemetryCalculatedFieldResultBuilder telemetryCfBuilder = TelemetryCalculatedFieldResult.builder() @@ -100,12 +132,14 @@ public class PropagationCalculatedFieldState extends ScriptCalculatedFieldState .type(output.getType()) .scope(output.getScope()); ObjectNode valuesNode = JacksonUtil.newObjectNode(); - arguments.forEach((outputKey, argumentEntry) -> { + updatedArgs.forEach((outputKey, argumentEntry) -> { if (argumentEntry instanceof PropagationArgumentEntry) { return; } if (argumentEntry instanceof SingleValueArgumentEntry singleArgumentEntry) { - JacksonUtil.addKvEntry(valuesNode, singleArgumentEntry.getKvEntryValue(), outputKey); + if (!singleArgumentEntry.isEmpty()) { + JacksonUtil.addKvEntry(valuesNode, singleArgumentEntry.getKvEntryValue(), outputKey); + } return; } throw new IllegalArgumentException("Unsupported argument type: " + argumentEntry.getType() + " detected for argument: " + outputKey + ". " + diff --git a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java index f0008d6cfb..a194680ca1 100644 --- a/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/cf/CalculatedFieldIntegrationTest.java @@ -1089,7 +1089,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes // Telemetry on device doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/timeseries/unusedScope", - JacksonUtil.toJsonNode("{\"temperature\":12.5}")).andExpect(status().isOk()); + JacksonUtil.toJsonNode("{\"temperature\":12.5, \"humidity\":85}")).andExpect(status().isOk()); // --- Build CF: PROPAGATION with expression --- CalculatedField cf = new CalculatedField(); @@ -1102,11 +1102,14 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(true); - Argument arg = new Argument(); - arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setArguments(Map.of("t", arg)); + Argument arg1 = new Argument(); + arg1.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setExpression("{\"testResult\": t * 2}"); + Argument arg2 = new Argument(); + arg2.setRefEntityKey(new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null)); + + cfg.setArguments(Map.of("t", arg1, "h", arg2)); + cfg.setExpression("return { testResult: (t + h) / 2};"); AttributesOutput output = new AttributesOutput(); output.setScope(AttributeScope.SERVER_SCOPE); @@ -1125,8 +1128,8 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes ArrayNode attrs2 = getServerAttributes(asset2.getId(), "testResult"); assertThat(attrs1).isNotNull(); assertThat(attrs2).isNotNull(); - assertThat(attrs1.get(0).get("value").asDouble()).isEqualTo(25.0); - assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(25.0); + assertThat(attrs1.get(0).get("value").asDouble()).isEqualTo(48.75); + assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(48.75); }); String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", @@ -1148,7 +1151,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes ArrayNode attrs2 = getServerAttributes(asset2.getId(), "testResult"); assertThat(attrs1).isNullOrEmpty(); assertThat(attrs2).isNotNull(); - assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(50); + assertThat(attrs2.get(0).get("value").asDouble()).isEqualTo(55); }); } @@ -1167,7 +1170,7 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes // Telemetry on device long ts = System.currentTimeMillis() - 300000L; - postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":12.5}}", ts)); + postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":12.5, \"humidity\":85}}", ts)); // --- Build CF: PROPAGATION without expression --- CalculatedField cf = new CalculatedField(); @@ -1180,9 +1183,12 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes cfg.setRelation(new RelationPathLevel(EntitySearchDirection.TO, EntityRelation.CONTAINS_TYPE)); cfg.setApplyExpressionToResolvedArguments(false); // arguments-only mode - Argument arg = new Argument(); - arg.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); - cfg.setArguments(Map.of("temperatureComputed", arg)); + Argument arg1 = new Argument(); + arg1.setRefEntityKey(new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null)); + Argument arg2 = new Argument(); + arg2.setRefEntityKey(new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null)); + + cfg.setArguments(Map.of("temperatureComputed", arg1, "humidityComputed", arg2)); TimeSeriesOutput output = new TimeSeriesOutput(); output.setStrategy(new TimeSeriesImmediateOutputStrategy(0, true, true, true, true)); @@ -1197,14 +1203,18 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed"); - ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed"); + ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed,humidityComputed"); + ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry1).isNotNull(); assertThat(telemetry2).isNotNull(); assertThat(telemetry1.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); assertThat(telemetry1.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(12.5); + assertThat(telemetry1.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry1.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); assertThat(telemetry2.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); assertThat(telemetry2.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(12.5); + assertThat(telemetry2.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry2.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); String deleteUrl = String.format("/api/v2/relation?fromId=%s&fromType=%s&relationType=%s&toId=%s&toType=%s", @@ -1212,10 +1222,10 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes EntityRelation.CONTAINS_TYPE, device.getId().getId(), EntityType.DEVICE ); doDelete(deleteUrl).andExpect(status().isOk()); - doDelete("/api/plugins/telemetry/ASSET/" + asset1.getId() + "/timeseries/delete?keys=temperatureComputed&deleteAllDataForKeys=true").andExpect(status().isOk()); + doDelete("/api/plugins/telemetry/ASSET/" + asset1.getId() + "/timeseries/delete?keys=temperatureComputed,humidityComputed&deleteAllDataForKeys=true").andExpect(status().isOk()); - // Update telemetry on device - long newTs = System.currentTimeMillis() - 300000L; + // Update telemetry on the device + long newTs = ts + 300000L; postTelemetry(device.getId(), String.format("{\"ts\": %s, \"values\": {\"temperature\":25}}", newTs)); // --- Assert propagated calculation (arguments-only mode after update) --- @@ -1223,13 +1233,18 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed"); - ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed"); + ObjectNode telemetry1 = getLatestTelemetry(asset1.getId(), "temperatureComputed,humidityComputed"); + ObjectNode telemetry2 = getLatestTelemetry(asset2.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry1).isNotNull(); assertThat(telemetry2).isNotNull(); assertThat(telemetry1.get("temperatureComputed").get(0).get("value")).isEqualTo(NullNode.instance); + assertThat(telemetry1.get("humidityComputed").get(0).get("value")).isEqualTo(NullNode.instance); + assertThat(telemetry2.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); assertThat(telemetry2.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(25); + // TS for humidity is not updated -> expected + assertThat(telemetry2.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(ts)); + assertThat(telemetry2.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); Asset asset3 = createAsset("Propagated Asset 3", null); @@ -1241,10 +1256,12 @@ public class CalculatedFieldIntegrationTest extends CalculatedFieldControllerTes .atMost(TIMEOUT, TimeUnit.SECONDS) .pollInterval(POLL_INTERVAL, TimeUnit.SECONDS) .untilAsserted(() -> { - ObjectNode telemetry = getLatestTelemetry(asset3.getId(), "temperatureComputed"); + ObjectNode telemetry = getLatestTelemetry(asset3.getId(), "temperatureComputed,humidityComputed"); assertThat(telemetry).isNotNull(); assertThat(telemetry.get("temperatureComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); assertThat(telemetry.get("temperatureComputed").get(0).get("value").asDouble()).isEqualTo(25); + assertThat(telemetry.get("humidityComputed").get(0).get("ts").asText()).isEqualTo(Long.toString(newTs)); + assertThat(telemetry.get("humidityComputed").get(0).get("value").asDouble()).isEqualTo(85); }); } diff --git a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java index add6c1ee39..346dbbcb7c 100644 --- a/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java +++ b/application/src/test/java/org/thingsboard/server/service/cf/ctx/state/PropagationCalculatedFieldStateTest.java @@ -70,17 +70,26 @@ import static org.thingsboard.server.common.data.cf.configuration.PropagationCal public class PropagationCalculatedFieldStateTest { private static final String TEMPERATURE_ARGUMENT_NAME = "t"; + private static final String HUMIDITY_ARGUMENT_NAME = "h"; private static final String TEST_RESULT_EXPRESSION_KEY = "testResult"; private static final double TEMPERATURE_VALUE = 12.5; + private static final double HUMIDITY_VALUE = 85; + + private static final PropagationArgumentEntry EMPTY_PROPAGATION_ARGUMENT = new PropagationArgumentEntry(Collections.emptyList()); private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("6c3513cb-85e7-4510-8746-1ba01859a8ce")); private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("be960a50-c029-4698-b2ec-c56a543c561c")); private final AssetId ASSET_ID_1 = new AssetId(UUID.fromString("d26f0e5b-7d7d-4a61-9f5e-08ab97b30734")); private final AssetId ASSET_ID_2 = new AssetId(UUID.fromString("1933a317-4df5-4d36-9800-68aded74579b")); - private final SingleValueArgumentEntry singleValueArgEntry = + private final SingleValueArgumentEntry EMPTY_SINGLE_VALUE_ARGUMENT = new SingleValueArgumentEntry(); + + private final SingleValueArgumentEntry temperatureArgumentEntry = new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("temperature", TEMPERATURE_VALUE), 99L); + private final SingleValueArgumentEntry humidityArgumentEntry = + new SingleValueArgumentEntry(System.currentTimeMillis(), new DoubleDataEntry("humidity", HUMIDITY_VALUE), 99L); + private final PropagationArgumentEntry propagationArgEntry = new PropagationArgumentEntry(new ArrayList<>(List.of(ASSET_ID_2, ASSET_ID_1))); @@ -121,7 +130,7 @@ public class PropagationCalculatedFieldStateTest { @Test void testInitAddsRequiredArgument() { initCtxAndState(false); - assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME, PROPAGATION_CONFIG_ARGUMENT); + assertThat(state.getRequiredArguments()).containsExactlyInAnyOrder(TEMPERATURE_ARGUMENT_NAME, HUMIDITY_ARGUMENT_NAME, PROPAGATION_CONFIG_ARGUMENT); } @Test @@ -133,7 +142,7 @@ public class PropagationCalculatedFieldStateTest { private static Stream provideInvalidPropagationArgs() { return Stream.of( null, - new PropagationArgumentEntry(Collections.emptyList()) + EMPTY_PROPAGATION_ARGUMENT ); } @@ -143,7 +152,8 @@ public class PropagationCalculatedFieldStateTest { initCtxAndState(false); Map args = new HashMap<>(); - args.put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); // Valid user arg + args.put(TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry); // Valid user arg + args.put(HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry); // Valid user arg if (propagationEntry != null) { args.put(PROPAGATION_CONFIG_ARGUMENT, propagationEntry); @@ -155,19 +165,54 @@ public class PropagationCalculatedFieldStateTest { } @Test - void testIsReadyWhenPropagationArgHasEntities() { + void testIsReadyWithoutExpressionWhenAllArgumentsAreNotEmpty() { initCtxAndState(false); - state.update(Map.of(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry, PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry), ctx); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(updatedArgs, ctx); assertThat(state.isReady()).isTrue(); assertThat(state.getReadinessStatus().errorMsg()).isNull(); } + @Test + void testIsReadyWithoutExpressionWhenAtLeastOneArgumentIsNotEmpty() { + initCtxAndState(false); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(updatedArgs, ctx); + assertThat(state.isReady()).isTrue(); + assertThat(state.getReadinessStatus().errorMsg()).isNull(); + } + + @Test + void testIsNotReadyWithExpressionWhenAtLeastOneArgumentIsEmpty() { + initCtxAndState(true); + Map updatedArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(updatedArgs, ctx); + assertThat(state.isReady()).isFalse(); + assertThat(state.getReadinessStatus().errorMsg()).isEqualTo("Required arguments are missing: h"); + } + @Test void testPerformCalculationWithEmptyPropagationArg() throws Exception { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, new PropagationArgumentEntry(Collections.emptyList())); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, EMPTY_PROPAGATION_ARGUMENT); + state.update(initArgs, ctx); + assertThat(state.isReady()).isFalse(); + // test empty propagation argument calculation PropagationCalculatedFieldResult result = performCalculation(); assertThat(result).isNotNull(); @@ -178,8 +223,12 @@ public class PropagationCalculatedFieldStateTest { @Test void testPerformCalculationWithArgumentsOnlyMode() throws Exception { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); PropagationCalculatedFieldResult propagationResult = performCalculation(); @@ -193,7 +242,7 @@ public class PropagationCalculatedFieldStateTest { assertThat(result.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE); ObjectNode expectedNode = JacksonUtil.newObjectNode(); - JacksonUtil.addKvEntry(expectedNode, singleValueArgEntry.getKvEntryValue(), TEMPERATURE_ARGUMENT_NAME); + JacksonUtil.addKvEntry(expectedNode, temperatureArgumentEntry.getKvEntryValue(), TEMPERATURE_ARGUMENT_NAME); assertThat(result.getResult()).isEqualTo(expectedNode); } @@ -201,9 +250,13 @@ public class PropagationCalculatedFieldStateTest { @Test void testPerformCalculationWithExpressionResultMode() throws Exception { initCtxAndState(true); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); - + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, humidityArgumentEntry, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); PropagationCalculatedFieldResult propagationResult = performCalculation(); assertThat(propagationResult).isNotNull(); @@ -216,7 +269,7 @@ public class PropagationCalculatedFieldStateTest { assertThat(result.getScope()).isEqualTo(AttributeScope.SERVER_SCOPE); ObjectNode expectedNode = JacksonUtil.newObjectNode(); - expectedNode.put(TEST_RESULT_EXPRESSION_KEY, TEMPERATURE_VALUE * 2); + expectedNode.put(TEST_RESULT_EXPRESSION_KEY, (TEMPERATURE_VALUE + HUMIDITY_VALUE) / 2); assertThat(result.getResult()).isEqualTo(expectedNode); } @@ -224,8 +277,13 @@ public class PropagationCalculatedFieldStateTest { @Test void testPropagationWithUpdatedPropagationArgument() throws ExecutionException, InterruptedException { initCtxAndState(false); - state.getArguments().put(PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry); - state.getArguments().put(TEMPERATURE_ARGUMENT_NAME, singleValueArgEntry); + Map initArgs = Map.of( + TEMPERATURE_ARGUMENT_NAME, temperatureArgumentEntry, + HUMIDITY_ARGUMENT_NAME, EMPTY_SINGLE_VALUE_ARGUMENT, + PROPAGATION_CONFIG_ARGUMENT, propagationArgEntry + ); + state.update(initArgs, ctx); + assertThat(state.isReady()).isTrue(); AssetId newEntityId = new AssetId(UUID.fromString("83e2c962-eeae-4708-984e-e6a24760f9c3")); PropagationArgumentEntry propagationArgumentEntry = new PropagationArgumentEntry(); @@ -236,6 +294,9 @@ public class PropagationCalculatedFieldStateTest { PropagationCalculatedFieldResult propagationCalculatedFieldResult = performCalculation(updated); assertThat(propagationCalculatedFieldResult).isNotNull(); assertThat(propagationCalculatedFieldResult.getEntityIds()).isNotNull().containsExactly(newEntityId); + assertThat(propagationCalculatedFieldResult.getResult()).isNotNull(); + assertThat(propagationCalculatedFieldResult.getResult().getResult()).isNotNull(); + assertThat(propagationCalculatedFieldResult.getResult().getResult()).isEqualTo(JacksonUtil.newObjectNode().put(TEMPERATURE_ARGUMENT_NAME, TEMPERATURE_VALUE)); } private CalculatedField getCalculatedField(boolean applyExpressionToResolvedArguments) { @@ -260,8 +321,12 @@ public class PropagationCalculatedFieldStateTest { ReferencedEntityKey tempKey = new ReferencedEntityKey("temperature", ArgumentType.TS_LATEST, null); temperatureArg.setRefEntityKey(tempKey); - config.setArguments(Map.of(TEMPERATURE_ARGUMENT_NAME, temperatureArg)); - config.setExpression("{" + TEST_RESULT_EXPRESSION_KEY + ": " + TEMPERATURE_ARGUMENT_NAME + " * 2}"); + Argument humidityArg = new Argument(); + ReferencedEntityKey humidityKey = new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null); + humidityArg.setRefEntityKey(humidityKey); + + config.setArguments(Map.of(TEMPERATURE_ARGUMENT_NAME, temperatureArg, HUMIDITY_ARGUMENT_NAME, humidityArg)); + config.setExpression("return { " + TEST_RESULT_EXPRESSION_KEY + ": (" + TEMPERATURE_ARGUMENT_NAME + " + " + HUMIDITY_ARGUMENT_NAME + ") / 2};"); AttributesOutput output = new AttributesOutput(); output.setScope(AttributeScope.SERVER_SCOPE);