This is an automated email from the ASF dual-hosted git repository.

lizhimins pushed a commit to branch rocketmq-studio
in repository https://gitbox.apache.org/repos/asf/rocketmq-dashboard.git


The following commit(s) were added to refs/heads/rocketmq-studio by this push:
     new c9cafb11 fix: validate resource write requests (#981)
c9cafb11 is described below

commit c9cafb1120b60043eb28e98cfba71a520c0b9a5f
Author: aias00 <[email protected]>
AuthorDate: Wed Aug 5 02:39:21 2026 -0700

    fix: validate resource write requests (#981)
---
 .../studio/instance/acl/AclController.java         | 20 ++++---
 .../studio/instance/acl/CreateAclRuleDTO.java      | 49 +++++++++++++++++
 .../studio/instance/acl/UpdateAclRuleDTO.java      | 52 ++++++++++++++++++
 .../studio/instance/acl/UpdateAclUserDTO.java      | 40 ++++++++++++++
 .../studio/instance/topic/TopicController.java     |  6 +--
 .../studio/instance/topic/UpdateTopicDTO.java      | 57 ++++++++++++++++++++
 .../studio/ops/alert/AlertRuleController.java      | 14 +++--
 .../studio/ops/alert/AlertRuleRequestDTO.java      | 63 ++++++++++++++++++++++
 .../studio/instance/acl/AclControllerTest.java     | 36 +++++++++++++
 .../studio/instance/topic/TopicControllerTest.java | 12 +++++
 .../studio/ops/alert/AlertRuleControllerTest.java  | 48 +++++++++++++++++
 11 files changed, 383 insertions(+), 14 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
index 29671efe..39d3894f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/AclController.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.instance.acl;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
@@ -44,13 +45,13 @@ public class AclController {
     }
 
     @PostMapping("/rules/create")
-    public Result<AclRuleVO> createRule(@RequestBody AclRuleVO rule) {
-        return Result.ok(aclService.createRule(rule));
+    public Result<AclRuleVO> createRule(@Valid @RequestBody(required = false) 
CreateAclRuleDTO rule) {
+        return Result.ok(aclService.createRule(requireRequest(rule, "ACL rule 
request is required").toAclRuleVO()));
     }
 
     @PostMapping("/rules/update")
-    public Result<AclRuleVO> updateRule(@RequestBody AclRuleVO rule) {
-        return Result.ok(aclService.updateRule(rule));
+    public Result<AclRuleVO> updateRule(@Valid @RequestBody(required = false) 
UpdateAclRuleDTO rule) {
+        return Result.ok(aclService.updateRule(requireRequest(rule, "ACL rule 
request is required").toAclRuleVO()));
     }
 
     @PostMapping("/rules/delete")
@@ -75,8 +76,8 @@ public class AclController {
     }
 
     @PostMapping("/users/update")
-    public Result<AclUserVO> updateUser(@RequestBody AclUserVO user) {
-        return Result.ok(aclService.updateUser(user));
+    public Result<AclUserVO> updateUser(@Valid @RequestBody(required = false) 
UpdateAclUserDTO user) {
+        return Result.ok(aclService.updateUser(requireRequest(user, "ACL user 
request is required").toAclUserVO()));
     }
 
     @PostMapping("/users/delete")
@@ -84,4 +85,11 @@ public class AclController {
         aclService.deleteUser(request.getId());
         return Result.ok();
     }
+
+    private <T> T requireRequest(T request, String message) {
+        if (request == null) {
+            throw new BusinessException(400, message);
+        }
+        return request;
+    }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/CreateAclRuleDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/CreateAclRuleDTO.java
new file mode 100644
index 00000000..9cac8dd8
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/CreateAclRuleDTO.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.rocketmq.studio.instance.acl;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class CreateAclRuleDTO {
+    @NotBlank(message = "principal is required")
+    private String principal;
+    @NotBlank(message = "resource is required")
+    private String resource;
+    private String resourceType;
+    private String resourcePattern;
+    private List<String> actions;
+    private String decision;
+    private String scope;
+    private String aclVersion;
+
+    public AclRuleVO toAclRuleVO() {
+        return AclRuleVO.builder()
+                .principal(principal)
+                .resource(resource)
+                .resourceType(resourceType)
+                .resourcePattern(resourcePattern)
+                .actions(actions)
+                .decision(decision)
+                .scope(scope)
+                .aclVersion(aclVersion)
+                .build();
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclRuleDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclRuleDTO.java
new file mode 100644
index 00000000..a7c4862d
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclRuleDTO.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.rocketmq.studio.instance.acl;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class UpdateAclRuleDTO {
+    @NotBlank(message = "id is required")
+    private String id;
+    @NotBlank(message = "principal is required")
+    private String principal;
+    @NotBlank(message = "resource is required")
+    private String resource;
+    private String resourceType;
+    private String resourcePattern;
+    private List<String> actions;
+    private String decision;
+    private String scope;
+    private String aclVersion;
+
+    public AclRuleVO toAclRuleVO() {
+        return AclRuleVO.builder()
+                .id(id)
+                .principal(principal)
+                .resource(resource)
+                .resourceType(resourceType)
+                .resourcePattern(resourcePattern)
+                .actions(actions)
+                .decision(decision)
+                .scope(scope)
+                .aclVersion(aclVersion)
+                .build();
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclUserDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclUserDTO.java
new file mode 100644
index 00000000..19d19e00
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/acl/UpdateAclUserDTO.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.rocketmq.studio.instance.acl;
+
+import jakarta.validation.constraints.NotBlank;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class UpdateAclUserDTO {
+    @NotBlank(message = "id is required")
+    private String id;
+    private String username;
+    private boolean admin;
+    private List<String> clusters;
+
+    public AclUserVO toAclUserVO() {
+        return AclUserVO.builder()
+                .id(id)
+                .username(username)
+                .admin(admin)
+                .clusters(clusters)
+                .build();
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicController.java
index 706e095b..51fba64c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/TopicController.java
@@ -52,9 +52,9 @@ public class TopicController {
     }
 
     @PostMapping("/update")
-    public Result<TopicVO> updateTopic(@RequestBody(required = false) TopicVO 
topic) {
+    public Result<TopicVO> updateTopic(@Valid @RequestBody(required = false) 
UpdateTopicDTO topic) {
         requireTopicRequest(topic);
-        return Result.ok(metadataService.updateTopic(topic));
+        return Result.ok(metadataService.updateTopic(topic.toTopicVO()));
     }
 
     @PostMapping("/delete")
@@ -80,7 +80,7 @@ public class TopicController {
         return Result.ok(metadataService.sendMessage(request));
     }
 
-    private void requireTopicRequest(TopicVO topic) {
+    private void requireTopicRequest(UpdateTopicDTO topic) {
         if (topic == null) {
             throw new BusinessException(400, "Topic request is required");
         }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/UpdateTopicDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/UpdateTopicDTO.java
new file mode 100644
index 00000000..e33dbf2a
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/UpdateTopicDTO.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.rocketmq.studio.instance.topic;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.PositiveOrZero;
+import lombok.Data;
+import org.apache.rocketmq.studio.common.domain.enums.TopicPerm;
+import org.apache.rocketmq.studio.common.domain.enums.TopicType;
+
+@Data
+public class UpdateTopicDTO {
+    @NotBlank(message = "name is required")
+    private String name;
+    private String namespace;
+    private String clusterId;
+    private String instanceId;
+    private TopicType type;
+    @PositiveOrZero(message = "writeQueues must be zero or positive")
+    private Integer writeQueues;
+    @PositiveOrZero(message = "readQueues must be zero or positive")
+    private Integer readQueues;
+    private TopicPerm perm;
+    private String remark;
+
+    public TopicVO toTopicVO() {
+        TopicVO topic = new TopicVO();
+        topic.setName(name);
+        topic.setNamespace(namespace);
+        topic.setClusterId(clusterId);
+        topic.setInstanceId(instanceId);
+        topic.setType(type);
+        if (writeQueues != null) {
+            topic.setWriteQueues(writeQueues);
+        }
+        if (readQueues != null) {
+            topic.setReadQueues(readQueues);
+        }
+        topic.setPerm(perm);
+        topic.setRemark(remark);
+        return topic;
+    }
+}
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleController.java
index d7613a5f..9e7e63a1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleController.java
@@ -41,13 +41,17 @@ public class AlertRuleController {
     }
 
     @PostMapping("/create")
-    public Result<AlertRuleVO> createRule(@RequestBody(required = false) 
AlertRuleVO rule) {
-        return Result.ok(alertService.createRule(requireAlertRule(rule)));
+    public Result<AlertRuleVO> createRule(@Valid @RequestBody(required = 
false) AlertRuleRequestDTO rule) {
+        return 
Result.ok(alertService.createRule(requireAlertRule(rule).toAlertRuleVO()));
     }
 
     @PostMapping("/update")
-    public Result<AlertRuleVO> updateRule(@RequestBody(required = false) 
AlertRuleVO rule) {
-        return Result.ok(alertService.updateRule(requireAlertRule(rule)));
+    public Result<AlertRuleVO> updateRule(@Valid @RequestBody(required = 
false) AlertRuleRequestDTO rule) {
+        AlertRuleRequestDTO request = requireAlertRule(rule);
+        if (request.getId() == null || request.getId().isBlank()) {
+            throw new BusinessException(400, "id is required");
+        }
+        return Result.ok(alertService.updateRule(request.toAlertRuleVO()));
     }
 
     @PostMapping("/toggle")
@@ -61,7 +65,7 @@ public class AlertRuleController {
         return Result.ok();
     }
 
-    private AlertRuleVO requireAlertRule(AlertRuleVO rule) {
+    private AlertRuleRequestDTO requireAlertRule(AlertRuleRequestDTO rule) {
         if (rule == null) {
             throw new BusinessException(400, "Alert rule request is required");
         }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleRequestDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleRequestDTO.java
new file mode 100644
index 00000000..57c66b1f
--- /dev/null
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/ops/alert/AlertRuleRequestDTO.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.rocketmq.studio.ops.alert;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class AlertRuleRequestDTO {
+    private String id;
+    @NotBlank(message = "name is required")
+    private String name;
+    private String metric;
+    @Pattern(regexp = ">|>=|<|<=|==|!=", message = "operator is invalid")
+    private String operator;
+    private double threshold;
+    private String thresholdUnit;
+    @Pattern(regexp = "(?:[0-9]+(?:ms|s|m|h|d|w|y))+", message = "duration is 
invalid")
+    private String duration;
+    private List<String> channels;
+    private boolean enabled;
+    private String description;
+    private String brokerName;
+    private String clusterName;
+    @Pattern(regexp = "critical|warning|info", flags = 
Pattern.Flag.CASE_INSENSITIVE,
+            message = "severity is invalid")
+    private String severity;
+
+    public AlertRuleVO toAlertRuleVO() {
+        return AlertRuleVO.builder()
+                .id(id)
+                .name(name)
+                .metric(metric)
+                .operator(operator)
+                .threshold(threshold)
+                .thresholdUnit(thresholdUnit)
+                .duration(duration)
+                .channels(channels)
+                .enabled(enabled)
+                .description(description)
+                .brokerName(brokerName)
+                .clusterName(clusterName)
+                .severity(severity)
+                .build();
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
index 6b8baf1b..8e66a1a2 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/acl/AclControllerTest.java
@@ -121,6 +121,18 @@ class AclControllerTest {
                 .andExpect(jsonPath("$.data.principal").value("user1"));
     }
 
+    @Test
+    void createRuleShouldRejectMissingPrincipal() throws Exception {
+        mockMvc.perform(post("/api/acl/rules/create")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"resource\":\"topic-1\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("principal is 
required"));
+
+        verifyNoInteractions(aclService);
+    }
+
     @Test
     void updateRuleShouldReturnUpdatedRule() throws Exception {
         AclRuleVO input = AclRuleVO.builder()
@@ -161,6 +173,18 @@ class AclControllerTest {
                 .andExpect(jsonPath("$.message").value("ACL rule not found: 
missing-rule"));
     }
 
+    @Test
+    void updateRuleShouldRejectMissingId() throws Exception {
+        mockMvc.perform(post("/api/acl/rules/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        
.content("{\"principal\":\"user1\",\"resource\":\"topic-1\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(aclService);
+    }
+
     @Test
     void deleteRuleShouldPassValidatedRequest() throws Exception {
         mockMvc.perform(post("/api/acl/rules/delete")
@@ -286,6 +310,18 @@ class AclControllerTest {
                 .andExpect(jsonPath("$.data.admin").value(false));
     }
 
+    @Test
+    void updateUserShouldRejectMissingId() throws Exception {
+        mockMvc.perform(post("/api/acl/users/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"username\":\"admin\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(aclService);
+    }
+
     @Test
     void deleteUserShouldPassValidatedRequest() throws Exception {
         mockMvc.perform(post("/api/acl/users/delete")
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/TopicControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/TopicControllerTest.java
index d39af4cc..118cfc7f 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/TopicControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/TopicControllerTest.java
@@ -184,6 +184,18 @@ class TopicControllerTest {
         verifyNoInteractions(metadataService);
     }
 
+    @Test
+    void updateTopicShouldRejectBlankName() throws Exception {
+        mockMvc.perform(post("/api/topics/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"name\":\" \"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("name is required"));
+
+        verifyNoInteractions(metadataService);
+    }
+
     @Test
     void sendMessageShouldReturnResult() throws Exception {
         SendMessageDTO request = SendMessageDTO.builder()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleControllerTest.java
index 057b04ba..8fac72fe 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/ops/alert/AlertRuleControllerTest.java
@@ -103,6 +103,54 @@ class AlertRuleControllerTest {
         verifyNoInteractions(alertService);
     }
 
+    @Test
+    void createRuleShouldRejectInvalidRuleFields() throws Exception {
+        mockMvc.perform(post("/api/alert-rules/create")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"name\":\"High 
Lag\",\"operator\":\"invalid\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("operator is invalid"));
+
+        verifyNoInteractions(alertService);
+    }
+
+    @Test
+    void createRuleShouldRejectInvalidDuration() throws Exception {
+        mockMvc.perform(post("/api/alert-rules/create")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"name\":\"High 
Lag\",\"duration\":\"later\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("duration is invalid"));
+
+        verifyNoInteractions(alertService);
+    }
+
+    @Test
+    void createRuleShouldRejectInvalidSeverity() throws Exception {
+        mockMvc.perform(post("/api/alert-rules/create")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"name\":\"High 
Lag\",\"severity\":\"urgent\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("severity is invalid"));
+
+        verifyNoInteractions(alertService);
+    }
+
+    @Test
+    void updateRuleShouldRejectMissingId() throws Exception {
+        mockMvc.perform(post("/api/alert-rules/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{\"name\":\"High Lag\"}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(alertService);
+    }
+
     @Test
     void updateRuleShouldRejectNullRequestBody() throws Exception {
         mockMvc.perform(post("/api/alert-rules/update")

Reply via email to