committed by
Dmytro Skarzhynets
4 changed files with 1446 additions and 643 deletions
@ -1,642 +0,0 @@ |
|||||
/** |
|
||||
* 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.rule.engine.action; |
|
||||
|
|
||||
import com.datastax.oss.driver.api.core.uuid.Uuids; |
|
||||
import com.fasterxml.jackson.databind.JsonNode; |
|
||||
import com.google.common.util.concurrent.Futures; |
|
||||
import org.apache.commons.lang3.NotImplementedException; |
|
||||
import org.junit.Before; |
|
||||
import org.junit.Test; |
|
||||
import org.junit.runner.RunWith; |
|
||||
import org.mockito.ArgumentCaptor; |
|
||||
import org.mockito.Captor; |
|
||||
import org.mockito.Mock; |
|
||||
import org.mockito.junit.MockitoJUnitRunner; |
|
||||
import org.thingsboard.common.util.JacksonUtil; |
|
||||
import org.thingsboard.common.util.ListeningExecutor; |
|
||||
import org.thingsboard.rule.engine.TestDbCallbackExecutor; |
|
||||
import org.thingsboard.rule.engine.api.RuleEngineAlarmService; |
|
||||
import org.thingsboard.rule.engine.api.ScriptEngine; |
|
||||
import org.thingsboard.rule.engine.api.TbContext; |
|
||||
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
|
||||
import org.thingsboard.rule.engine.api.TbNodeException; |
|
||||
import org.thingsboard.server.common.data.DataConstants; |
|
||||
import org.thingsboard.server.common.data.alarm.Alarm; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmCreateOrUpdateActiveRequest; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmInfo; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
|
||||
import org.thingsboard.server.common.data.alarm.AlarmUpdateRequest; |
|
||||
import org.thingsboard.server.common.data.id.AlarmId; |
|
||||
import org.thingsboard.server.common.data.id.DeviceId; |
|
||||
import org.thingsboard.server.common.data.id.EntityId; |
|
||||
import org.thingsboard.server.common.data.id.RuleChainId; |
|
||||
import org.thingsboard.server.common.data.id.RuleNodeId; |
|
||||
import org.thingsboard.server.common.data.id.TenantId; |
|
||||
import org.thingsboard.server.common.data.msg.TbMsgType; |
|
||||
import org.thingsboard.server.common.data.script.ScriptLanguage; |
|
||||
import org.thingsboard.server.common.msg.TbMsg; |
|
||||
import org.thingsboard.server.common.msg.TbMsgDataType; |
|
||||
import org.thingsboard.server.common.msg.TbMsgMetaData; |
|
||||
|
|
||||
import javax.script.ScriptException; |
|
||||
import java.io.IOException; |
|
||||
import java.util.function.Consumer; |
|
||||
|
|
||||
import static org.junit.Assert.assertEquals; |
|
||||
import static org.junit.Assert.assertNotSame; |
|
||||
import static org.junit.Assert.assertTrue; |
|
||||
import static org.mockito.ArgumentMatchers.any; |
|
||||
import static org.mockito.ArgumentMatchers.nullable; |
|
||||
import static org.mockito.Mockito.anyLong; |
|
||||
import static org.mockito.Mockito.atMost; |
|
||||
import static org.mockito.Mockito.eq; |
|
||||
import static org.mockito.Mockito.same; |
|
||||
import static org.mockito.Mockito.times; |
|
||||
import static org.mockito.Mockito.verify; |
|
||||
import static org.mockito.Mockito.verifyNoMoreInteractions; |
|
||||
import static org.mockito.Mockito.when; |
|
||||
|
|
||||
@RunWith(MockitoJUnitRunner.class) |
|
||||
public class TbAlarmNodeTest { |
|
||||
|
|
||||
private TbAbstractAlarmNode node; |
|
||||
|
|
||||
@Mock |
|
||||
private TbContext ctx; |
|
||||
@Mock |
|
||||
private RuleEngineAlarmService alarmService; |
|
||||
|
|
||||
@Mock |
|
||||
private ScriptEngine detailsJs; |
|
||||
|
|
||||
@Captor |
|
||||
private ArgumentCaptor<Runnable> successCaptor; |
|
||||
@Captor |
|
||||
private ArgumentCaptor<Consumer<Throwable>> failureCaptor; |
|
||||
|
|
||||
private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); |
|
||||
private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); |
|
||||
|
|
||||
private ListeningExecutor dbExecutor; |
|
||||
|
|
||||
private final EntityId originator = new DeviceId(Uuids.timeBased()); |
|
||||
private final EntityId alarmOriginator = new AlarmId(Uuids.timeBased()); |
|
||||
private final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); |
|
||||
private final TbMsgMetaData metaData = new TbMsgMetaData(); |
|
||||
private final String rawJson = "{\"name\": \"Vit\", \"passed\": 5}"; |
|
||||
|
|
||||
@Before |
|
||||
public void before() { |
|
||||
dbExecutor = new TestDbCallbackExecutor(); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void newAlarmCanBeCreated() { |
|
||||
initWithCreateAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
long ts = msg.getTs(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); |
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.startTs(ts) |
|
||||
.endTs(ts) |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.CRITICAL) |
|
||||
.propagate(true) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.build(); |
|
||||
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.created(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Created")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void buildDetailsThrowsException() { |
|
||||
initWithCreateAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFailedFuture(new NotImplementedException("message"))); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verifyError(msg, "message", NotImplementedException.class); |
|
||||
|
|
||||
verify(ctx).createScriptEngine(ScriptLanguage.JS, "DETAILS"); |
|
||||
verify(ctx).getAlarmService(); |
|
||||
verify(ctx, times(2)).getDbCallbackExecutor(); |
|
||||
verify(ctx).logJsEvalRequest(); |
|
||||
verify(ctx).getTenantId(); |
|
||||
verify(alarmService).findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType"); |
|
||||
|
|
||||
verifyNoMoreInteractions(ctx, alarmService); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void ifAlarmClearedCreateNew() { |
|
||||
initWithCreateAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
long ts = msg.getTs(); |
|
||||
Alarm clearedAlarm = Alarm.builder().cleared(true).acknowledged(true).build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(clearedAlarm); |
|
||||
|
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.startTs(ts) |
|
||||
.endTs(ts) |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.CRITICAL) |
|
||||
.propagate(true) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.build(); |
|
||||
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.created(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Created")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void alarmCanBeUpdated() { |
|
||||
initWithCreateAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
|
|
||||
long oldEndDate = System.currentTimeMillis(); |
|
||||
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm); |
|
||||
|
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.CRITICAL) |
|
||||
.propagate(true) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.endTs(activeAlarm.getEndTs()) |
|
||||
.build(); |
|
||||
when(alarmService.updateAlarm(any(AlarmUpdateRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.modified(true) |
|
||||
.old(new Alarm(activeAlarm)) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Updated")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_EXISTING_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertTrue(activeAlarm.getEndTs() >= oldEndDate); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void alarmCanBeCleared() { |
|
||||
initWithClearAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
|
|
||||
long oldEndDate = System.currentTimeMillis(); |
|
||||
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); |
|
||||
|
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.cleared(true) |
|
||||
.severity(AlarmSeverity.WARNING) |
|
||||
.propagate(false) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.endTs(oldEndDate) |
|
||||
.build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(activeAlarm); |
|
||||
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class))) |
|
||||
.thenReturn(AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.cleared(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Cleared")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void alarmCanBeClearedWithAlarmOriginator() throws ScriptException, IOException { |
|
||||
initWithClearAlarmScript(); |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, alarmOriginator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
|
|
||||
long oldEndDate = System.currentTimeMillis(); |
|
||||
AlarmId id = new AlarmId(alarmOriginator.getId()); |
|
||||
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(originator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); |
|
||||
activeAlarm.setId(id); |
|
||||
|
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.cleared(true) |
|
||||
.severity(AlarmSeverity.WARNING) |
|
||||
.propagate(false) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.endTs(oldEndDate) |
|
||||
.build(); |
|
||||
expectedAlarm.setId(id); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findAlarmById(tenantId, id)).thenReturn(activeAlarm); |
|
||||
when(alarmService.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class))) |
|
||||
.thenReturn(AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.cleared(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Cleared")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(alarmOriginator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testCreateAlarmWithDynamicSeverityFromMessageBody() throws Exception { |
|
||||
TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); |
|
||||
config.setPropagate(true); |
|
||||
config.setSeverity("$[alarmSeverity]"); |
|
||||
config.setAlarmType("SomeType"); |
|
||||
config.setScriptLang(ScriptLanguage.JS); |
|
||||
config.setAlarmDetailsBuildJs("DETAILS"); |
|
||||
config.setDynamicSeverity(true); |
|
||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
|
||||
|
|
||||
when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); |
|
||||
|
|
||||
when(ctx.getTenantId()).thenReturn(tenantId); |
|
||||
when(ctx.getAlarmService()).thenReturn(alarmService); |
|
||||
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); |
|
||||
|
|
||||
node = new TbCreateAlarmNode(); |
|
||||
node.init(ctx, nodeConfiguration); |
|
||||
|
|
||||
String rawJson = "{\"alarmSeverity\": \"WARNING\", \"passed\": 5}"; |
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
long ts = msg.getTs(); |
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.startTs(ts) |
|
||||
.endTs(ts) |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.WARNING) |
|
||||
.propagate(true) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); |
|
||||
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.created(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Created")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testCreateAlarmWithDynamicSeverityFromMetadata() throws Exception { |
|
||||
TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); |
|
||||
config.setPropagate(true); |
|
||||
config.setScriptLang(ScriptLanguage.JS); |
|
||||
config.setSeverity("${alarmSeverity}"); |
|
||||
config.setAlarmType("SomeType"); |
|
||||
config.setAlarmDetailsBuildJs("DETAILS"); |
|
||||
config.setDynamicSeverity(true); |
|
||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
|
||||
|
|
||||
when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); |
|
||||
|
|
||||
when(ctx.getTenantId()).thenReturn(tenantId); |
|
||||
when(ctx.getAlarmService()).thenReturn(alarmService); |
|
||||
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); |
|
||||
|
|
||||
node = new TbCreateAlarmNode(); |
|
||||
node.init(ctx, nodeConfiguration); |
|
||||
|
|
||||
metaData.putValue("alarmSeverity", "WARNING"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
long ts = msg.getTs(); |
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.startTs(ts) |
|
||||
.endTs(ts) |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.WARNING) |
|
||||
.propagate(true) |
|
||||
.type("SomeType") |
|
||||
.details(null) |
|
||||
.build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType")).thenReturn(null); |
|
||||
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.created(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
|
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx).tellNext(any(), eq("Created")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
|
|
||||
@Test |
|
||||
public void testCreateAlarmsWithPropagationToTenantWithDynamicTypes() throws Exception { |
|
||||
for (int i = 0; i < 10; i++) { |
|
||||
var config = new TbCreateAlarmNodeConfiguration(); |
|
||||
config.setPropagateToTenant(true); |
|
||||
config.setSeverity(AlarmSeverity.CRITICAL.name()); |
|
||||
config.setAlarmType("SomeType" + i); |
|
||||
config.setScriptLang(ScriptLanguage.JS); |
|
||||
config.setAlarmDetailsBuildJs("DETAILS"); |
|
||||
config.setDynamicSeverity(true); |
|
||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
|
||||
|
|
||||
when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); |
|
||||
|
|
||||
when(ctx.getTenantId()).thenReturn(tenantId); |
|
||||
when(ctx.getAlarmService()).thenReturn(alarmService); |
|
||||
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); |
|
||||
|
|
||||
node = new TbCreateAlarmNode(); |
|
||||
node.init(ctx, nodeConfiguration); |
|
||||
|
|
||||
metaData.putValue("key", "value"); |
|
||||
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, metaData, TbMsgDataType.JSON, rawJson, ruleChainId, ruleNodeId); |
|
||||
long ts = msg.getTs(); |
|
||||
Alarm expectedAlarm = Alarm.builder() |
|
||||
.startTs(ts) |
|
||||
.endTs(ts) |
|
||||
.tenantId(tenantId) |
|
||||
.originator(originator) |
|
||||
.severity(AlarmSeverity.CRITICAL) |
|
||||
.propagateToTenant(true) |
|
||||
.type("SomeType" + i) |
|
||||
.details(null) |
|
||||
.build(); |
|
||||
|
|
||||
when(detailsJs.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
|
||||
when(alarmService.findLatestActiveByOriginatorAndType(tenantId, originator, "SomeType" + i)).thenReturn(null); |
|
||||
when(alarmService.createAlarm(any(AlarmCreateOrUpdateActiveRequest.class))).thenReturn( |
|
||||
AlarmApiCallResult.builder() |
|
||||
.successful(true) |
|
||||
.created(true) |
|
||||
.alarm(new AlarmInfo(expectedAlarm)) |
|
||||
.build()); |
|
||||
node.onMsg(ctx, msg); |
|
||||
|
|
||||
verify(ctx, atMost(10)).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
|
||||
successCaptor.getValue().run(); |
|
||||
verify(ctx, atMost(10)).tellNext(any(), eq("Created")); |
|
||||
|
|
||||
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
|
||||
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
|
||||
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
|
||||
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
|
||||
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
|
||||
verify(ctx, atMost(10)).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
|
||||
|
|
||||
assertEquals(TbMsgType.ALARM, typeCaptor.getValue()); |
|
||||
assertEquals(originator, originatorCaptor.getValue()); |
|
||||
assertEquals("value", metadataCaptor.getValue().getValue("key")); |
|
||||
assertEquals(Boolean.TRUE.toString(), metadataCaptor.getValue().getValue(DataConstants.IS_NEW_ALARM)); |
|
||||
assertNotSame(metaData, metadataCaptor.getValue()); |
|
||||
|
|
||||
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
|
||||
assertEquals(expectedAlarm, actualAlarm); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initWithCreateAlarmScript() { |
|
||||
try { |
|
||||
TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); |
|
||||
config.setPropagate(true); |
|
||||
config.setSeverity(AlarmSeverity.CRITICAL.name()); |
|
||||
config.setAlarmType("SomeType"); |
|
||||
config.setScriptLang(ScriptLanguage.JS); |
|
||||
config.setAlarmDetailsBuildJs("DETAILS"); |
|
||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
|
||||
|
|
||||
when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); |
|
||||
|
|
||||
when(ctx.getTenantId()).thenReturn(tenantId); |
|
||||
when(ctx.getAlarmService()).thenReturn(alarmService); |
|
||||
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); |
|
||||
|
|
||||
node = new TbCreateAlarmNode(); |
|
||||
node.init(ctx, nodeConfiguration); |
|
||||
} catch (TbNodeException ex) { |
|
||||
throw new IllegalStateException(ex); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void initWithClearAlarmScript() { |
|
||||
try { |
|
||||
TbClearAlarmNodeConfiguration config = new TbClearAlarmNodeConfiguration(); |
|
||||
config.setAlarmType("SomeType"); |
|
||||
config.setScriptLang(ScriptLanguage.JS); |
|
||||
config.setAlarmDetailsBuildJs("DETAILS"); |
|
||||
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
|
||||
|
|
||||
when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); |
|
||||
|
|
||||
when(ctx.getTenantId()).thenReturn(tenantId); |
|
||||
when(ctx.getAlarmService()).thenReturn(alarmService); |
|
||||
when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); |
|
||||
|
|
||||
node = new TbClearAlarmNode(); |
|
||||
node.init(ctx, nodeConfiguration); |
|
||||
} catch (TbNodeException ex) { |
|
||||
throw new IllegalStateException(ex); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void verifyError(TbMsg msg, String message, Class expectedClass) { |
|
||||
ArgumentCaptor<Throwable> captor = ArgumentCaptor.forClass(Throwable.class); |
|
||||
verify(ctx).tellFailure(same(msg), captor.capture()); |
|
||||
|
|
||||
Throwable value = captor.getValue(); |
|
||||
assertEquals(expectedClass, value.getClass()); |
|
||||
assertEquals(message, value.getMessage()); |
|
||||
} |
|
||||
|
|
||||
} |
|
||||
@ -0,0 +1,218 @@ |
|||||
|
/** |
||||
|
* 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.rule.engine.action; |
||||
|
|
||||
|
import com.datastax.oss.driver.api.core.uuid.Uuids; |
||||
|
import com.fasterxml.jackson.databind.JsonNode; |
||||
|
import com.google.common.util.concurrent.Futures; |
||||
|
import org.junit.jupiter.api.BeforeEach; |
||||
|
import org.junit.jupiter.api.Test; |
||||
|
import org.junit.jupiter.api.extension.ExtendWith; |
||||
|
import org.mockito.ArgumentCaptor; |
||||
|
import org.mockito.Captor; |
||||
|
import org.mockito.Mock; |
||||
|
import org.mockito.junit.jupiter.MockitoExtension; |
||||
|
import org.thingsboard.common.util.JacksonUtil; |
||||
|
import org.thingsboard.common.util.ListeningExecutor; |
||||
|
import org.thingsboard.rule.engine.TestDbCallbackExecutor; |
||||
|
import org.thingsboard.rule.engine.api.RuleEngineAlarmService; |
||||
|
import org.thingsboard.rule.engine.api.ScriptEngine; |
||||
|
import org.thingsboard.rule.engine.api.TbContext; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeConfiguration; |
||||
|
import org.thingsboard.rule.engine.api.TbNodeException; |
||||
|
import org.thingsboard.server.common.data.DataConstants; |
||||
|
import org.thingsboard.server.common.data.alarm.Alarm; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmApiCallResult; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmInfo; |
||||
|
import org.thingsboard.server.common.data.alarm.AlarmSeverity; |
||||
|
import org.thingsboard.server.common.data.id.AlarmId; |
||||
|
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.msg.TbMsgType; |
||||
|
import org.thingsboard.server.common.data.script.ScriptLanguage; |
||||
|
import org.thingsboard.server.common.msg.TbMsg; |
||||
|
import org.thingsboard.server.common.msg.TbMsgMetaData; |
||||
|
|
||||
|
import java.util.function.Consumer; |
||||
|
|
||||
|
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
import static org.mockito.ArgumentMatchers.any; |
||||
|
import static org.mockito.ArgumentMatchers.anyLong; |
||||
|
import static org.mockito.ArgumentMatchers.eq; |
||||
|
import static org.mockito.ArgumentMatchers.nullable; |
||||
|
import static org.mockito.Mockito.verify; |
||||
|
import static org.mockito.Mockito.when; |
||||
|
|
||||
|
@ExtendWith(MockitoExtension.class) |
||||
|
class TbClearAlarmNodeTest { |
||||
|
|
||||
|
@Mock |
||||
|
TbContext ctxMock; |
||||
|
@Mock |
||||
|
RuleEngineAlarmService alarmServiceMock; |
||||
|
@Mock |
||||
|
ScriptEngine alarmDetailsScriptMock; |
||||
|
|
||||
|
@Captor |
||||
|
ArgumentCaptor<Runnable> successCaptor; |
||||
|
@Captor |
||||
|
ArgumentCaptor<Consumer<Throwable>> failureCaptor; |
||||
|
|
||||
|
TbClearAlarmNode node; |
||||
|
|
||||
|
final TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); |
||||
|
final EntityId msgOriginator = new DeviceId(Uuids.timeBased()); |
||||
|
final EntityId alarmOriginator = new AlarmId(Uuids.timeBased()); |
||||
|
TbMsgMetaData metadata; |
||||
|
|
||||
|
ListeningExecutor dbExecutor; |
||||
|
|
||||
|
@BeforeEach |
||||
|
void before() { |
||||
|
dbExecutor = new TestDbCallbackExecutor(); |
||||
|
metadata = new TbMsgMetaData(); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void alarmCanBeCleared() { |
||||
|
initWithClearAlarmScript(); |
||||
|
metadata.putValue("key", "value"); |
||||
|
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, msgOriginator, metadata, "{\"temperature\": 50}"); |
||||
|
|
||||
|
long oldEndDate = System.currentTimeMillis(); |
||||
|
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(msgOriginator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); |
||||
|
|
||||
|
Alarm expectedAlarm = Alarm.builder() |
||||
|
.tenantId(tenantId) |
||||
|
.originator(msgOriginator) |
||||
|
.cleared(true) |
||||
|
.severity(AlarmSeverity.WARNING) |
||||
|
.propagate(false) |
||||
|
.type("SomeType") |
||||
|
.details(null) |
||||
|
.endTs(oldEndDate) |
||||
|
.build(); |
||||
|
|
||||
|
when(alarmDetailsScriptMock.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
||||
|
when(alarmServiceMock.findLatestActiveByOriginatorAndType(tenantId, msgOriginator, "SomeType")).thenReturn(activeAlarm); |
||||
|
when(alarmServiceMock.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class))) |
||||
|
.thenReturn(AlarmApiCallResult.builder() |
||||
|
.successful(true) |
||||
|
.cleared(true) |
||||
|
.alarm(new AlarmInfo(expectedAlarm)) |
||||
|
.build()); |
||||
|
|
||||
|
node.onMsg(ctxMock, msg); |
||||
|
|
||||
|
verify(ctxMock).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
||||
|
successCaptor.getValue().run(); |
||||
|
verify(ctxMock).tellNext(any(), eq("Cleared")); |
||||
|
|
||||
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
||||
|
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
||||
|
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
||||
|
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
||||
|
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
||||
|
verify(ctxMock).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
||||
|
|
||||
|
assertThat(TbMsgType.ALARM).isEqualTo(typeCaptor.getValue()); |
||||
|
assertThat(msgOriginator).isEqualTo(originatorCaptor.getValue()); |
||||
|
assertThat("value").isEqualTo(metadataCaptor.getValue().getValue("key")); |
||||
|
assertThat(Boolean.TRUE.toString()).isEqualTo(metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); |
||||
|
assertThat(metadata).isNotSameAs(metadataCaptor.getValue()); |
||||
|
|
||||
|
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
||||
|
assertThat(actualAlarm).isEqualTo(expectedAlarm); |
||||
|
} |
||||
|
|
||||
|
@Test |
||||
|
void alarmCanBeClearedWithAlarmOriginator() { |
||||
|
initWithClearAlarmScript(); |
||||
|
metadata.putValue("key", "value"); |
||||
|
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, alarmOriginator, metadata, "{\"temperature\": 50}"); |
||||
|
|
||||
|
long oldEndDate = System.currentTimeMillis(); |
||||
|
AlarmId id = new AlarmId(alarmOriginator.getId()); |
||||
|
Alarm activeAlarm = Alarm.builder().type("SomeType").tenantId(tenantId).originator(msgOriginator).severity(AlarmSeverity.WARNING).endTs(oldEndDate).build(); |
||||
|
activeAlarm.setId(id); |
||||
|
|
||||
|
Alarm expectedAlarm = Alarm.builder() |
||||
|
.tenantId(tenantId) |
||||
|
.originator(msgOriginator) |
||||
|
.cleared(true) |
||||
|
.severity(AlarmSeverity.WARNING) |
||||
|
.propagate(false) |
||||
|
.type("SomeType") |
||||
|
.details(null) |
||||
|
.endTs(oldEndDate) |
||||
|
.build(); |
||||
|
expectedAlarm.setId(id); |
||||
|
|
||||
|
when(alarmDetailsScriptMock.executeJsonAsync(msg)).thenReturn(Futures.immediateFuture(null)); |
||||
|
when(alarmServiceMock.findAlarmById(tenantId, id)).thenReturn(activeAlarm); |
||||
|
when(alarmServiceMock.clearAlarm(eq(activeAlarm.getTenantId()), eq(activeAlarm.getId()), anyLong(), nullable(JsonNode.class))) |
||||
|
.thenReturn(AlarmApiCallResult.builder() |
||||
|
.successful(true) |
||||
|
.cleared(true) |
||||
|
.alarm(new AlarmInfo(expectedAlarm)) |
||||
|
.build()); |
||||
|
|
||||
|
node.onMsg(ctxMock, msg); |
||||
|
|
||||
|
verify(ctxMock).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); |
||||
|
successCaptor.getValue().run(); |
||||
|
verify(ctxMock).tellNext(any(), eq("Cleared")); |
||||
|
|
||||
|
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); |
||||
|
ArgumentCaptor<TbMsgType> typeCaptor = ArgumentCaptor.forClass(TbMsgType.class); |
||||
|
ArgumentCaptor<EntityId> originatorCaptor = ArgumentCaptor.forClass(EntityId.class); |
||||
|
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); |
||||
|
ArgumentCaptor<String> dataCaptor = ArgumentCaptor.forClass(String.class); |
||||
|
verify(ctxMock).transformMsg(msgCaptor.capture(), typeCaptor.capture(), originatorCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); |
||||
|
|
||||
|
assertThat(TbMsgType.ALARM).isEqualTo(typeCaptor.getValue()); |
||||
|
assertThat(alarmOriginator).isEqualTo(originatorCaptor.getValue()); |
||||
|
assertThat("value").isEqualTo(metadataCaptor.getValue().getValue("key")); |
||||
|
assertThat(Boolean.TRUE.toString()).isEqualTo(metadataCaptor.getValue().getValue(DataConstants.IS_CLEARED_ALARM)); |
||||
|
assertThat(metadata).isNotSameAs(metadataCaptor.getValue()); |
||||
|
|
||||
|
Alarm actualAlarm = JacksonUtil.fromBytes(dataCaptor.getValue().getBytes(), Alarm.class); |
||||
|
assertThat(actualAlarm).isEqualTo(expectedAlarm); |
||||
|
} |
||||
|
|
||||
|
private void initWithClearAlarmScript() { |
||||
|
try { |
||||
|
TbClearAlarmNodeConfiguration config = new TbClearAlarmNodeConfiguration(); |
||||
|
config.setAlarmType("SomeType"); |
||||
|
config.setScriptLang(ScriptLanguage.JS); |
||||
|
config.setAlarmDetailsBuildJs("DETAILS"); |
||||
|
TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(JacksonUtil.valueToTree(config)); |
||||
|
|
||||
|
when(ctxMock.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(alarmDetailsScriptMock); |
||||
|
|
||||
|
when(ctxMock.getTenantId()).thenReturn(tenantId); |
||||
|
when(ctxMock.getAlarmService()).thenReturn(alarmServiceMock); |
||||
|
when(ctxMock.getDbCallbackExecutor()).thenReturn(dbExecutor); |
||||
|
|
||||
|
node = new TbClearAlarmNode(); |
||||
|
node.init(ctxMock, nodeConfiguration); |
||||
|
} catch (TbNodeException ex) { |
||||
|
throw new IllegalStateException(ex); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
} |
||||
File diff suppressed because it is too large
Loading…
Reference in new issue