This is an automated email from the ASF dual-hosted git repository.
zhangliang pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shardingsphere.git
The following commit(s) were added to refs/heads/master by this push:
new 827586e0470 Add SingleRuleConfigurationDecoratorTest (#37148)
827586e0470 is described below
commit 827586e04709b80726aaff16cf6d43f112798224
Author: Liang Zhang <[email protected]>
AuthorDate: Thu Nov 20 20:36:04 2025 +0800
Add SingleRuleConfigurationDecoratorTest (#37148)
* Add SingleRuleConfigurationDecoratorTest
* Add SingleRuleConfigurationDecoratorTest
* Add SingleRuleConfigurationDecoratorTest
* Add SingleRuleConfigurationDecoratorTest
---
AGENTS.md | 4 +-
.../SingleRuleConfigurationDecoratorTest.java | 207 +++++++++++++++++++++
2 files changed, 210 insertions(+), 1 deletion(-)
diff --git a/AGENTS.md b/AGENTS.md
index 70d3a86c1bb..4c2214f4369 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -30,6 +30,7 @@ This guide is written **for AI coding agents only**. Follow
it literally; improv
- **Test-Driven**: design for testability, ensure unit-test coverage, and keep
background unit tests under 60s to avoid job stalls.
- **Quality Assurance**: run static checks, formatting, and code reviews.
- **Continuous Verification**: rely on automated tests and integration
validation.
+- **Public-Only Tests**: unit tests must exercise behavior via public APIs
only; never use reflection to access private members.
## Tool Usage Guide
@@ -181,7 +182,7 @@ Always state which topology, registry, and engine versions
(e.g., MySQL 5.7 vs 8
## Execution Loop
1. **Intake & Clarify** – restate the request, map affected modules, confirm
sandbox/network/approval constraints, and capture a constraint checklist
(forbidden APIs, output formats, ordering rules, coverage targets).
-2. **Plan & Reason** – craft a multi-step plan (analysis, edits, tests).
Enumerate branches/tests upfront whenever the user demands minimum coverage;
add rule-specific constraints (e.g., “no `assertEquals`”) to the plan and
re-check them before edits.
+2. **Plan & Reason** – craft a multi-step plan (analysis, edits, tests).
Enumerate branches/tests upfront whenever the user demands minimum coverage;
add rule-specific constraints (e.g., “no `assertEquals`”) to the plan and
re-check them before edits. Before altering tests or mocks, inspect how
`AutoMockExtension`, `@StaticMockSettings`, or other helpers already handle
static/construction mocks and list every static dependency you will touch so
you can confirm whether it is already cover [...]
3. **Implement** – touch only the required files, reuse abstractions, preserve
ASF headers, and document major decisions.
4. **Validate** – run the narrowest meaningful command (e.g., `./mvnw -pl
<module> -am test`, `./mvnw -pl <module> -DskipITs -Dspotless.skip=true
-Dtest=ClassName test`). Announce intent beforehand and summarize exit codes
afterward; when blocked, state the command you intended to run and why it
matters.
5. **Report** – lead with intent, list edited files plus rationale/line refs,
cite verification commands + results, and propose next steps.
@@ -237,6 +238,7 @@ Always state which topology, registry, and engine versions
(e.g., MySQL 5.7 vs 8
- Use marker interfaces when distinct rule/attribute types are needed; reuse
SPI types such as `ShardingSphereRule` where possible.
- Name tests after the production method under test; never probe private
helpers directly—document unreachable branches instead.
- Mock heavy dependencies (database/cache/registry/network) and prefer mocking
over building deep object graphs; avoid `RETURNS_DEEP_STUBS` unless chained
interactions demand it.
+- Before changing how mocks are created, scan the repository for similar tests
(e.g., other rule decorators or executor tests) and reuse their proven mocking
pattern instead of inventing a new structure.
- When constructors hide collaborators, use `Plugins.getMemberAccessor()` to
inject mocks and document why SPI creation is bypassed.
- Cache SPI loader results (`OrderedSPILoader`, `TypedSPILoader`,
`DatabaseTypedSPILoader`, etc.) per key at the test-class level to avoid
redundant lookups.
diff --git
a/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decorator/SingleRuleConfigurationDecoratorTest.java
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decorator/SingleRuleConfigurationDecoratorTest.java
new file mode 100644
index 00000000000..6c4e6d17d26
--- /dev/null
+++
b/kernel/single/core/src/test/java/org/apache/shardingsphere/single/decorator/SingleRuleConfigurationDecoratorTest.java
@@ -0,0 +1,207 @@
+/*
+ * 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.shardingsphere.single.decorator;
+
+import
org.apache.shardingsphere.database.connector.core.metadata.database.metadata.DialectDatabaseMetaData;
+import org.apache.shardingsphere.database.connector.core.type.DatabaseType;
+import
org.apache.shardingsphere.database.connector.core.type.DatabaseTypeRegistry;
+import
org.apache.shardingsphere.infra.config.rule.decorator.RuleConfigurationDecorator;
+import org.apache.shardingsphere.infra.database.DatabaseTypeEngine;
+import org.apache.shardingsphere.infra.datanode.DataNode;
+import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
+import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
+import org.apache.shardingsphere.single.config.SingleRuleConfiguration;
+import org.apache.shardingsphere.single.constant.SingleTableConstants;
+import org.apache.shardingsphere.single.datanode.SingleTableDataNodeLoader;
+import
org.apache.shardingsphere.single.exception.InvalidSingleRuleConfigurationException;
+import org.apache.shardingsphere.single.util.SingleTableLoadUtils;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
+import
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.MockedConstruction;
+
+import javax.sql.DataSource;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Optional;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.contains;
+import static org.hamcrest.Matchers.is;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyCollection;
+import static org.mockito.ArgumentMatchers.anyMap;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockConstruction;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(AutoMockExtension.class)
+@StaticMockSettings({DatabaseTypeEngine.class,
SingleTableDataNodeLoader.class, SingleTableLoadUtils.class})
+class SingleRuleConfigurationDecoratorTest {
+
+ private final SingleRuleConfigurationDecorator decorator =
+ (SingleRuleConfigurationDecorator)
TypedSPILoader.getService(RuleConfigurationDecorator.class,
SingleRuleConfiguration.class);
+
+ private final DatabaseType databaseType =
TypedSPILoader.getService(DatabaseType.class, "FIXTURE");
+
+ @BeforeEach
+ void setUp() {
+
when(DatabaseTypeEngine.getStorageType(any(DataSource.class))).thenReturn(databaseType);
+ }
+
+ @Test
+ void assertDecorateReturnsEmptyWhenTablesAndRulesAbsent() {
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Collections.emptyList(), null);
+ SingleRuleConfiguration actual = decorator.decorate("foo_db",
Collections.emptyMap(), Collections.emptyList(), ruleConfig);
+ assertThat(actual.getTables(), is(Collections.emptyList()));
+ }
+
+ @Test
+ void assertDecorateLoadsTablesWhenRulesPresentButConfigEmpty() {
+ Map<String, Collection<DataNode>> actualDataNodes =
Collections.singletonMap("t_order", Collections.singleton(new
DataNode("foo_ds", "foo_schema", "t_order")));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+
mockSplitAndConvert(Collections.singleton(SingleTableConstants.ALL_TABLES),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Collections.emptyList(), null);
+ assertThat(decorator.decorate("foo_db", dataSources,
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig).getTables(), contains("foo_ds.t_order"));
+ }
+
+ @Test
+ void assertDecorateReturnsSplitTablesWhenNoExpansionRequired() {
+ Collection<String> tables = Arrays.asList("foo_ds.foo_tbl",
"bar_ds.bar_tbl");
+ mockSplitAndConvert(tables, Collections.emptyList(),
Collections.emptyList(), Collections.emptyList());
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(tables, null);
+ assertThat(decorator.decorate("foo_db", Collections.emptyMap(),
Collections.emptyList(), ruleConfig).getTables(), contains("foo_ds.foo_tbl",
"bar_ds.bar_tbl"));
+ }
+
+ @Test
+ void assertDecorateLoadsAllSchemaTablesWhenSchemaSupported() {
+ Map<String, Collection<DataNode>> actualDataNodes =
Collections.singletonMap("t_order", Collections.singleton(new
DataNode("foo_ds", "public", "t_order")));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+
mockSplitAndConvert(Collections.singleton(SingleTableConstants.ALL_SCHEMA_TABLES),
Collections.emptyList(), Collections.emptyList(), Collections.emptyList());
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Collections.singleton(SingleTableConstants.ALL_SCHEMA_TABLES),
null);
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockSchemaAwareRegistry()) {
+ assertThat(decorator.decorate("foo_db", dataSources,
Collections.emptyList(), ruleConfig).getTables(),
contains("foo_ds.public.t_order"));
+ }
+ }
+
+ @Test
+ void assertDecorateUsesDefaultStorageTypeWhenNoDataSourceConfigured() {
+ Map<String, Collection<DataNode>> actualDataNodes =
createActualDataNodes(new DataNode("foo_ds", "foo_schema", "t_order"));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+ Collection<String> splitTables =
Collections.singleton("*.foo_schema.t_order");
+ Collection<DataNode> configuredDataNodes = Collections.singleton(new
DataNode("foo_ds", "foo_schema", "t_order"));
+ mockSplitAndConvert(splitTables, configuredDataNodes,
Collections.emptyList(), Collections.emptyList());
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Collections.singleton("*.foo_schema.t_order"), null);
+ try (MockedConstruction<DatabaseTypeRegistry> ignored =
mockSchemaAwareRegistry()) {
+ assertThat(decorator.decorate("foo_db", Collections.emptyMap(),
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig).getTables(),
+ contains("foo_ds.foo_schema.t_order"));
+ }
+ }
+
+ @Test
+ void assertDecorateThrowsWhenSpecifiedTableMismatch() {
+ Map<String, Collection<DataNode>> actualDataNodes =
createActualDataNodes(new DataNode("bar_ds", "foo_schema", "t_order"));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+ Collection<String> splitTables =
Collections.singleton("*.foo_schema.t_order");
+ Collection<DataNode> configuredDataNodes = Collections.singleton(new
DataNode("foo_ds", "foo_schema", "t_order"));
+ mockSplitAndConvert(splitTables, configuredDataNodes,
Collections.emptyList(), Collections.emptyList());
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Collections.singleton("*.foo_schema.t_order"), null);
+ assertThrows(InvalidSingleRuleConfigurationException.class,
+ () -> decorator.decorate("foo_db", dataSources,
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig));
+ }
+
+ @Test
+ void assertDecorateLoadsSpecifiedTablesWithExpand() {
+ Map<String, Collection<DataNode>> actualDataNodes = new
LinkedHashMap<>(3, 1F);
+ actualDataNodes.put("feature_tbl", Collections.singleton(new
DataNode("skip_ds", "foo_schema", "feature_tbl")));
+ actualDataNodes.put("expanded_tbl", Collections.singleton(new
DataNode("expand_ds", "foo_schema", "expanded_tbl")));
+ actualDataNodes.put("matched_tbl", Collections.singleton(new
DataNode("bar_ds", "foo_schema", "matched_tbl")));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+ Collection<String> splitTables = Arrays.asList("expand_ds.*",
"bar_ds.bar_tbl");
+ Collection<DataNode> configuredDataNodes = Arrays.asList(
+ new DataNode("expand_ds", "foo_schema",
SingleTableConstants.ASTERISK),
+ new DataNode("bar_ds", "foo_schema", "matched_tbl"));
+ mockSplitAndConvert(splitTables, configuredDataNodes,
Collections.emptyList(), Collections.singleton("feature_tbl"));
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Arrays.asList("expand_ds.*", "bar_ds.bar_tbl"), null);
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ assertThat(decorator.decorate("foo_db", dataSources,
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig).getTables(),
+ contains("expand_ds.expanded_tbl", "bar_ds.matched_tbl"));
+ }
+
+ @Test
+ void assertDecorateIgnoresUnexpectedTablesDuringExpand() {
+ Map<String, Collection<DataNode>> actualDataNodes = new
LinkedHashMap<>(3, 1F);
+ actualDataNodes.put("expanded_tbl", Collections.singleton(new
DataNode("expand_ds", "foo_schema", "expanded_tbl")));
+ actualDataNodes.put("ignored_tbl", Collections.singleton(new
DataNode("ignored_ds", "foo_schema", "ignored_tbl")));
+ actualDataNodes.put("matched_tbl", Collections.singleton(new
DataNode("bar_ds", "foo_schema", "matched_tbl")));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+ Collection<String> splitTables = Arrays.asList("expand_ds.*",
"bar_ds.bar_tbl");
+ Collection<DataNode> configuredDataNodes = Arrays.asList(
+ new DataNode("expand_ds", "foo_schema",
SingleTableConstants.ASTERISK),
+ new DataNode("bar_ds", "foo_schema", "matched_tbl"));
+ mockSplitAndConvert(splitTables, configuredDataNodes,
Collections.emptyList(), Collections.emptyList());
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Arrays.asList("expand_ds.*", "bar_ds.bar_tbl"), null);
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ assertThat(decorator.decorate("foo_db", dataSources,
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig).getTables(),
+ contains("expand_ds.expanded_tbl", "bar_ds.matched_tbl"));
+ }
+
+ @Test
+ void assertDecorateThrowsWhenExpandedNodeMismatch() {
+ Map<String, Collection<DataNode>> actualDataNodes =
Collections.singletonMap("t_order", Collections.singleton(new
DataNode("other_ds", "foo_schema", "t_order")));
+ when(SingleTableDataNodeLoader.load(anyString(), anyMap(),
anyCollection())).thenReturn(actualDataNodes);
+ Collection<String> splitTables = Arrays.asList("expand_ds.*",
"expand_ds.t_order");
+ Collection<DataNode> configuredDataNodes = Arrays.asList(
+ new DataNode("expand_ds", "foo_schema",
SingleTableConstants.ASTERISK),
+ new DataNode("expand_ds", "foo_schema", "t_order"));
+ mockSplitAndConvert(splitTables, configuredDataNodes,
Collections.emptyList(), Collections.emptyList());
+ SingleRuleConfiguration ruleConfig = new
SingleRuleConfiguration(Arrays.asList("expand_ds.*", "expand_ds.t_order"),
null);
+ Map<String, DataSource> dataSources =
Collections.singletonMap("foo_ds", mock(DataSource.class));
+ assertThrows(InvalidSingleRuleConfigurationException.class,
+ () -> decorator.decorate("foo_db", dataSources,
Collections.singleton(mock(ShardingSphereRule.class, RETURNS_DEEP_STUBS)),
ruleConfig));
+ }
+
+ private MockedConstruction<DatabaseTypeRegistry> mockSchemaAwareRegistry()
{
+ DialectDatabaseMetaData dialectDatabaseMetaData =
mock(DialectDatabaseMetaData.class, RETURNS_DEEP_STUBS);
+
when(dialectDatabaseMetaData.getSchemaOption().getDefaultSchema()).thenReturn(Optional.of("public"));
+ return mockConstruction(DatabaseTypeRegistry.class, (mock, context) ->
when(mock.getDialectDatabaseMetaData()).thenReturn(dialectDatabaseMetaData));
+ }
+
+ private void mockSplitAndConvert(final Collection<String> splitTables,
final Collection<DataNode> configuredDataNodes,
+ final Collection<String> excludedTables,
final Collection<String> featureRequiredTables) {
+
when(SingleTableLoadUtils.splitTableLines(anyCollection())).thenReturn(splitTables);
+
when(SingleTableLoadUtils.getExcludedTables(anyCollection())).thenReturn(excludedTables);
+ when(SingleTableLoadUtils.convertToDataNodes(anyString(),
any(DatabaseType.class), anyCollection())).thenReturn(configuredDataNodes);
+
when(SingleTableLoadUtils.getFeatureRequiredSingleTables(anyCollection())).thenReturn(featureRequiredTables);
+ }
+
+ private Map<String, Collection<DataNode>> createActualDataNodes(final
DataNode dataNode) {
+ return Collections.singletonMap(dataNode.getTableName(),
Collections.singleton(dataNode));
+ }
+}