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 d36729f2 fix: validate instance resource action requests (#634)
d36729f2 is described below

commit d36729f23afa6bf871c9a056da74fe59075bd195
Author: aias00 <[email protected]>
AuthorDate: Tue Jul 28 07:08:53 2026 -0700

    fix: validate instance resource action requests (#634)
    
    * [Studio] Validate instance resource action requests
    
    * fix: validate instance delete requests
---
 .../studio/instance/InstanceController.java        |  6 +-
 ...pdateDTO.java => InstanceDeleteRequestDTO.java} | 16 ++--
 .../studio/instance/dlq/DLQController.java         | 11 +--
 .../DLQResendRequestDTO.java}                      | 19 +++--
 .../rocketmq/studio/instance/dlq/DLQService.java   | 25 ++++++
 .../instance/group/ConsumerGroupController.java    |  8 +-
 .../ResetConsumerOffsetDTO.java}                   | 24 ++++--
 ...eTopicTTLUpdateDTO.java => DeleteTopicDTO.java} | 14 +++-
 .../studio/instance/topic/LiteTopicController.java |  3 +-
 .../instance/topic/LiteTopicTTLUpdateDTO.java      |  4 +
 .../studio/instance/topic/TopicController.java     |  6 +-
 .../studio/instance/InstanceControllerTest.java    | 25 ++++++
 .../studio/instance/dlq/DLQControllerTest.java     | 38 +++++++++
 .../studio/instance/dlq/DLQServiceTest.java        | 34 ++++++++
 .../group/ConsumerGroupControllerTest.java         | 98 ++++++++++++++++++++++
 .../instance/topic/LiteTopicControllerTest.java    | 32 +++++++
 .../studio/instance/topic/TopicControllerTest.java | 38 +++++++++
 17 files changed, 363 insertions(+), 38 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
index 29dcb83d..e5db352a 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceController.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.instance;
 
 import org.apache.rocketmq.studio.common.domain.Result;
 import org.apache.rocketmq.studio.common.domain.enums.InstanceType;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -28,7 +29,6 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping("/api/instances")
@@ -55,8 +55,8 @@ public class InstanceController {
     }
 
     @PostMapping("/delete")
-    public Result<Void> deleteInstance(@RequestBody Map<String, String> body) {
-        instanceService.deleteInstance(body.get("id"));
+    public Result<Void> deleteInstance(@Valid @RequestBody 
InstanceDeleteRequestDTO request) {
+        instanceService.deleteInstance(request.getId());
         return Result.ok();
     }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java
similarity index 71%
copy from 
server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java
index a511c336..418bf53f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/InstanceDeleteRequestDTO.java
@@ -14,13 +14,19 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.rocketmq.studio.instance;
 
-package org.apache.rocketmq.studio.instance.topic;
-
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
-public class LiteTopicTTLUpdateDTO {
-    private String topicPattern;
-    private Long newTTL;
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class InstanceDeleteRequestDTO {
+    @NotBlank(message = "id is required")
+    private String id;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQController.java
index 8d59c608..4015e1de 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQController.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.instance.dlq;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -26,7 +27,6 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping("/api/dlq")
@@ -41,12 +41,9 @@ public class DLQController {
     }
 
     @PostMapping("/resend")
-    public Result<Void> resendMessages(@RequestBody Map<String, Object> 
request) {
-        String groupName = (String) request.get("groupName");
-        Long startTime = request.get("startTime") != null ? ((Number) 
request.get("startTime")).longValue() : null;
-        Long endTime = request.get("endTime") != null ? ((Number) 
request.get("endTime")).longValue() : null;
-        String targetTopic = (String) request.get("targetTopic");
-        dlqService.resendMessages(groupName, startTime, endTime, targetTopic);
+    public Result<Void> resendMessages(@Valid @RequestBody DLQResendRequestDTO 
request) {
+        dlqService.resendMessages(
+                request.getGroupName(), request.getStartTime(), 
request.getEndTime(), request.getTargetTopic());
         return Result.ok();
     }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQResendRequestDTO.java
similarity index 65%
copy from 
server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQResendRequestDTO.java
index a511c336..b91e1f48 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQResendRequestDTO.java
@@ -14,13 +14,22 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.rocketmq.studio.instance.dlq;
 
-package org.apache.rocketmq.studio.instance.topic;
-
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
-public class LiteTopicTTLUpdateDTO {
-    private String topicPattern;
-    private Long newTTL;
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DLQResendRequestDTO {
+    @NotBlank(message = "groupName is required")
+    private String groupName;
+    private Long startTime;
+    private Long endTime;
+    private String targetTopic;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
index 42f7ce77..a829c07f 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/dlq/DLQService.java
@@ -16,9 +16,12 @@
  */
 package org.apache.rocketmq.studio.instance.dlq;
 
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
 import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
 
 import java.util.List;
 
@@ -35,7 +38,29 @@ public class DLQService {
     }
 
     public void resendMessages(String groupName, Long startTime, Long endTime, 
String targetTopic) {
+        validateResendRequest(groupName, startTime, endTime);
         log.info("Resending DLQ messages: group={}, targetTopic={}", 
groupName, targetTopic);
         dlqProvider.resendMessages(groupName, startTime, endTime, targetTopic);
     }
+
+    private void validateResendRequest(String groupName, Long startTime, Long 
endTime) {
+        if (!StringUtils.hasText(groupName)) {
+            throw new BusinessException(HttpStatus.BAD_REQUEST.value(), 
"groupName is required");
+        }
+        if ((startTime == null) != (endTime == null)) {
+            throw new BusinessException(
+                    HttpStatus.BAD_REQUEST.value(), "startTime and endTime 
must be provided together");
+        }
+        if (startTime == null) {
+            return;
+        }
+        if (startTime <= 0 || endTime <= 0) {
+            throw new BusinessException(
+                    HttpStatus.BAD_REQUEST.value(), "startTime and endTime 
must be positive");
+        }
+        if (endTime < startTime) {
+            throw new BusinessException(
+                    HttpStatus.BAD_REQUEST.value(), "endTime must not be 
earlier than startTime");
+        }
+    }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupController.java
index 9ef78e34..10644eb3 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupController.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.instance.group;
 import org.apache.rocketmq.studio.instance.topic.MetadataService;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -80,11 +81,8 @@ public class ConsumerGroupController {
     }
 
     @PostMapping("/reset-offset")
-    public Result<Void> resetOffset(@RequestBody Map<String, Object> request) {
-        String name = (String) request.get("name");
-        long timestamp = ((Number) request.get("timestamp")).longValue();
-        String topic = (String) request.get("topic");
-        metadataService.resetOffset(name, timestamp, topic);
+    public Result<Void> resetOffset(@Valid @RequestBody ResetConsumerOffsetDTO 
request) {
+        metadataService.resetOffset(request.getName(), request.getTimestamp(), 
request.getTopic());
         return Result.ok();
     }
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
similarity index 58%
copy from 
server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
index a511c336..b7f5265c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/group/ResetConsumerOffsetDTO.java
@@ -14,13 +14,27 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package org.apache.rocketmq.studio.instance.group;
 
-package org.apache.rocketmq.studio.instance.topic;
-
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Positive;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
-public class LiteTopicTTLUpdateDTO {
-    private String topicPattern;
-    private Long newTTL;
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class ResetConsumerOffsetDTO {
+    @NotBlank(message = "name is required")
+    private String name;
+
+    @NotNull(message = "timestamp is required")
+    @Positive(message = "timestamp must be positive")
+    private Long timestamp;
+
+    private String topic;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/DeleteTopicDTO.java
similarity index 75%
copy from 
server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
copy to 
server/src/main/java/org/apache/rocketmq/studio/instance/topic/DeleteTopicDTO.java
index a511c336..50f256c7 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/DeleteTopicDTO.java
@@ -14,13 +14,19 @@
  * 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 lombok.AllArgsConstructor;
+import lombok.Builder;
 import lombok.Data;
+import lombok.NoArgsConstructor;
 
 @Data
-public class LiteTopicTTLUpdateDTO {
-    private String topicPattern;
-    private Long newTTL;
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class DeleteTopicDTO {
+    @NotBlank(message = "name is required")
+    private String name;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicController.java
index d9ed7f5c..ff98e498 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicController.java
@@ -18,6 +18,7 @@
 package org.apache.rocketmq.studio.instance.topic;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -49,7 +50,7 @@ public class LiteTopicController {
     }
 
     @PostMapping("/extendTTL")
-    public Result<Void> extendTTL(@RequestBody LiteTopicTTLUpdateDTO request) {
+    public Result<Void> extendTTL(@Valid @RequestBody LiteTopicTTLUpdateDTO 
request) {
         liteTopicService.extendTTL(request.getTopicPattern(), 
request.getNewTTL());
         return Result.ok();
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
index a511c336..206948f4 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/instance/topic/LiteTopicTTLUpdateDTO.java
@@ -17,10 +17,14 @@
 
 package org.apache.rocketmq.studio.instance.topic;
 
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Positive;
 import lombok.Data;
 
 @Data
 public class LiteTopicTTLUpdateDTO {
+    @NotBlank(message = "topicPattern is required")
     private String topicPattern;
+    @Positive(message = "newTTL must be positive")
     private Long newTTL;
 }
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 3948d1e1..07c059d2 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
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.instance.topic;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.GetMapping;
 import org.springframework.web.bind.annotation.PathVariable;
@@ -27,7 +28,6 @@ import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.util.List;
-import java.util.Map;
 
 @RestController
 @RequestMapping("/api/topics")
@@ -55,8 +55,8 @@ public class TopicController {
     }
 
     @PostMapping("/delete")
-    public Result<Void> deleteTopic(@RequestBody Map<String, String> request) {
-        metadataService.deleteTopic(request.get("name"));
+    public Result<Void> deleteTopic(@Valid @RequestBody DeleteTopicDTO 
request) {
+        metadataService.deleteTopic(request.getName());
         return Result.ok();
     }
 
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
index 3c868d84..37f14fdf 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/InstanceControllerTest.java
@@ -36,6 +36,7 @@ import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -175,6 +176,30 @@ class InstanceControllerTest {
         verify(instanceService).deleteInstance("inst-1");
     }
 
+    @Test
+    void deleteInstanceShouldRejectBlankId() throws Exception {
+        mockMvc.perform(post("/api/instances/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of("id", 
" "))))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(instanceService);
+    }
+
+    @Test
+    void deleteInstanceShouldRejectMissingId() throws Exception {
+        mockMvc.perform(post("/api/instances/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(Map.of())))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(instanceService);
+    }
+
     private InstanceVO buildInstance(String id, String name, InstanceType 
type, String endpoint) {
         InstanceVO instance = InstanceVO.builder()
                 .name(name)
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQControllerTest.java
index 7dad065b..6c23be58 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQControllerTest.java
@@ -33,6 +33,7 @@ import java.util.Map;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -122,4 +123,41 @@ class DLQControllerTest {
         verify(dlqService).resendMessages(
                 eq("test-group"), isNull(), isNull(), eq("target-topic"));
     }
+
+    @Test
+    void resendMessagesShouldRejectMissingGroupName() throws Exception {
+        Map<String, Object> body = Map.of(
+                "startTime", 1000,
+                "endTime", 2000,
+                "targetTopic", "target-topic"
+        );
+
+        mockMvc.perform(post("/api/dlq/resend")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("groupName is 
required"));
+
+        verifyNoInteractions(dlqService);
+    }
+
+    @Test
+    void resendMessagesShouldRejectInvalidTimeType() throws Exception {
+        Map<String, Object> body = Map.of(
+                "groupName", "test-group",
+                "startTime", "invalid",
+                "endTime", 2000,
+                "targetTopic", "target-topic"
+        );
+
+        mockMvc.perform(post("/api/dlq/resend")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("Invalid request 
body"));
+
+        verifyNoInteractions(dlqService);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
index 9c120c8c..9dfeff4c 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/dlq/DLQServiceTest.java
@@ -26,7 +26,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
 import java.util.List;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 
 @ExtendWith(MockitoExtension.class)
@@ -87,4 +89,36 @@ class DLQServiceTest {
 
         verify(dlqProvider).resendMessages("group-1", null, null, 
"target-topic");
     }
+
+    @Test
+    void resendMessagesShouldRejectBlankGroupName() {
+        assertThatThrownBy(() -> dlqService.resendMessages(" ", 1000L, 2000L, 
"target-topic"))
+                .hasMessage("groupName is required");
+
+        verifyNoInteractions(dlqProvider);
+    }
+
+    @Test
+    void resendMessagesShouldRejectPartialTimeRange() {
+        assertThatThrownBy(() -> dlqService.resendMessages("group-1", 1000L, 
null, "target-topic"))
+                .hasMessage("startTime and endTime must be provided together");
+
+        verifyNoInteractions(dlqProvider);
+    }
+
+    @Test
+    void resendMessagesShouldRejectNonPositiveTimeRange() {
+        assertThatThrownBy(() -> dlqService.resendMessages("group-1", 0L, 
2000L, "target-topic"))
+                .hasMessage("startTime and endTime must be positive");
+
+        verifyNoInteractions(dlqProvider);
+    }
+
+    @Test
+    void resendMessagesShouldRejectReversedTimeRange() {
+        assertThatThrownBy(() -> dlqService.resendMessages("group-1", 2000L, 
1000L, "target-topic"))
+                .hasMessage("endTime must not be earlier than startTime");
+
+        verifyNoInteractions(dlqProvider);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
index d743daa2..4dbd2d60 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/group/ConsumerGroupControllerTest.java
@@ -17,19 +17,26 @@
 
 package org.apache.rocketmq.studio.instance.group;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
 import org.apache.rocketmq.studio.instance.topic.MetadataService;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import 
org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 
 import java.time.LocalDateTime;
 import java.util.List;
+import java.util.Map;
 
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.when;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
+import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
 import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
 import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
 
@@ -40,6 +47,9 @@ class ConsumerGroupControllerTest {
     @Autowired
     private MockMvc mockMvc;
 
+    @Autowired
+    private ObjectMapper objectMapper;
+
     @MockBean
     private MetadataService metadataService;
 
@@ -76,4 +86,92 @@ class ConsumerGroupControllerTest {
                 .andExpect(jsonPath("$.data.threads[0].stackTrace[0]")
                         
.value("org.apache.rocketmq.client.impl.consumer.ConsumeMessageConcurrentlyService.run"));
     }
+
+    @Test
+    void resetOffsetShouldPassValidatedRequest() throws Exception {
+        Map<String, Object> body = Map.of(
+                "name", "cg-orders",
+                "topic", "orders",
+                "timestamp", 1784246400000L
+        );
+
+        mockMvc.perform(post("/api/groups/reset-offset")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.message").value("success"));
+
+        verify(metadataService).resetOffset(eq("cg-orders"), 
eq(1784246400000L), eq("orders"));
+    }
+
+    @Test
+    void resetOffsetShouldRejectMissingName() throws Exception {
+        Map<String, Object> body = Map.of(
+                "topic", "orders",
+                "timestamp", 1784246400000L
+        );
+
+        mockMvc.perform(post("/api/groups/reset-offset")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("name is required"));
+
+        verifyNoInteractions(metadataService);
+    }
+
+    @Test
+    void resetOffsetShouldRejectMissingTimestamp() throws Exception {
+        Map<String, Object> body = Map.of(
+                "name", "cg-orders",
+                "topic", "orders"
+        );
+
+        mockMvc.perform(post("/api/groups/reset-offset")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("timestamp is 
required"));
+
+        verifyNoInteractions(metadataService);
+    }
+
+    @Test
+    void resetOffsetShouldRejectNonPositiveTimestamp() throws Exception {
+        Map<String, Object> body = Map.of(
+                "name", "cg-orders",
+                "topic", "orders",
+                "timestamp", 0L
+        );
+
+        mockMvc.perform(post("/api/groups/reset-offset")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("timestamp must be 
positive"));
+
+        verifyNoInteractions(metadataService);
+    }
+
+    @Test
+    void resetOffsetShouldRejectInvalidTimestampType() throws Exception {
+        Map<String, Object> body = Map.of(
+                "name", "cg-orders",
+                "topic", "orders",
+                "timestamp", "invalid"
+        );
+
+        mockMvc.perform(post("/api/groups/reset-offset")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(body)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("Invalid request 
body"));
+
+        verifyNoInteractions(metadataService);
+    }
 }
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
index d2184fcf..8c1609f4 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/instance/topic/LiteTopicControllerTest.java
@@ -30,6 +30,7 @@ import java.util.List;
 
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -124,4 +125,35 @@ class LiteTopicControllerTest {
 
         verify(liteTopicService).extendTTL(eq("chat/{sessionId}"), 
eq(7_200_000L));
     }
+
+    @Test
+    void extendTTLShouldRejectMissingTopicPattern() throws Exception {
+        LiteTopicTTLUpdateDTO request = new LiteTopicTTLUpdateDTO();
+        request.setNewTTL(7_200_000L);
+
+        mockMvc.perform(post("/api/liteTopic/extendTTL")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("topicPattern is 
required"));
+
+        verifyNoInteractions(liteTopicService);
+    }
+
+    @Test
+    void extendTTLShouldRejectNonPositiveTTL() throws Exception {
+        LiteTopicTTLUpdateDTO request = new LiteTopicTTLUpdateDTO();
+        request.setTopicPattern("chat/{sessionId}");
+        request.setNewTTL(0L);
+
+        mockMvc.perform(post("/api/liteTopic/extendTTL")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("newTTL must be 
positive"));
+
+        verifyNoInteractions(liteTopicService);
+    }
 }
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 eb8aa7e6..27c7bc16 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
@@ -27,11 +27,13 @@ import org.springframework.http.MediaType;
 import org.springframework.test.web.servlet.MockMvc;
 
 import java.util.List;
+import java.util.Map;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
 import static 
org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -130,4 +132,40 @@ class TopicControllerTest {
                 .andExpect(jsonPath("$.data.msgId").value("msg-001"))
                 .andExpect(jsonPath("$.data.offsetMsgId").value("offset-001"));
     }
+
+    @Test
+    void deleteTopicShouldReturnSuccess() throws Exception {
+        mockMvc.perform(post("/api/topics/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        
.content(objectMapper.writeValueAsString(Map.of("name", "test-topic"))))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.message").value("success"));
+
+        verify(metadataService).deleteTopic("test-topic");
+    }
+
+    @Test
+    void deleteTopicShouldRejectMissingName() throws Exception {
+        mockMvc.perform(post("/api/topics/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content("{}"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("name is required"));
+
+        verifyNoInteractions(metadataService);
+    }
+
+    @Test
+    void deleteTopicShouldRejectBlankName() throws Exception {
+        mockMvc.perform(post("/api/topics/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        
.content(objectMapper.writeValueAsString(Map.of("name", " "))))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("name is required"));
+
+        verifyNoInteractions(metadataService);
+    }
 }

Reply via email to