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));
+    }
+}

Reply via email to