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<>();