dashboardjavacloudcoapiotiot-analyticsiot-platformiot-solutionskafkalwm2mmicroservicesmiddlewaremqttnettyplatformsnmpthingsboardvisualizationwebsocketswidgets
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
339 lines
15 KiB
339 lines
15 KiB
/**
|
|
* Copyright © 2016-2022 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 math;
|
|
|
|
import com.datastax.oss.driver.api.core.uuid.Uuids;
|
|
import com.google.common.util.concurrent.Futures;
|
|
import org.junit.After;
|
|
import org.junit.Assert;
|
|
import org.junit.Before;
|
|
import org.junit.Test;
|
|
import org.junit.runner.RunWith;
|
|
import org.mockito.ArgumentCaptor;
|
|
import org.mockito.Mock;
|
|
import org.mockito.Mockito;
|
|
import org.mockito.junit.MockitoJUnitRunner;
|
|
import org.thingsboard.common.util.AbstractListeningExecutor;
|
|
import org.thingsboard.common.util.JacksonUtil;
|
|
import org.thingsboard.rule.engine.api.TbContext;
|
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
|
|
import org.thingsboard.rule.engine.api.TbNodeException;
|
|
import org.thingsboard.rule.engine.math.TbMathArgument;
|
|
import org.thingsboard.rule.engine.math.TbMathArgumentType;
|
|
import org.thingsboard.rule.engine.math.TbMathNode;
|
|
import org.thingsboard.rule.engine.math.TbMathNodeConfiguration;
|
|
import org.thingsboard.rule.engine.math.TbMathResult;
|
|
import org.thingsboard.rule.engine.math.TbRuleNodeMathFunctionType;
|
|
import org.thingsboard.server.common.data.DataConstants;
|
|
import org.thingsboard.server.common.data.id.DeviceId;
|
|
import org.thingsboard.server.common.data.id.EntityId;
|
|
import org.thingsboard.server.common.data.id.TenantId;
|
|
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
|
|
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
|
|
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
|
|
import org.thingsboard.server.common.data.kv.LongDataEntry;
|
|
import org.thingsboard.server.common.msg.TbMsg;
|
|
import org.thingsboard.server.common.msg.TbMsgMetaData;
|
|
import org.thingsboard.server.dao.attributes.AttributesService;
|
|
import org.thingsboard.server.dao.timeseries.TimeseriesService;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Optional;
|
|
|
|
import static org.mockito.Mockito.lenient;
|
|
|
|
@RunWith(MockitoJUnitRunner.class)
|
|
public class TbMathNodeTest {
|
|
|
|
private EntityId originator = new DeviceId(Uuids.timeBased());
|
|
private TenantId tenantId = TenantId.fromUUID(Uuids.timeBased());
|
|
|
|
@Mock
|
|
private TbContext ctx;
|
|
@Mock
|
|
private AttributesService attributesService;
|
|
@Mock
|
|
private TimeseriesService tsService;
|
|
|
|
private AbstractListeningExecutor dbExecutor;
|
|
|
|
@Before
|
|
public void before() {
|
|
dbExecutor = new AbstractListeningExecutor() {
|
|
@Override
|
|
protected int getThreadPollSize() {
|
|
return 3;
|
|
}
|
|
};
|
|
dbExecutor.init();
|
|
initMocks();
|
|
}
|
|
|
|
@After
|
|
public void after() {
|
|
dbExecutor.destroy();
|
|
}
|
|
|
|
private void initMocks() {
|
|
Mockito.reset(ctx);
|
|
Mockito.reset(attributesService);
|
|
Mockito.reset(tsService);
|
|
lenient().when(ctx.getAttributesService()).thenReturn(attributesService);
|
|
lenient().when(ctx.getTimeseriesService()).thenReturn(tsService);
|
|
lenient().when(ctx.getTenantId()).thenReturn(tenantId);
|
|
lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor);
|
|
}
|
|
|
|
private TbMathNode initNode(TbRuleNodeMathFunctionType operation, TbMathResult result, TbMathArgument... arguments) {
|
|
return initNode(operation, null, result, arguments);
|
|
}
|
|
|
|
private TbMathNode initNodeWithCustomFunction(String expression, TbMathResult result, TbMathArgument... arguments) {
|
|
return initNode(TbRuleNodeMathFunctionType.CUSTOM, expression, result, arguments);
|
|
}
|
|
|
|
private TbMathNode initNode(TbRuleNodeMathFunctionType operation, String expression, TbMathResult result, TbMathArgument... arguments) {
|
|
try {
|
|
TbMathNodeConfiguration configuration = new TbMathNodeConfiguration();
|
|
configuration.setOperation(operation);
|
|
if (TbRuleNodeMathFunctionType.CUSTOM.equals(operation)) {
|
|
configuration.setCustomFunction(expression);
|
|
}
|
|
configuration.setResult(result);
|
|
configuration.setArguments(Arrays.asList(arguments));
|
|
TbMathNode node = new TbMathNode();
|
|
node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(configuration)));
|
|
return node;
|
|
} catch (TbNodeException ex) {
|
|
throw new IllegalStateException(ex);
|
|
}
|
|
}
|
|
|
|
@Test
|
|
public void testExp4j() {
|
|
var node = initNodeWithCustomFunction("2a+3b",
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(10, resultJson.get("result").asInt());
|
|
}
|
|
|
|
@Test
|
|
public void testSimpleFunctions() {
|
|
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.ADD, 2.1, 2.2, 4.3);
|
|
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.SUB, 2.1, 2.2, -0.1);
|
|
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MULT, 2.1, 2.0, 4.2);
|
|
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.DIV, 4.2, 2.0, 2.1);
|
|
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(30), 0.5);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(90), 1.0);
|
|
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SINH, Math.toRadians(0), 0.0);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COSH, Math.toRadians(0), 1.0);
|
|
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COS, Math.toRadians(60), 0.5);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COS, Math.toRadians(0), 1.0);
|
|
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(45), 1);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(0), 0);
|
|
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ABS, -1, 1);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SQRT, 4, 2);
|
|
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.CBRT, 8, 2);
|
|
}
|
|
|
|
private void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) {
|
|
initMocks();
|
|
|
|
var node = initNode(function,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).put("b", arg2).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000).times(1)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d);
|
|
}
|
|
|
|
private void testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double result) {
|
|
initMocks();
|
|
|
|
var node = initNode(function,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", arg1).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d);
|
|
}
|
|
|
|
@Test
|
|
public void test_2_plus_2_body() {
|
|
var node = initNode(TbRuleNodeMathFunctionType.ADD,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(4, resultJson.get("result").asInt());
|
|
}
|
|
|
|
@Test
|
|
public void test_2_plus_2_meta() {
|
|
var node = initNode(TbRuleNodeMathFunctionType.ADD,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 0, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "b")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 2).put("b", 2).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
Assert.assertNotNull(resultMsg.getMetaData());
|
|
var result = resultMsg.getMetaData().getValue("result");
|
|
Assert.assertNotNull(result);
|
|
Assert.assertEquals("4", result);
|
|
}
|
|
|
|
@Test
|
|
public void test_2_plus_2_attr_and_ts() {
|
|
var node = initNode(TbRuleNodeMathFunctionType.ADD,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.ATTRIBUTE, "a"),
|
|
new TbMathArgument(TbMathArgumentType.TIME_SERIES, "b")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().toString());
|
|
|
|
Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a"))
|
|
.thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0)))));
|
|
|
|
Mockito.when(tsService.findLatest(tenantId, originator, "b"))
|
|
.thenReturn(Futures.immediateFuture(Optional.of(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("b", 2L)))));
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(4, resultJson.get("result").asInt());
|
|
}
|
|
|
|
@Test
|
|
public void test_sqrt_5_body() {
|
|
var node = initNode(TbRuleNodeMathFunctionType.SQRT,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 3, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
|
|
Assert.assertTrue(resultJson.has("result"));
|
|
Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
|
|
}
|
|
|
|
@Test
|
|
public void test_sqrt_5_meta() {
|
|
var node = initNode(TbRuleNodeMathFunctionType.SQRT,
|
|
new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null),
|
|
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
|
|
);
|
|
|
|
TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString());
|
|
|
|
node.onMsg(ctx, msg);
|
|
|
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
|
|
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
|
|
|
|
TbMsg resultMsg = msgCaptor.getValue();
|
|
Assert.assertNotNull(resultMsg);
|
|
Assert.assertNotNull(resultMsg.getData());
|
|
var result = resultMsg.getMetaData().getValue("result");
|
|
Assert.assertNotNull(result);
|
|
Assert.assertEquals("2.236", result);
|
|
}
|
|
|
|
}
|
|
|