From a0b38a7eb4731ecce9f5dbef81d8b5ae1a87b73e Mon Sep 17 00:00:00 2001 From: VIacheslavKlimov Date: Mon, 20 Oct 2025 13:09:10 +0300 Subject: [PATCH] Fix NotificationRuleApiTest --- .../alarm/AlarmCalculatedFieldState.java | 2 - .../notification/NotificationRuleApiTest.java | 91 ++++++++----------- 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java index 61d55f376f..e36892378c 100644 --- a/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java +++ b/application/src/main/java/org/thingsboard/server/service/cf/ctx/state/alarm/AlarmCalculatedFieldState.java @@ -189,8 +189,6 @@ public class AlarmCalculatedFieldState extends BaseCalculatedFieldState { @Override public ListenableFuture performCalculation(Map updatedArgs, CalculatedFieldCtx ctx) { initCurrentAlarm(ctx); - // FIXME: don't create alarm if attrs were deleted, or config is updated - // TODO: what if expression is changed? do we reevaluate? or only on new events? TbAlarmResult result = createOrClearAlarms(state -> { if (updatedArgs != null) { boolean newEvent = !updatedArgs.isEmpty(); diff --git a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java index bab70ea505..2bfdc2f330 100644 --- a/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java +++ b/application/src/test/java/org/thingsboard/server/service/notification/NotificationRuleApiTest.java @@ -27,7 +27,7 @@ import org.springframework.data.util.Pair; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cache.limits.RateLimitService; -import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.AttributeScope; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; @@ -39,17 +39,19 @@ import org.thingsboard.server.common.data.alarm.AlarmCommentType; import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.alarm.rule.AlarmRule; +import org.thingsboard.server.common.data.alarm.rule.condition.SimpleAlarmCondition; +import org.thingsboard.server.common.data.alarm.rule.condition.expression.TbelAlarmConditionExpression; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.cf.CalculatedField; +import org.thingsboard.server.common.data.cf.CalculatedFieldType; +import org.thingsboard.server.common.data.cf.configuration.AlarmCalculatedFieldConfiguration; +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.ReferencedEntityKey; import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.data.DeviceData; -import org.thingsboard.server.common.data.device.profile.AlarmCondition; -import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; -import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; -import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; -import org.thingsboard.server.common.data.device.profile.AlarmRule; -import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; -import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -87,9 +89,6 @@ import org.thingsboard.server.common.data.notification.targets.platform.SystemAd import org.thingsboard.server.common.data.notification.template.NotificationTemplate; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.query.BooleanFilterPredicate; -import org.thingsboard.server.common.data.query.EntityKeyValueType; -import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.security.Authority; @@ -106,12 +105,10 @@ import org.thingsboard.server.service.system.DefaultSystemInfoService; import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; import java.lang.reflect.Method; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.TreeMap; import java.util.UUID; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; @@ -193,7 +190,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { @Test public void testNotificationRuleProcessing_alarmTrigger() throws Exception { String notificationSubject = "Alarm type: ${alarmType}, status: ${alarmStatus}, " + - "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}"; + "severity: ${alarmSeverity}, deviceId: ${alarmOriginatorId}"; String notificationText = "Status: ${alarmStatus}, severity: ${alarmSeverity}"; NotificationTemplate notificationTemplate = createNotificationTemplate(NotificationType.ALARM, notificationSubject, notificationText, NotificationDeliveryMethod.WEB); @@ -234,8 +231,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { }); JsonNode attr = JacksonUtil.newObjectNode() - .set("bool", BooleanNode.TRUE); - doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr); + .set("createAlarm", BooleanNode.TRUE); + postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString()); await().atMost(10, TimeUnit.SECONDS) .until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null); @@ -250,7 +247,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { assertThat(actualDelay).isCloseTo(expectedDelay, offset(2.0)); assertThat(notification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + AlarmStatus.ACTIVE_UNACK + ", " + - "severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase() + ", deviceId: " + device.getId()); + "severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase() + ", deviceId: " + device.getId()); assertThat(notification.getText()).isEqualTo("Status: " + AlarmStatus.ACTIVE_UNACK + ", severity: " + AlarmSeverity.CRITICAL.toString().toLowerCase()); assertThat(notification.getType()).isEqualTo(NotificationType.ALARM); @@ -270,7 +267,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { wsClient.waitForUpdate(true); Notification updatedNotification = wsClient.getLastDataUpdate().getUpdate(); assertThat(updatedNotification.getSubject()).isEqualTo("Alarm type: " + alarmType + ", status: " + expectedStatus + ", " + - "severity: " + expectedSeverity.toString().toLowerCase() + ", deviceId: " + device.getId()); + "severity: " + expectedSeverity.toString().toLowerCase() + ", deviceId: " + device.getId()); assertThat(updatedNotification.getText()).isEqualTo("Status: " + expectedStatus + ", severity: " + expectedSeverity.toString().toLowerCase()); wsClient.close(); @@ -296,7 +293,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { List notifications = getMyNotifications(false, 10); assertThat(notifications).singleElement().matches(notification -> { return notification.getType() == NotificationType.ALARM && - notification.getSubject().equals("New alarm 'testAlarm'"); + notification.getSubject().equals("New alarm 'testAlarm'"); }); }); } @@ -341,8 +338,8 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { getWsClient().subscribeForUnreadNotifications(10).waitForReply(true); getWsClient().registerWaitForUpdate(); JsonNode attr = JacksonUtil.newObjectNode() - .set("bool", BooleanNode.TRUE); - doPost("/api/plugins/telemetry/" + device.getId() + "/" + DataConstants.SHARED_SCOPE, attr); + .set("createAlarm", BooleanNode.TRUE); + postAttributes(device.getId(), AttributeScope.SERVER_SCOPE, attr.toString()); await().atMost(10, TimeUnit.SECONDS) .until(() -> alarmSubscriptionService.findLatestByOriginatorAndType(tenantId, device.getId(), alarmType) != null); @@ -491,11 +488,11 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { }); assertThat(notifications).anySatisfy(notification -> { assertThat(notification.getText()).isEqualTo("Rate limits for REST API requests per customer " + - "exceeded for 'Customer'"); + "exceeded for 'Customer'"); }); assertThat(notifications).anySatisfy(notification -> { assertThat(notification.getText()).isEqualTo("Rate limits for notification requests " + - "per rule exceeded for '" + rule.getName() + "'"); + "per rule exceeded for '" + rule.getName() + "'"); }); loginSysAdmin(); @@ -748,7 +745,7 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { .build(); assertThat(DefaultNotificationDeduplicationService.getDeduplicationKey(expectedTrigger, rule)) .isEqualTo("RATE_LIMITS:TENANT:" + tenantId + ":ENTITY_EXPORT_" + - target.getId() + ":ENTITY_EXPORT,TRANSPORT_MESSAGES_PER_DEVICE"); + target.getId() + ":ENTITY_EXPORT,TRANSPORT_MESSAGES_PER_DEVICE"); loginTenantAdmin(); getWsClient().subscribeForUnreadNotifications(10).waitForReply(); @@ -944,35 +941,27 @@ public class NotificationRuleApiTest extends AbstractNotificationApiTest { private DeviceProfile createDeviceProfileWithAlarmRules(String alarmType) { DeviceProfile deviceProfile = createDeviceProfile("For notification rule test"); deviceProfile.setTenantId(tenantId); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - List alarms = new ArrayList<>(); - DeviceProfileAlarm alarm = new DeviceProfileAlarm(); - alarm.setAlarmType(alarmType); - alarm.setId(alarmType); + CalculatedField alarmCf = new CalculatedField(); + alarmCf.setType(CalculatedFieldType.ALARM); + alarmCf.setEntityId(deviceProfile.getId()); + alarmCf.setName(alarmType); + AlarmCalculatedFieldConfiguration configuration = new AlarmCalculatedFieldConfiguration(); + Argument argument = new Argument(); + argument.setRefEntityKey(new ReferencedEntityKey("createAlarm", ArgumentType.ATTRIBUTE, AttributeScope.SERVER_SCOPE)); + configuration.setArguments(Map.of("createAlarm", argument)); AlarmRule alarmRule = new AlarmRule(); - alarmRule.setAlarmDetails("Details"); - AlarmCondition alarmCondition = new AlarmCondition(); - alarmCondition.setSpec(new SimpleAlarmConditionSpec()); - List condition = new ArrayList<>(); - - AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter(); - alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "bool")); - BooleanFilterPredicate predicate = new BooleanFilterPredicate(); - predicate.setOperation(BooleanFilterPredicate.BooleanOperation.EQUAL); - predicate.setValue(new FilterPredicateValue<>(true)); - - alarmConditionFilter.setPredicate(predicate); - alarmConditionFilter.setValueType(EntityKeyValueType.BOOLEAN); - condition.add(alarmConditionFilter); - alarmCondition.setCondition(condition); - alarmRule.setCondition(alarmCondition); - TreeMap createRules = new TreeMap<>(); - createRules.put(AlarmSeverity.CRITICAL, alarmRule); - alarm.setCreateRules(createRules); - alarms.add(alarm); - - deviceProfile.getProfileData().setAlarms(alarms); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + SimpleAlarmCondition condition = new SimpleAlarmCondition(); + TbelAlarmConditionExpression expression = new TbelAlarmConditionExpression(); + expression.setExpression("return createAlarm == true;"); + condition.setExpression(expression); + alarmRule.setCondition(condition); + configuration.setCreateRules(Map.of( + AlarmSeverity.CRITICAL, alarmRule + )); + alarmCf.setConfiguration(configuration); + saveCalculatedField(alarmCf); return deviceProfile; }