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 b73fbe77 fix(settings): harden persisted settings integrity (#1475)
b73fbe77 is described below
commit b73fbe77b3523cf8dbabd6401467e8fc5ba0114f
Author: aias00 <[email protected]>
AuthorDate: Tue Aug 11 15:33:14 2026 +0800
fix(settings): harden persisted settings integrity (#1475)
* fix(settings): surface corrupt persisted settings
Signed-off-by: liuhy <[email protected]>
* fix(settings): surface corrupt persisted data sources
Signed-off-by: liuhy <[email protected]>
---------
Signed-off-by: liuhy <[email protected]>
---
.../persistence/MybatisPlusSettingsRepository.java | 16 +---
.../MybatisPlusSettingsRepositoryTest.java | 86 ++++++++++++++++++++++
2 files changed, 89 insertions(+), 13 deletions(-)
diff --git
a/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
index ad887969..45e63a45 100644
---
a/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
+++
b/server/src/main/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepository.java
@@ -23,6 +23,7 @@ import
org.apache.rocketmq.studio.persistence.entity.RmqDataSource;
import org.apache.rocketmq.studio.persistence.entity.RmqSettings;
import org.apache.rocketmq.studio.persistence.mapper.RmqDataSourceMapper;
import org.apache.rocketmq.studio.persistence.mapper.RmqSettingsMapper;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
import org.apache.rocketmq.studio.settings.DataSourceVO;
import org.apache.rocketmq.studio.settings.GeneralSettingsVO;
import org.apache.rocketmq.studio.settings.SettingsRepository;
@@ -73,18 +74,7 @@ public class MybatisPlusSettingsRepository implements
SettingsRepository {
return objectMapper.readValue(entity.getJson(),
GeneralSettingsVO.class);
} catch (JsonProcessingException e) {
log.error("Failed to deserialize general settings", e);
- return GeneralSettingsVO.builder()
- .theme("system")
- .compact(false)
- .desktopNotify(true)
- .notifySound(false)
- .sessionTimeout(30)
- .requireLogin(false)
- .llmProvider("openai")
- .apiKey("")
- .model("gpt-4")
- .baseUrl("")
- .build();
+ throw new BusinessException(500, "Persisted general settings are
invalid");
}
}
@@ -162,7 +152,7 @@ public class MybatisPlusSettingsRepository implements
SettingsRepository {
return vo;
} catch (JsonProcessingException e) {
log.error("Failed to deserialize data source: {}",
entity.getDsKey(), e);
- return DataSourceVO.builder().key(entity.getDsKey()).build();
+ throw new BusinessException(500, "Persisted data source is
invalid: " + entity.getDsKey());
}
}
diff --git
a/server/src/test/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepositoryTest.java
b/server/src/test/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepositoryTest.java
new file mode 100644
index 00000000..84ad68d4
--- /dev/null
+++
b/server/src/test/java/org/apache/rocketmq/studio/persistence/MybatisPlusSettingsRepositoryTest.java
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+package org.apache.rocketmq.studio.persistence;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.rocketmq.studio.common.exception.BusinessException;
+import org.apache.rocketmq.studio.persistence.entity.RmqDataSource;
+import org.apache.rocketmq.studio.persistence.entity.RmqSettings;
+import org.apache.rocketmq.studio.persistence.mapper.RmqDataSourceMapper;
+import org.apache.rocketmq.studio.persistence.mapper.RmqSettingsMapper;
+import org.apache.rocketmq.studio.settings.GeneralSettingsVO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+class MybatisPlusSettingsRepositoryTest {
+
+ private RmqSettingsMapper settingsMapper;
+ private RmqDataSourceMapper dataSourceMapper;
+ private MybatisPlusSettingsRepository repository;
+
+ @BeforeEach
+ void setUp() {
+ settingsMapper = mock(RmqSettingsMapper.class);
+ dataSourceMapper = mock(RmqDataSourceMapper.class);
+ repository = new MybatisPlusSettingsRepository(settingsMapper,
dataSourceMapper,
+ new ObjectMapper());
+ }
+
+ @Test
+ void shouldReturnDefaultsWhenGeneralSettingsDoNotExist() {
+ when(settingsMapper.selectById("singleton")).thenReturn(null);
+
+ GeneralSettingsVO settings = repository.loadGeneralSettings();
+
+ assertThat(settings.getTheme()).isEqualTo("system");
+ assertThat(settings.isRequireLogin()).isFalse();
+ }
+
+ @Test
+ void shouldRejectCorruptPersistedGeneralSettings() {
+ RmqSettings settings = new RmqSettings();
+ settings.setJson("{not-json");
+ when(settingsMapper.selectById("singleton")).thenReturn(settings);
+
+ assertThatThrownBy(repository::loadGeneralSettings)
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Persisted general settings are invalid")
+ .extracting("code")
+ .isEqualTo(500);
+ }
+
+ @Test
+ void shouldReadValidPersistedGeneralSettings() {
+ RmqSettings settings = new RmqSettings();
+ settings.setJson("{\"theme\":\"dark\",\"requireLogin\":true}");
+ when(settingsMapper.selectById("singleton")).thenReturn(settings);
+
+ GeneralSettingsVO loaded = repository.loadGeneralSettings();
+
+ assertThat(loaded.getTheme()).isEqualTo("dark");
+ assertThat(loaded.isRequireLogin()).isTrue();
+ }
+
+ @Test
+ void shouldRejectCorruptPersistedDataSource() {
+ RmqDataSource dataSource = new RmqDataSource();
+ dataSource.setDsKey("metrics-prod");
+ dataSource.setJson("{not-json");
+
when(dataSourceMapper.selectById("metrics-prod")).thenReturn(dataSource);
+
+ assertThatThrownBy(() ->
repository.findDataSourceByKey("metrics-prod"))
+ .isInstanceOf(BusinessException.class)
+ .hasMessage("Persisted data source is invalid: metrics-prod")
+ .extracting("code")
+ .isEqualTo(500);
+ }
+}