13 changed files with 725 additions and 56 deletions
@ -0,0 +1,238 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.cf.ctx.state; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.boot.test.context.SpringBootTest; |
||||
|
import org.thingsboard.script.api.tbel.DefaultTbelInvokeService; |
||||
|
import org.thingsboard.script.api.tbel.TbelInvokeService; |
||||
|
import org.thingsboard.server.common.data.AttributeScope; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
||||
|
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.Output; |
||||
|
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.SimpleCalculatedFieldConfiguration; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.service.cf.CalculatedFieldResult; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.TreeMap; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
|
||||
|
@SpringBootTest(classes = DefaultTbelInvokeService.class) |
||||
|
public class ScriptCalculatedFieldStateTest { |
||||
|
|
||||
|
private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("5b18e321-3327-4290-b996-d72a65e90382")); |
||||
|
private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("5512071d-5abc-411d-a907-4cdb6539c2eb")); |
||||
|
private final AssetId ASSET_ID = new AssetId(UUID.fromString("5bc010ae-bcfd-46c8-98b9-8ee8c8955a76")); |
||||
|
|
||||
|
private final SingleValueArgumentEntry assetHumidityArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 10, 43, 122L); |
||||
|
private final TsRollingArgumentEntry deviceTemperatureArgEntry = createRollingArgEntry(); |
||||
|
|
||||
|
private final long ts = System.currentTimeMillis(); |
||||
|
|
||||
|
private ScriptCalculatedFieldState state; |
||||
|
private CalculatedFieldCtx ctx; |
||||
|
|
||||
|
@Autowired |
||||
|
private TbelInvokeService tbelInvokeService; |
||||
|
|
||||
|
@BeforeEach |
||||
|
void setUp() { |
||||
|
ctx = new CalculatedFieldCtx(getCalculatedField(), tbelInvokeService); |
||||
|
state = new ScriptCalculatedFieldState(ctx.getArgNames()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testType() { |
||||
|
assertThat(state.getType()).isEqualTo(CalculatedFieldType.SCRIPT); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateState() { |
||||
|
state.arguments = new HashMap<>(Map.of("assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
Map<String, ArgumentEntry> newArgs = Map.of("deviceTemperature", deviceTemperatureArgEntry); |
||||
|
boolean stateUpdated = state.updateState(newArgs); |
||||
|
|
||||
|
assertThat(stateUpdated).isTrue(); |
||||
|
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf( |
||||
|
Map.of( |
||||
|
"assetHumidity", assetHumidityArgEntry, |
||||
|
"deviceTemperature", deviceTemperatureArgEntry |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateStateWhenUpdateExistingEntry() { |
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
SingleValueArgumentEntry newArgEntry = new SingleValueArgumentEntry(ts, 41, 349L); |
||||
|
Map<String, ArgumentEntry> newArgs = Map.of("assetHumidity", newArgEntry); |
||||
|
boolean stateUpdated = state.updateState(newArgs); |
||||
|
|
||||
|
assertThat(stateUpdated).isTrue(); |
||||
|
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf( |
||||
|
Map.of( |
||||
|
"assetHumidity", newArgEntry, |
||||
|
"deviceTemperature", deviceTemperatureArgEntry |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testPerformCalculation() throws ExecutionException, InterruptedException { |
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
CalculatedFieldResult result = state.performCalculation(ctx).get(); |
||||
|
|
||||
|
assertThat(result).isNotNull(); |
||||
|
Output output = getCalculatedFieldConfig().getOutput(); |
||||
|
assertThat(result.getType()).isEqualTo(output.getType()); |
||||
|
assertThat(result.getScope()).isEqualTo(output.getScope()); |
||||
|
assertThat(result.getResultMap()).isEqualTo(Map.of("averageDeviceTemperature", 13.0, "assetHumidity", 43)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testPerformCalculationWhenOldTelemetry() throws ExecutionException, InterruptedException { |
||||
|
TsRollingArgumentEntry argumentEntry = new TsRollingArgumentEntry(); |
||||
|
|
||||
|
TreeMap<Long, Object> values = new TreeMap<>(); |
||||
|
values.put(ts - 40000, 4);// will not be used for calculation
|
||||
|
values.put(ts - 45000, 2);// will not be used for calculation
|
||||
|
values.put(ts - 20, 0); |
||||
|
|
||||
|
argumentEntry.setTsRecords(values); |
||||
|
|
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", argumentEntry, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
CalculatedFieldResult result = state.performCalculation(ctx).get(); |
||||
|
|
||||
|
assertThat(result).isNotNull(); |
||||
|
Output output = getCalculatedFieldConfig().getOutput(); |
||||
|
assertThat(result.getType()).isEqualTo(output.getType()); |
||||
|
assertThat(result.getScope()).isEqualTo(output.getScope()); |
||||
|
assertThat(result.getResultMap()).isEqualTo(Map.of("averageDeviceTemperature", 0.0, "assetHumidity", 43)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testPerformCalculationWhenArgumentsMoreThanLimit() throws ExecutionException, InterruptedException { |
||||
|
TsRollingArgumentEntry argumentEntry = new TsRollingArgumentEntry(); |
||||
|
TreeMap<Long, Object> values = new TreeMap<>(); |
||||
|
values.put(ts - 20, 1000);// will not be used
|
||||
|
values.put(ts - 18, 0); |
||||
|
values.put(ts - 16, 0); |
||||
|
values.put(ts - 14, 0); |
||||
|
values.put(ts - 12, 0); |
||||
|
values.put(ts - 10, 0); |
||||
|
argumentEntry.setTsRecords(values); |
||||
|
|
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", argumentEntry, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
CalculatedFieldResult result = state.performCalculation(ctx).get(); |
||||
|
|
||||
|
assertThat(result).isNotNull(); |
||||
|
Output output = getCalculatedFieldConfig().getOutput(); |
||||
|
assertThat(result.getType()).isEqualTo(output.getType()); |
||||
|
assertThat(result.getScope()).isEqualTo(output.getScope()); |
||||
|
assertThat(result.getResultMap()).isEqualTo(Map.of("averageDeviceTemperature", 0.0, "assetHumidity", 43)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenNotAllArgPresent() { |
||||
|
assertThat(state.isReady()).isFalse(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenAllArgPresent() { |
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", deviceTemperatureArgEntry, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
assertThat(state.isReady()).isTrue(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenEmptyEntryPresents() { |
||||
|
state.arguments = new HashMap<>(Map.of("deviceTemperature", TsRollingArgumentEntry.EMPTY, "assetHumidity", assetHumidityArgEntry)); |
||||
|
|
||||
|
assertThat(state.isReady()).isFalse(); |
||||
|
} |
||||
|
|
||||
|
private TsRollingArgumentEntry createRollingArgEntry() { |
||||
|
TsRollingArgumentEntry argumentEntry = new TsRollingArgumentEntry(); |
||||
|
long ts = System.currentTimeMillis(); |
||||
|
|
||||
|
TreeMap<Long, Object> values = new TreeMap<>(); |
||||
|
values.put(ts - 40, 10); |
||||
|
values.put(ts - 30, 12); |
||||
|
values.put(ts - 20, 17); |
||||
|
|
||||
|
argumentEntry.setTsRecords(values); |
||||
|
return argumentEntry; |
||||
|
} |
||||
|
|
||||
|
private CalculatedField getCalculatedField() { |
||||
|
CalculatedField calculatedField = new CalculatedField(); |
||||
|
calculatedField.setTenantId(TENANT_ID); |
||||
|
calculatedField.setEntityId(ASSET_ID); |
||||
|
calculatedField.setType(CalculatedFieldType.SCRIPT); |
||||
|
calculatedField.setName("Test Calculated Field"); |
||||
|
calculatedField.setConfigurationVersion(1); |
||||
|
calculatedField.setConfiguration(getCalculatedFieldConfig()); |
||||
|
calculatedField.setVersion(1L); |
||||
|
return calculatedField; |
||||
|
} |
||||
|
|
||||
|
private CalculatedFieldConfiguration getCalculatedFieldConfig() { |
||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
||||
|
|
||||
|
Argument argument1 = new Argument(); |
||||
|
argument1.setRefEntityId(DEVICE_ID); |
||||
|
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("temperature", ArgumentType.TS_ROLLING, null); |
||||
|
argument1.setRefEntityKey(refEntityKey1); |
||||
|
argument1.setLimit(5); |
||||
|
argument1.setTimeWindow(30000); |
||||
|
|
||||
|
Argument argument2 = new Argument(); |
||||
|
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("humidity", ArgumentType.TS_LATEST, null); |
||||
|
argument1.setRefEntityKey(refEntityKey2); |
||||
|
|
||||
|
config.setArguments(Map.of("deviceTemperature", argument1, "assetHumidity", argument2)); |
||||
|
|
||||
|
config.setExpression("var result = 0; foreach(element : deviceTemperature.entrySet()) { result += element.getValue(); } var map = {}; map.put(\"averageDeviceTemperature\", result / deviceTemperature.size()); map.put(\"assetHumidity\", assetHumidity); return map;"); |
||||
|
|
||||
|
Output output = new Output(); |
||||
|
output.setType(OutputType.ATTRIBUTES); |
||||
|
output.setScope(AttributeScope.SERVER_SCOPE); |
||||
|
|
||||
|
config.setOutput(output); |
||||
|
|
||||
|
return config; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,213 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.cf.ctx.state; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.thingsboard.server.common.data.AttributeScope; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedField; |
||||
|
import org.thingsboard.server.common.data.cf.CalculatedFieldType; |
||||
|
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.Output; |
||||
|
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.SimpleCalculatedFieldConfiguration; |
||||
|
import org.thingsboard.server.common.data.id.AssetId; |
||||
|
import org.thingsboard.server.common.data.id.DeviceId; |
||||
|
import org.thingsboard.server.common.data.id.TenantId; |
||||
|
import org.thingsboard.server.service.cf.CalculatedFieldResult; |
||||
|
|
||||
|
import java.util.HashMap; |
||||
|
import java.util.Map; |
||||
|
import java.util.UUID; |
||||
|
import java.util.concurrent.ExecutionException; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
|
||||
|
public class SimpleCalculatedFieldStateTest { |
||||
|
|
||||
|
private final TenantId TENANT_ID = TenantId.fromUUID(UUID.fromString("5b18e321-3327-4290-b996-d72a65e90382")); |
||||
|
private final DeviceId DEVICE_ID = new DeviceId(UUID.fromString("5512071d-5abc-411d-a907-4cdb6539c2eb")); |
||||
|
private final AssetId ASSET_ID = new AssetId(UUID.fromString("5bc010ae-bcfd-46c8-98b9-8ee8c8955a76")); |
||||
|
|
||||
|
private final SingleValueArgumentEntry key1ArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 10, 11, 145L); |
||||
|
private final SingleValueArgumentEntry key2ArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 6, 15, 165L); |
||||
|
private final SingleValueArgumentEntry key3ArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis() - 3, 23, 184L); |
||||
|
|
||||
|
private SimpleCalculatedFieldState state; |
||||
|
private CalculatedFieldCtx ctx; |
||||
|
|
||||
|
@BeforeEach |
||||
|
void setUp() { |
||||
|
ctx = new CalculatedFieldCtx(getCalculatedField(), null); |
||||
|
state = new SimpleCalculatedFieldState(ctx.getArgNames()); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testType() { |
||||
|
assertThat(state.getType()).isEqualTo(CalculatedFieldType.SIMPLE); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateState() { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry |
||||
|
)); |
||||
|
|
||||
|
Map<String, ArgumentEntry> newArgs = Map.of("key3", key3ArgEntry); |
||||
|
boolean stateUpdated = state.updateState(newArgs); |
||||
|
|
||||
|
assertThat(stateUpdated).isTrue(); |
||||
|
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf( |
||||
|
Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry, |
||||
|
"key3", key3ArgEntry |
||||
|
) |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateStateWhenUpdateExistingEntry() { |
||||
|
state.arguments = new HashMap<>(Map.of("key1", key1ArgEntry)); |
||||
|
|
||||
|
SingleValueArgumentEntry newArgEntry = new SingleValueArgumentEntry(System.currentTimeMillis(), 18, 190L); |
||||
|
Map<String, ArgumentEntry> newArgs = Map.of("key1", newArgEntry); |
||||
|
boolean stateUpdated = state.updateState(newArgs); |
||||
|
|
||||
|
assertThat(stateUpdated).isTrue(); |
||||
|
assertThat(state.getArguments()).containsExactlyInAnyOrderEntriesOf(Map.of("key1", newArgEntry)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateStateWhenRollingEntryPassed() { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry |
||||
|
)); |
||||
|
|
||||
|
Map<String, ArgumentEntry> newArgs = Map.of("key3", TsRollingArgumentEntry.EMPTY); |
||||
|
assertThatThrownBy(() -> state.updateState(newArgs)) |
||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||
|
.hasMessage("Rolling argument entry is not supported for simple calculated fields."); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testPerformCalculation() throws ExecutionException, InterruptedException { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry, |
||||
|
"key3", key3ArgEntry |
||||
|
)); |
||||
|
|
||||
|
CalculatedFieldResult result = state.performCalculation(ctx).get(); |
||||
|
|
||||
|
assertThat(result).isNotNull(); |
||||
|
Output output = getCalculatedFieldConfig().getOutput(); |
||||
|
assertThat(result.getType()).isEqualTo(output.getType()); |
||||
|
assertThat(result.getScope()).isEqualTo(output.getScope()); |
||||
|
assertThat(result.getResultMap()).isEqualTo(Map.of("output", 49.0)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testPerformCalculationWhenPassedNotNumber() { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", new SingleValueArgumentEntry(System.currentTimeMillis() - 9, "string", 124L), |
||||
|
"key3", key3ArgEntry |
||||
|
)); |
||||
|
|
||||
|
assertThatThrownBy(() -> state.performCalculation(ctx)) |
||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||
|
.hasMessage("Argument 'key2' is not a number."); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenNotAllArgPresent() { |
||||
|
assertThat(state.isReady()).isFalse(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenAllArgPresent() { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry, |
||||
|
"key3", key3ArgEntry |
||||
|
)); |
||||
|
|
||||
|
assertThat(state.isReady()).isTrue(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testIsReadyWhenEmptyEntryPresents() { |
||||
|
state.arguments = new HashMap<>(Map.of( |
||||
|
"key1", key1ArgEntry, |
||||
|
"key2", key2ArgEntry |
||||
|
)); |
||||
|
state.getArguments().put("key3", SingleValueArgumentEntry.EMPTY); |
||||
|
|
||||
|
assertThat(state.isReady()).isFalse(); |
||||
|
} |
||||
|
|
||||
|
private CalculatedField getCalculatedField() { |
||||
|
CalculatedField calculatedField = new CalculatedField(); |
||||
|
calculatedField.setTenantId(TENANT_ID); |
||||
|
calculatedField.setEntityId(DEVICE_ID); |
||||
|
calculatedField.setType(CalculatedFieldType.SIMPLE); |
||||
|
calculatedField.setName("Test Calculated Field"); |
||||
|
calculatedField.setConfigurationVersion(1); |
||||
|
calculatedField.setConfiguration(getCalculatedFieldConfig()); |
||||
|
calculatedField.setVersion(1L); |
||||
|
return calculatedField; |
||||
|
} |
||||
|
|
||||
|
private CalculatedFieldConfiguration getCalculatedFieldConfig() { |
||||
|
SimpleCalculatedFieldConfiguration config = new SimpleCalculatedFieldConfiguration(); |
||||
|
|
||||
|
Argument argument1 = new Argument(); |
||||
|
argument1.setRefEntityId(ASSET_ID); |
||||
|
ReferencedEntityKey refEntityKey1 = new ReferencedEntityKey("temp1", ArgumentType.TS_LATEST, null); |
||||
|
argument1.setRefEntityKey(refEntityKey1); |
||||
|
|
||||
|
Argument argument2 = new Argument(); |
||||
|
argument2.setRefEntityId(ASSET_ID); |
||||
|
ReferencedEntityKey refEntityKey2 = new ReferencedEntityKey("temp2", ArgumentType.ATTRIBUTE, null); |
||||
|
argument2.setRefEntityKey(refEntityKey2); |
||||
|
|
||||
|
Argument argument3 = new Argument(); |
||||
|
argument3.setRefEntityId(ASSET_ID); |
||||
|
ReferencedEntityKey refEntityKey3 = new ReferencedEntityKey("temp3", ArgumentType.TS_LATEST, null); |
||||
|
argument3.setRefEntityKey(refEntityKey3); |
||||
|
|
||||
|
config.setArguments(Map.of("key1", argument1, "key2", argument2, "key3", argument3)); |
||||
|
|
||||
|
config.setExpression("key1 + key2 + key3"); |
||||
|
|
||||
|
Output output = new Output(); |
||||
|
output.setName("output"); |
||||
|
output.setType(OutputType.ATTRIBUTES); |
||||
|
output.setScope(AttributeScope.SERVER_SCOPE); |
||||
|
|
||||
|
config.setOutput(output); |
||||
|
|
||||
|
return config; |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,71 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.cf.ctx.state; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
|
||||
|
public class SingleValueArgumentEntryTest { |
||||
|
|
||||
|
private SingleValueArgumentEntry entry; |
||||
|
|
||||
|
private final long ts = System.currentTimeMillis(); |
||||
|
|
||||
|
@BeforeEach |
||||
|
void setUp() { |
||||
|
entry = new SingleValueArgumentEntry(ts, 11, 363L); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testArgumentEntryType() { |
||||
|
assertThat(entry.getType()).isEqualTo(ArgumentEntryType.SINGLE_VALUE); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenRollingEntryPassed() { |
||||
|
assertThatThrownBy(() -> entry.updateEntry(TsRollingArgumentEntry.EMPTY)) |
||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||
|
.hasMessage("Unsupported argument entry type for single value argument entry: " + ArgumentEntryType.TS_ROLLING); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWithThaSameTs() { |
||||
|
assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts, 13, 363L))).isFalse(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenNewVersionIsNull() { |
||||
|
assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 16, 13, null))).isTrue(); |
||||
|
assertThat(entry.getValue()).isEqualTo(13); |
||||
|
assertThat(entry.getVersion()).isNull(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenNewVersionIsGreaterThanCurrent() { |
||||
|
assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, 18, 369L))).isTrue(); |
||||
|
assertThat(entry.getValue()).isEqualTo(18); |
||||
|
assertThat(entry.getVersion()).isEqualTo(369L); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenNewVersionIsLessThanCurrent() { |
||||
|
assertThat(entry.updateEntry(new SingleValueArgumentEntry(ts + 18, 18, 234L))).isFalse(); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,93 @@ |
|||||
|
/** |
||||
|
* Copyright © 2016-2024 The Thingsboard Authors |
||||
|
* |
||||
|
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
|
* you may not use this file except in compliance with the License. |
||||
|
* You may obtain a copy of the License at |
||||
|
* |
||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
* |
||||
|
* Unless required by applicable law or agreed to in writing, software |
||||
|
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
|
* See the License for the specific language governing permissions and |
||||
|
* limitations under the License. |
||||
|
*/ |
||||
|
package org.thingsboard.server.service.cf.ctx.state; |
||||
|
|
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
|
||||
|
import java.util.Map; |
||||
|
import java.util.TreeMap; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
|
|
||||
|
public class TsRollingArgumentEntryTest { |
||||
|
|
||||
|
private TsRollingArgumentEntry entry; |
||||
|
|
||||
|
private final long ts = System.currentTimeMillis(); |
||||
|
|
||||
|
@BeforeEach |
||||
|
void setUp() { |
||||
|
TreeMap<Long, Object> values = new TreeMap<>(); |
||||
|
values.put(ts - 40, 10); |
||||
|
values.put(ts - 30, 12); |
||||
|
values.put(ts - 20, 17); |
||||
|
|
||||
|
entry = new TsRollingArgumentEntry(values); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testArgumentEntryType() { |
||||
|
assertThat(entry.getType()).isEqualTo(ArgumentEntryType.TS_ROLLING); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenSingleValueEntryPassed() { |
||||
|
SingleValueArgumentEntry newEntry = new SingleValueArgumentEntry(ts - 10, 23, 123L); |
||||
|
|
||||
|
assertThat(entry.updateEntry(newEntry)).isTrue(); |
||||
|
assertThat(entry.getTsRecords()).hasSize(4); |
||||
|
assertThat(entry.getTsRecords().get(ts - 10)).isEqualTo(23); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenSingleValueEntryWithTheSameTsPassed() { |
||||
|
SingleValueArgumentEntry newEntry = new SingleValueArgumentEntry(ts - 20, 23, 123L); |
||||
|
|
||||
|
assertThat(entry.updateEntry(newEntry)).isFalse(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenRollingEntryPassed() { |
||||
|
TsRollingArgumentEntry newEntry = new TsRollingArgumentEntry(); |
||||
|
TreeMap<Long, Object> values = new TreeMap<>(); |
||||
|
values.put(ts - 20, 16); |
||||
|
values.put(ts - 10, 7); |
||||
|
values.put(ts - 5, 1); |
||||
|
newEntry.setTsRecords(values); |
||||
|
|
||||
|
assertThat(entry.updateEntry(newEntry)).isTrue(); |
||||
|
assertThat(entry.getTsRecords()).hasSize(5); |
||||
|
assertThat(entry.getTsRecords()).isEqualTo(Map.of( |
||||
|
ts - 40, 10, |
||||
|
ts - 30, 12, |
||||
|
ts - 20, 17, |
||||
|
ts - 10, 7, |
||||
|
ts - 5, 1 |
||||
|
)); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void testUpdateEntryWhenValueIsNotNumber() { |
||||
|
SingleValueArgumentEntry newEntry = new SingleValueArgumentEntry(ts - 10, "string", 123L); |
||||
|
|
||||
|
assertThatThrownBy(() -> entry.updateEntry(newEntry)) |
||||
|
.isInstanceOf(IllegalArgumentException.class) |
||||
|
.hasMessage("Argument type " + ArgumentEntryType.TS_ROLLING + " only supports numeric values."); |
||||
|
} |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue