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 bd0e056f fix: validate data source mutation keys (#718)
bd0e056f is described below

commit bd0e056fb2f08fc7574a45fa38e1b2d1c25f94c2
Author: yx9o <[email protected]>
AuthorDate: Mon Aug 3 11:12:47 2026 +0800

    fix: validate data source mutation keys (#718)
---
 .../settings/InMemorySettingsRepository.java       |  4 +--
 .../studio/settings/SettingsController.java        |  2 +-
 .../studio/settings/SettingsRepository.java        |  2 +-
 .../rocketmq/studio/settings/SettingsService.java  | 20 +++++++++---
 .../settings/InMemorySettingsRepositoryTest.java   |  8 ++++-
 .../studio/settings/SettingsControllerTest.java    | 29 +++++++++++++++++
 .../studio/settings/SettingsServiceTest.java       | 37 ++++++++++++++++++++--
 7 files changed, 91 insertions(+), 11 deletions(-)

diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
index ae72350e..59f0da33 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepository.java
@@ -71,8 +71,8 @@ public class InMemorySettingsRepository implements 
SettingsRepository {
     }
 
     @Override
-    public void deleteDataSource(String key) {
-        dataSources.remove(key);
+    public boolean deleteDataSource(String key) {
+        return dataSources.remove(key) != null;
     }
 
     @Override
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
index 2b2ee43f..7be4fad3 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsController.java
@@ -62,7 +62,7 @@ public class SettingsController {
     }
 
     @PostMapping("/datasources/delete")
-    public Result<Void> deleteDataSource(@RequestParam String key) {
+    public Result<Void> deleteDataSource(@RequestParam(required = false) 
String key) {
         settingsService.deleteDataSource(key);
         return Result.ok();
     }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsRepository.java
 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsRepository.java
index ecf24bf5..3a73aadd 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsRepository.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsRepository.java
@@ -32,7 +32,7 @@ public interface SettingsRepository {
 
     boolean replaceDataSource(DataSourceVO dataSource);
 
-    void deleteDataSource(String key);
+    boolean deleteDataSource(String key);
 
     Optional<DataSourceVO> findDataSourceByKey(String key);
 }
diff --git 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
index 6a49836d..4ca38f44 100644
--- 
a/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
+++ 
b/server/src/main/java/org/apache/rocketmq/studio/settings/SettingsService.java
@@ -101,17 +101,22 @@ public class SettingsService {
 
 
     public DataSourceVO updateDataSource(DataSourceVO dataSource) {
-        log.info("Updating data source: {}", dataSource.getKey());
+        String key = normalizeDataSourceKey(dataSource.getKey());
+        dataSource.setKey(key);
+        log.info("Updating data source: {}", key);
         if (!settingsRepository.replaceDataSource(dataSource)) {
-            throw new BusinessException(404, "Data source not found: " + 
dataSource.getKey());
+            throw new BusinessException(404, "Data source not found: " + key);
         }
         return dataSource;
     }
 
 
     public void deleteDataSource(String key) {
-        log.info("Deleting data source: {}", key);
-        settingsRepository.deleteDataSource(key);
+        String normalizedKey = normalizeDataSourceKey(key);
+        log.info("Deleting data source: {}", normalizedKey);
+        if (!settingsRepository.deleteDataSource(normalizedKey)) {
+            throw new BusinessException(404, "Data source not found: " + 
normalizedKey);
+        }
     }
 
 
@@ -154,6 +159,13 @@ public class SettingsService {
                 && 
PROMETHEUS_COMPATIBLE_TYPES.contains(type.replaceAll("\\s+", "").toLowerCase());
     }
 
+    private String normalizeDataSourceKey(String key) {
+        if (!StringUtils.hasText(key)) {
+            throw new BusinessException(400, "Data source key is required");
+        }
+        return key.trim();
+    }
+
     private void applyAuthentication(HttpHeaders headers, DataSourceTestDTO 
request) {
         String auth = normalizeAuth(request.getAuth());
         if (AUTH_NONE.equals(auth)) {
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
index 373d3753..aac58463 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/settings/InMemorySettingsRepositoryTest.java
@@ -38,12 +38,18 @@ class InMemorySettingsRepositoryTest {
         
assertThat(repository.findDataSourceByKey("source-1")).containsSame(dataSource);
         
assertThat(repository.findAllDataSources()).containsExactly(dataSource);
 
-        repository.deleteDataSource("source-1");
+        boolean deleted = repository.deleteDataSource("source-1");
 
+        assertThat(deleted).isTrue();
         assertThat(repository.findDataSourceByKey("source-1")).isEmpty();
         assertThat(repository.findAllDataSources()).isEmpty();
     }
 
+    @Test
+    void deleteDataSourceShouldReportMissingEntry() {
+        assertThat(repository.deleteDataSource("missing")).isFalse();
+    }
+
     @Test
     void replaceDataSourceShouldUpdateExistingEntry() {
         DataSourceVO existing = 
DataSourceVO.builder().key("source-1").name("Prometheus").build();
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsControllerTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsControllerTest.java
index 5ab867d0..b6f4f7d7 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsControllerTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsControllerTest.java
@@ -17,6 +17,7 @@
 package org.apache.rocketmq.studio.settings;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
 import org.junit.jupiter.api.Test;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
@@ -32,6 +33,7 @@ import static org.hamcrest.Matchers.is;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.argThat;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
 import static org.mockito.Mockito.when;
@@ -265,6 +267,33 @@ class SettingsControllerTest {
         verify(settingsService).deleteDataSource("ds-1");
     }
 
+    @Test
+    void deleteDataSourceShouldRejectMissingKey() throws Exception {
+        doThrow(new BusinessException(400, "Data source key is required"))
+                .when(settingsService).deleteDataSource(null);
+
+        mockMvc.perform(post("/api/settings/datasources/delete"))
+                .andExpect(status().isBadRequest())
+                .andExpect(jsonPath("$.code", is(400)))
+                .andExpect(jsonPath("$.message", is("Data source key is 
required")));
+
+        verify(settingsService).deleteDataSource(null);
+    }
+
+    @Test
+    void deleteDataSourceShouldRejectUnknownKey() throws Exception {
+        doThrow(new BusinessException(404, "Data source not found: missing"))
+                .when(settingsService).deleteDataSource("missing");
+
+        mockMvc.perform(post("/api/settings/datasources/delete")
+                        .param("key", "missing"))
+                .andExpect(status().isNotFound())
+                .andExpect(jsonPath("$.code", is(404)))
+                .andExpect(jsonPath("$.message", is("Data source not found: 
missing")));
+
+        verify(settingsService).deleteDataSource("missing");
+    }
+
     @Test
     void testDataSourceShouldReturnTestResult() throws Exception {
         DataSourceTestDTO request = DataSourceTestDTO.builder()
diff --git 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
index b7d09c82..7ff2a724 100644
--- 
a/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
+++ 
b/server/src/test/java/org/apache/rocketmq/studio/settings/SettingsServiceTest.java
@@ -40,7 +40,6 @@ import java.util.concurrent.atomic.AtomicReference;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -238,15 +237,49 @@ class SettingsServiceTest {
         assertThat(service.listDataSources()).isEmpty();
     }
 
+    @Test
+    void updateDataSourceShouldRejectBlankKey() {
+        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper());
+        DataSourceVO input = DataSourceVO.builder().key(" ").name("Unexpected 
DS").type("rocketmq")
+                .url("unexpected-host:9876").build();
+
+        assertThatThrownBy(() -> service.updateDataSource(input))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Data source key is required")
+                .extracting("code")
+                .isEqualTo(400);
+        assertThat(service.listDataSources()).isEmpty();
+    }
+
     @Test
     void deleteDataSourceShouldDelegateToRepository() {
-        doNothing().when(settingsRepository).deleteDataSource("ds-1");
+        when(settingsRepository.deleteDataSource("ds-1")).thenReturn(true);
 
         settingsService.deleteDataSource("ds-1");
 
         verify(settingsRepository).deleteDataSource("ds-1");
     }
 
+    @Test
+    void deleteDataSourceShouldRejectUnknownKey() {
+        SettingsService service = new SettingsService(new 
InMemorySettingsRepository(), RestClient.builder(), new ObjectMapper());
+
+        assertThatThrownBy(() -> service.deleteDataSource("missing"))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Data source not found: missing")
+                .extracting("code")
+                .isEqualTo(404);
+    }
+
+    @Test
+    void deleteDataSourceShouldRejectBlankKey() {
+        assertThatThrownBy(() -> settingsService.deleteDataSource(" "))
+                .isInstanceOf(BusinessException.class)
+                .hasMessage("Data source key is required")
+                .extracting("code")
+                .isEqualTo(400);
+    }
+
     @Test
     void testConnectionShouldQueryPrometheusEndpoint() {
         AtomicReference<String> requestPath = new AtomicReference<>();

Reply via email to