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 c3b2300e fix: validate cluster operation requests (#633)
c3b2300e is described below

commit c3b2300e5146926e5983a7380d6c382e466332d3
Author: aias00 <[email protected]>
AuthorDate: Tue Jul 28 07:07:11 2026 -0700

    fix: validate cluster operation requests (#633)
---
 .../studio/cluster/broker/ClusterController.java   |   3 +-
 .../studio/cluster/config/UpdateConfigDTO.java     |   3 +
 .../cluster/nameserver/CreateNameServerDTO.java    |   5 +
 .../cluster/nameserver/DeleteNameServerDTO.java    |   4 +
 .../cluster/nameserver/NameServerController.java   |  11 +-
 .../cluster/nameserver/RestartNameServerDTO.java   |   4 +
 .../cluster/nameserver/UpdateNameServerDTO.java    |   5 +
 .../cluster/nameserver/UpgradeNameServerDTO.java   |   6 +
 .../studio/cluster/proxy/ProxyController.java      |   3 +-
 .../studio/cluster/proxy/RestartProxyDTO.java      |   4 +
 .../nameserver/NameServerControllerTest.java       | 159 +++++++++++++++++++++
 .../cluster/broker/ClusterControllerTest.java      |  35 +++++
 .../studio/cluster/proxy/ProxyControllerTest.java  | 100 +++++++++++++
 13 files changed, 335 insertions(+), 7 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterController.java
index c3b68403..1cd53c2c 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/broker/ClusterController.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.broker;
 import org.apache.rocketmq.studio.cluster.config.UpdateConfigDTO;
 
 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;
@@ -48,7 +49,7 @@ public class ClusterController {
     }
 
     @PostMapping("/config/update")
-    public Result<ClusterVO> updateClusterConfig(@RequestBody UpdateConfigDTO 
command) {
+    public Result<ClusterVO> updateClusterConfig(@Valid @RequestBody 
UpdateConfigDTO command) {
         return Result.ok(clusterService.updateClusterConfig(command));
     }
 
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
index 1413be21..229653c1 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/config/UpdateConfigDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.config;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,7 +27,9 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class UpdateConfigDTO {
+    @NotBlank(message = "id is required")
     private String id;
+
     private String flushDiskType;
     private Boolean autoCreateTopicEnable;
     private Boolean autoCreateSubscriptionGroup;
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/CreateNameServerDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/CreateNameServerDTO.java
index 30054716..fd63cc95 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/CreateNameServerDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/CreateNameServerDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.nameserver;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,7 +27,11 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class CreateNameServerDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
+
     private String version;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/DeleteNameServerDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/DeleteNameServerDTO.java
index 55f67554..acc02515 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/DeleteNameServerDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/DeleteNameServerDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.nameserver;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,6 +27,9 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class DeleteNameServerDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerController.java
index d775baf3..0e1b8cc2 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/NameServerController.java
@@ -19,6 +19,7 @@ package org.apache.rocketmq.studio.cluster.nameserver;
 import org.apache.rocketmq.studio.cluster.broker.ClusterService;
 
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -35,30 +36,30 @@ public class NameServerController {
     private final ClusterService clusterService;
 
     @PostMapping("/create")
-    public Result<NameServerVO> createNameServer(@RequestBody 
CreateNameServerDTO command) {
+    public Result<NameServerVO> createNameServer(@Valid @RequestBody 
CreateNameServerDTO command) {
         return Result.ok(clusterService.createNameServer(command));
     }
 
     @PostMapping("/update")
-    public Result<Void> updateNameServer(@RequestBody UpdateNameServerDTO 
command) {
+    public Result<Void> updateNameServer(@Valid @RequestBody 
UpdateNameServerDTO command) {
         clusterService.updateNameServer(command);
         return Result.ok();
     }
 
     @PostMapping("/restart")
-    public Result<Map<String, Boolean>> restartNameServer(@RequestBody 
RestartNameServerDTO command) {
+    public Result<Map<String, Boolean>> restartNameServer(@Valid @RequestBody 
RestartNameServerDTO command) {
         boolean success = clusterService.restartNameServer(command);
         return Result.ok(Map.of("success", success));
     }
 
     @PostMapping("/upgrade")
-    public Result<Map<String, Boolean>> upgradeNameServer(@RequestBody 
UpgradeNameServerDTO command) {
+    public Result<Map<String, Boolean>> upgradeNameServer(@Valid @RequestBody 
UpgradeNameServerDTO command) {
         boolean success = clusterService.upgradeNameServer(command);
         return Result.ok(Map.of("success", success));
     }
 
     @PostMapping("/delete")
-    public Result<Map<String, Boolean>> deleteNameServer(@RequestBody 
DeleteNameServerDTO command) {
+    public Result<Map<String, Boolean>> deleteNameServer(@Valid @RequestBody 
DeleteNameServerDTO command) {
         boolean success = clusterService.deleteNameServer(command);
         return Result.ok(Map.of("success", success));
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/RestartNameServerDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/RestartNameServerDTO.java
index a9ca0c1b..91ae1841 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/RestartNameServerDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/RestartNameServerDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.nameserver;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,6 +27,9 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class RestartNameServerDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpdateNameServerDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpdateNameServerDTO.java
index c0d3803d..8b2c1b47 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpdateNameServerDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpdateNameServerDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.nameserver;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,7 +27,11 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class UpdateNameServerDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
+
     private String version;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpgradeNameServerDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpgradeNameServerDTO.java
index adbd944a..8fab9568 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpgradeNameServerDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/nameserver/UpgradeNameServerDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.nameserver;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,7 +27,12 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class UpgradeNameServerDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
+
+    @NotBlank(message = "targetVersion is required")
     private String targetVersion;
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/ProxyController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/ProxyController.java
index 12487096..fa90b888 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/ProxyController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/ProxyController.java
@@ -18,6 +18,7 @@ package org.apache.rocketmq.studio.cluster.proxy;
 
 import org.apache.rocketmq.studio.cluster.broker.ClusterService;
 import org.apache.rocketmq.studio.common.domain.Result;
+import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
@@ -34,7 +35,7 @@ public class ProxyController {
     private final ClusterService clusterService;
 
     @PostMapping("/restart")
-    public Result<Map<String, Boolean>> restartProxy(@RequestBody 
RestartProxyDTO command) {
+    public Result<Map<String, Boolean>> restartProxy(@Valid @RequestBody 
RestartProxyDTO command) {
         boolean success = clusterService.restartProxy(command);
         return Result.ok(Map.of("success", success));
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/RestartProxyDTO.java
 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/RestartProxyDTO.java
index e393f6f0..5c80e89e 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/RestartProxyDTO.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/cluster/proxy/RestartProxyDTO.java
@@ -16,6 +16,7 @@
  */
 package org.apache.rocketmq.studio.cluster.proxy;
 
+import jakarta.validation.constraints.NotBlank;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -26,6 +27,9 @@ import lombok.NoArgsConstructor;
 @NoArgsConstructor
 @AllArgsConstructor
 public class RestartProxyDTO {
+    @NotBlank(message = "clusterId is required")
     private String clusterId;
+
+    @NotBlank(message = "addr is required")
     private String addr;
 }
diff --git 
a/server/src/test/java/com/rocketmq/studio/cluster/nameserver/NameServerControllerTest.java
 
b/server/src/test/java/com/rocketmq/studio/cluster/nameserver/NameServerControllerTest.java
new file mode 100644
index 00000000..eb4de252
--- /dev/null
+++ 
b/server/src/test/java/com/rocketmq/studio/cluster/nameserver/NameServerControllerTest.java
@@ -0,0 +1,159 @@
+/*
+ * 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.cluster.nameserver;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.cluster.broker.ClusterService;
+import org.apache.rocketmq.studio.common.domain.enums.ClusterStatus;
+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 static org.mockito.ArgumentMatchers.any;
+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.post;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(NameServerController.class)
+@AutoConfigureMockMvc(addFilters = false)
+class NameServerControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @MockBean
+    private ClusterService clusterService;
+
+    @Test
+    void createNameServerShouldPassValidatedRequest() throws Exception {
+        CreateNameServerDTO request = CreateNameServerDTO.builder()
+                .clusterId("cluster-1")
+                .addr("127.0.0.1:9876")
+                .version("5.3.2")
+                .build();
+        NameServerVO created = NameServerVO.builder()
+                .addr("127.0.0.1:9876")
+                .status(ClusterStatus.healthy)
+                .build();
+        
when(clusterService.createNameServer(any(CreateNameServerDTO.class))).thenReturn(created);
+
+        mockMvc.perform(post("/api/nameservers/create")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.addr").value("127.0.0.1:9876"));
+
+        
verify(clusterService).createNameServer(any(CreateNameServerDTO.class));
+    }
+
+    @Test
+    void updateNameServerShouldRejectBlankAddr() throws Exception {
+        UpdateNameServerDTO request = UpdateNameServerDTO.builder()
+                .clusterId("cluster-1")
+                .addr(" ")
+                .build();
+
+        mockMvc.perform(post("/api/nameservers/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("addr is required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
+    @Test
+    void restartNameServerShouldPassValidatedRequest() throws Exception {
+        RestartNameServerDTO request = RestartNameServerDTO.builder()
+                .clusterId("cluster-1")
+                .addr("127.0.0.1:9876")
+                .build();
+        
when(clusterService.restartNameServer(any(RestartNameServerDTO.class))).thenReturn(true);
+
+        mockMvc.perform(post("/api/nameservers/restart")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.success").value(true));
+
+        
verify(clusterService).restartNameServer(any(RestartNameServerDTO.class));
+    }
+
+    @Test
+    void restartNameServerShouldRejectMissingClusterId() throws Exception {
+        RestartNameServerDTO request = RestartNameServerDTO.builder()
+                .addr("127.0.0.1:9876")
+                .build();
+
+        mockMvc.perform(post("/api/nameservers/restart")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("clusterId is 
required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
+    @Test
+    void upgradeNameServerShouldRejectMissingTargetVersion() throws Exception {
+        UpgradeNameServerDTO request = UpgradeNameServerDTO.builder()
+                .clusterId("cluster-1")
+                .addr("127.0.0.1:9876")
+                .build();
+
+        mockMvc.perform(post("/api/nameservers/upgrade")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("targetVersion is 
required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
+    @Test
+    void deleteNameServerShouldPassValidatedRequest() throws Exception {
+        DeleteNameServerDTO request = DeleteNameServerDTO.builder()
+                .clusterId("cluster-1")
+                .addr("127.0.0.1:9876")
+                .build();
+        
when(clusterService.deleteNameServer(any(DeleteNameServerDTO.class))).thenReturn(true);
+
+        mockMvc.perform(post("/api/nameservers/delete")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.success").value(true));
+
+        
verify(clusterService).deleteNameServer(any(DeleteNameServerDTO.class));
+    }
+}
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
index 6c3356c5..5279f617 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/broker/ClusterControllerTest.java
@@ -35,6 +35,7 @@ import java.util.Arrays;
 import java.util.Collections;
 
 import static org.mockito.ArgumentMatchers.any;
+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;
@@ -135,6 +136,40 @@ class ClusterControllerTest {
                 .andExpect(jsonPath("$.data.config.readQueueNums").value(16));
     }
 
+    @Test
+    void updateConfigShouldRejectMissingId() throws Exception {
+        UpdateConfigDTO command = UpdateConfigDTO.builder()
+                .flushDiskType("SYNC_FLUSH")
+                .writeQueueNums(16)
+                .build();
+
+        mockMvc.perform(post("/api/clusters/config/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(command)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
+    @Test
+    void updateConfigShouldRejectBlankId() throws Exception {
+        UpdateConfigDTO command = UpdateConfigDTO.builder()
+                .id(" ")
+                .flushDiskType("SYNC_FLUSH")
+                .build();
+
+        mockMvc.perform(post("/api/clusters/config/update")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(command)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("id is required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
     @Test
     void restartBrokerShouldReturnSuccess() throws Exception {
         when(clusterService.restartBroker("cluster-1", 
"broker-0")).thenReturn(true);
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/cluster/proxy/ProxyControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/proxy/ProxyControllerTest.java
new file mode 100644
index 00000000..3f5d0d5d
--- /dev/null
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/cluster/proxy/ProxyControllerTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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.cluster.proxy;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.cluster.broker.ClusterService;
+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 static org.mockito.ArgumentMatchers.any;
+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.post;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
+import static 
org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+@WebMvcTest(ProxyController.class)
+@AutoConfigureMockMvc(addFilters = false)
+class ProxyControllerTest {
+
+    @Autowired
+    private MockMvc mockMvc;
+
+    @Autowired
+    private ObjectMapper objectMapper;
+
+    @MockBean
+    private ClusterService clusterService;
+
+    @Test
+    void restartProxyShouldPassValidatedRequest() throws Exception {
+        RestartProxyDTO request = RestartProxyDTO.builder()
+                .clusterId("cluster-1")
+                .addr("127.0.0.1:8081")
+                .build();
+        
when(clusterService.restartProxy(any(RestartProxyDTO.class))).thenReturn(true);
+
+        mockMvc.perform(post("/api/proxies/restart")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isOk())
+                .andExpect(jsonPath("$.code").value(200))
+                .andExpect(jsonPath("$.data.success").value(true));
+
+        verify(clusterService).restartProxy(any(RestartProxyDTO.class));
+    }
+
+    @Test
+    void restartProxyShouldRejectMissingClusterId() throws Exception {
+        RestartProxyDTO request = RestartProxyDTO.builder()
+                .addr("127.0.0.1:8081")
+                .build();
+
+        mockMvc.perform(post("/api/proxies/restart")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("clusterId is 
required"));
+
+        verifyNoInteractions(clusterService);
+    }
+
+    @Test
+    void restartProxyShouldRejectBlankAddr() throws Exception {
+        RestartProxyDTO request = RestartProxyDTO.builder()
+                .clusterId("cluster-1")
+                .addr(" ")
+                .build();
+
+        mockMvc.perform(post("/api/proxies/restart")
+                        .contentType(MediaType.APPLICATION_JSON)
+                        .content(objectMapper.writeValueAsString(request)))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code").value(400))
+                .andExpect(jsonPath("$.message").value("addr is required"));
+
+        verifyNoInteractions(clusterService);
+    }
+}

Reply via email to