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 ad8ffb30f73 Add more test cases on ProxyDatabaseConnectionManagerTest 
(#37980)
ad8ffb30f73 is described below

commit ad8ffb30f7362dcb4204ed4d91650015eeec549b
Author: Liang Zhang <[email protected]>
AuthorDate: Sun Feb 8 00:23:09 2026 +0800

    Add more test cases on ProxyDatabaseConnectionManagerTest (#37980)
    
    * Add more test cases on ProxyDatabaseConnectionManagerTest
    
    * Add more test cases on ProxyDatabaseConnectionManagerTest
    
    * Add more test cases on ProxyDatabaseConnectionManagerTest
---
 .codex/skills/analyze-issue/SKILL.md               |   2 -
 .codex/skills/gen-ut/SKILL.md                      |  42 ++++-
 .codex/skills/review-pr/SKILL.md                   |   6 +-
 AGENTS.md                                          |  11 +-
 .../ProxyDatabaseConnectionManagerTest.java        | 199 ++++++++++++++++++---
 5 files changed, 218 insertions(+), 42 deletions(-)

diff --git a/.codex/skills/analyze-issue/SKILL.md 
b/.codex/skills/analyze-issue/SKILL.md
index 912daa64ff4..2da33936b64 100644
--- a/.codex/skills/analyze-issue/SKILL.md
+++ b/.codex/skills/analyze-issue/SKILL.md
@@ -187,8 +187,6 @@ Type-specific rules:
 
 ## Mandatory Output Structure
 
-Respond in the same language as the user.
-
 Four-section structure (Question, Misunderstanding / Invalid Usage):
 1. Problem Understanding
 2. Root Cause
diff --git a/.codex/skills/gen-ut/SKILL.md b/.codex/skills/gen-ut/SKILL.md
index 8b847f90071..83805e26257 100644
--- a/.codex/skills/gen-ut/SKILL.md
+++ b/.codex/skills/gen-ut/SKILL.md
@@ -43,6 +43,20 @@ Test class placeholder convention:
 - `R10`: If a related test class already exists for a target class, extend 
that class to add only missing-coverage tests; create a new test class only 
when no related test class exists.
 - `R11`: Do not claim completion if target tests were not actually executed 
due compile/runtime blockers. First remove blockers with minimal test-scope 
fixes and rerun verification;
   only when blockers are outside scope and cannot be resolved safely in-turn, 
report exact blocker files/lines/commands and request user decision.
+- `R12`: Boolean assertion policy in tests:
+  - Must use `assertTrue` / `assertFalse` for boolean checks.
+  - Forbidden assertion patterns:
+    - `assertThat(<boolean expression>, is(true))`
+    - `assertThat(<boolean expression>, is(false))`
+    - `assertEquals(true, ...)`
+    - `assertEquals(false, ...)`
+- `R13`: Hard gate for `R12`:
+  - Use the unified scan regex:
+    - 
`assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,`
+  - Run hard-gate scan twice:
+    - after test implementation (early fail-fast gate);
+    - before final delivery (final release gate).
+  - If any match is found, task state is "not complete" until all violations 
are fixed and scan is rerun clean.
 
 ## Execution Boundary
 
@@ -58,9 +72,11 @@ Test class placeholder convention:
 3. Output branch-path inventory according to `R4`.
 4. Output branch-to-test mapping according to `R5`.
 5. Perform dead-code analysis according to `R8` and record findings.
-6. Implement or extend tests according to `R2/R3/R6/R7/R10`.
-7. Run verification commands and iterate.
-8. Deliver results using the output structure.
+6. Implement or extend tests according to `R2/R3/R6/R7/R10/R12/R13`.
+7. Run the first `R13` hard-gate scan (early fail-fast) and fix all hits.
+8. Run verification commands and iterate.
+9. Run the second `R13` hard-gate scan (final release gate) and ensure clean.
+10. Deliver results using the output structure.
 
 ## Verification and Commands
 
@@ -81,6 +97,11 @@ With module input:
 ./mvnw -pl <module> -am -Pcheck checkstyle:check -DskipTests
 ```
 
+4. `R13` hard-gate scan (must be clean, run in step 7 and step 9):
+```bash
+bash -lc 'if rg -n 
"assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,"
 <module>/src/test/java; then echo "[R13] forbidden boolean assertion found"; 
exit 1; fi'
+```
+
 Without module input:
 
 1. Targeted unit tests:
@@ -98,6 +119,11 @@ Without module input:
 ./mvnw -Pcheck checkstyle:check -DskipTests
 ```
 
+4. `R13` hard-gate scan (must be clean, run in step 7 and step 9):
+```bash
+bash -lc 'if rg -n 
"assertThat\\s*\\(.*is\\s*\\(\\s*(true|false)\\s*\\)\\s*\\)|assertEquals\\s*\\(\\s*(true|false)\\s*,"
 . --glob "**/src/test/java/**"; then echo "[R13] forbidden boolean assertion 
found"; exit 1; fi'
+```
+
 Command execution rules:
 - Record every command and exit code.
 - If a command fails, record the failure reason and execute at least one 
remediation attempt; if still blocked, continue clearing blockers within test 
scope before escalating.
@@ -105,18 +131,20 @@ Command execution rules:
 
 ## Output Structure
 
-Respond in the same language as the user and follow this order:
+Follow this order:
 
-1. Goal and constraints (mapped to `R1-R11`)
+1. Goal and constraints (mapped to `R1-R13`)
 2. Plan and implementation (including branch mapping result)
 3. Dead-code and coverage results (according to `R8/R9`)
 4. Verification commands and exit codes
-5. Risks and next actions
+5. `R13` hard-gate evidence (both scan commands and exit codes)
+6. Risks and next actions
 
 ## Quality Self-Check
 
 - Rule definitions must exist only in "Mandatory Constraints"; other sections 
should reference rule IDs only.
-- Final state must satisfy `R9`, and all applicable rules (including `R10` and 
`R11`) must be met, with complete command and exit-code records.
+- Final state must satisfy `R9`, and all applicable rules (including `R10`, 
`R11`, `R12`, and `R13`) must be met, with complete command and exit-code 
records.
+- `R13` command is mandatory evidence; missing `R13` command record or 
non-clean scan means not complete.
 
 ## Maintenance Rules
 
diff --git a/.codex/skills/review-pr/SKILL.md b/.codex/skills/review-pr/SKILL.md
index 8a6395a5392..0edf8989ed1 100644
--- a/.codex/skills/review-pr/SKILL.md
+++ b/.codex/skills/review-pr/SKILL.md
@@ -34,10 +34,8 @@ description: >-
    and list the minimum additional information required.
 5. Change request replies must be gentle in tone and contain no emojis.
 6. If unrelated changes exist, you must explicitly ask for rollback; if none 
exist, do not output that section.
-7. Use the same language as the user;
-   if the user explicitly specifies a language, prioritize that language.
-8. Any "fallback-only without root-cause repair" or "unresolved risk" must not 
receive `Merge Verdict: Mergeable`.
-9. Review only the PR's latest code version; do not reuse conclusions from 
older versions.
+7. Any "fallback-only without root-cause repair" or "unresolved risk" must not 
receive `Merge Verdict: Mergeable`.
+8. Review only the PR's latest code version; do not reuse conclusions from 
older versions.
 
 ## Execution Boundary
 
diff --git a/AGENTS.md b/AGENTS.md
index c9e3f481742..e37047451fa 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -62,7 +62,7 @@ This guide is written **for AI coding agents only**. Follow 
it literally; improv
     - `topic`: technical topic or concept (e.g., "adaptive servo control").
     - `depth`: 1-3 to control semantic layers.
 
-## ⚠️ Dangerous Operation Confirmation Mechanism
+## Dangerous Operation Confirmation Mechanism
 
 ### High-Risk Operation Checklist—obtain explicit confirmation **before** 
doing any of the following:
 - **File System**: deleting files/directories, bulk edits, or moving system 
files.
@@ -74,7 +74,7 @@ This guide is written **for AI coding agents only**. Follow 
it literally; improv
 
 ### Confirmation Template
 
-⚠️ Dangerous operation detected! Operation type: [specific action] Scope of 
impact: [affected area] Risk assessment: [potential consequence] Please confirm 
whether to continue. [Requires explicit “yes”, “confirm”, or “proceed”]
+Dangerous operation detected! Operation type: [specific action] Scope of 
impact: [affected area] Risk assessment: [potential consequence] Please confirm 
whether to continue. [Requires explicit “yes”, “confirm”, or “proceed”]
 
 ## Workflow
 - Use Sequential Thinking when tasks need decomposition: 6-10 steps (fallback 
3-5), one sentence each, actionable.
@@ -101,8 +101,9 @@ This guide is written **for AI coding agents only**. Follow 
it literally; improv
 ## Response Style
 
 ### Language and Tone
+- **Language Consistency**: respond in the same language as the user; if the 
user explicitly specifies a language, prioritize that language.
 - **Friendly and Natural**: interact like a professional peer; avoid stiff 
formal language.
-- **Use Light Accents**: prepend headings or bullets with emojis such as ✨⚠️ 
to highlight key points.
+- **No Emojis or Symbols**: do not use emojis or decorative graphic symbols in 
any reply.
 - **Hit the Point Fast**: start with a sentence that captures the core idea, 
especially for complex problems.
 
 ### Content Organization
@@ -110,7 +111,7 @@ This guide is written **for AI coding agents only**. Follow 
it literally; improv
 - **Focused Bullets**: break long paragraphs into short sentences or bullets, 
each covering a single idea.
 - **Logical Flow**: use ordered lists for multi-step work (1. 2. 3.) and 
unordered lists for peers (- or *).
 - **Proper Spacing**: keep blank lines or `---` between blocks to boost 
readability.
-> ❌ Avoid complex tables in the terminal (especially for long, code-heavy, or 
narrative content).
+> Avoid complex tables in the terminal (especially for long, code-heavy, or 
narrative content).
 
 ### Visual & Layout Optimization
 - **Keep It Simple**: limit each line length to ≤200 characters.
@@ -135,7 +136,7 @@ This guide is written **for AI coding agents only**. Follow 
it literally; improv
 - **Visible Status**: surface progress for important actions (e.g., 
“Processing...”).
 - **Friendly Errors**: clearly explain failures and suggest actionable fixes.
 
-### ✅ Ending Suggestions
+### Ending Suggestions
 - Append a **short summary** after complex content to reiterate the core 
points.
 - **Guide the Next Step**: close with actionable advice, instructions, or an 
invitation for follow-up questions.
 
diff --git 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxyDatabaseConnectionManagerTest.java
 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxyDatabaseConnectionManagerTest.java
index f4c280d90a6..6803abf5d23 100644
--- 
a/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxyDatabaseConnectionManagerTest.java
+++ 
b/proxy/backend/core/src/test/java/org/apache/shardingsphere/proxy/backend/connector/ProxyDatabaseConnectionManagerTest.java
@@ -30,6 +30,7 @@ import 
org.apache.shardingsphere.infra.metadata.database.resource.unit.StorageUn
 import org.apache.shardingsphere.infra.metadata.database.rule.RuleMetaData;
 import 
org.apache.shardingsphere.infra.metadata.statistics.ShardingSphereStatistics;
 import 
org.apache.shardingsphere.infra.metadata.statistics.builder.ShardingSphereStatisticsFactory;
+import org.apache.shardingsphere.infra.rule.ShardingSphereRule;
 import 
org.apache.shardingsphere.infra.session.connection.transaction.TransactionConnectionContext;
 import org.apache.shardingsphere.infra.spi.type.typed.TypedSPILoader;
 import org.apache.shardingsphere.mode.manager.ContextManager;
@@ -44,14 +45,19 @@ import 
org.apache.shardingsphere.proxy.backend.handler.ProxyBackendHandler;
 import org.apache.shardingsphere.proxy.backend.session.ConnectionSession;
 import 
org.apache.shardingsphere.proxy.backend.session.RequiredSessionVariableRecorder;
 import 
org.apache.shardingsphere.proxy.backend.session.transaction.TransactionStatus;
+import 
org.apache.shardingsphere.sql.parser.statement.core.enums.TransactionIsolationLevel;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.AutoMockExtension;
 import 
org.apache.shardingsphere.test.infra.framework.extension.mock.StaticMockSettings;
 import org.apache.shardingsphere.transaction.api.TransactionType;
 import org.apache.shardingsphere.transaction.rule.TransactionRule;
+import org.apache.shardingsphere.transaction.spi.TransactionHook;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 import org.mockito.Answers;
 import org.mockito.Mock;
 import org.mockito.MockedConstruction;
@@ -69,21 +75,26 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Stream;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertThrows;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyList;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.mockConstruction;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoInteractions;
@@ -157,6 +168,12 @@ class ProxyDatabaseConnectionManagerTest {
         assertTrue(connectionSession.getTransactionStatus().isInTransaction());
     }
     
+    @Test
+    void assertGetConnectionsWithNullDatabaseName() {
+        NullPointerException actualException = 
assertThrows(NullPointerException.class, () -> 
databaseConnectionManager.getConnections(null, "ds1", 0, 1, 
ConnectionMode.MEMORY_STRICTLY));
+        assertThat(actualException.getMessage(), is("Current database name is 
null."));
+    }
+    
     @Test
     void assertGetConnectionSizeLessThanCache() throws SQLException {
         connectionSession.getTransactionStatus().setInTransaction(true);
@@ -190,6 +207,35 @@ class ProxyDatabaseConnectionManagerTest {
         assertTrue(connectionSession.getTransactionStatus().isInTransaction());
     }
     
+    @Test
+    void assertGetConnectionWithTransactionHook() throws SQLException {
+        connectionSession.getTransactionStatus().setInTransaction(true);
+        ShardingSphereRule rule = mock(ShardingSphereRule.class);
+        TransactionHook transactionHook = mock(TransactionHook.class);
+        setTransactionHooks(Collections.singletonMap(rule, transactionHook));
+        when(backendDataSource.getConnections(anyString(), anyString(), eq(1), 
any())).thenReturn(MockConnectionUtils.mockNewConnections(1));
+        databaseConnectionManager.getConnections("foo_db", "ds1", 0, 1, 
ConnectionMode.MEMORY_STRICTLY);
+        verify(transactionHook).afterCreateConnections(eq(rule), any(), 
anyList(), any());
+    }
+    
+    @Test
+    void assertGetConnectionWithReplayTransactionOption() throws SQLException {
+        when(connectionSession.isReadOnly()).thenReturn(true);
+        
when(connectionSession.getIsolationLevel()).thenReturn(Optional.of(TransactionIsolationLevel.READ_UNCOMMITTED));
+        Connection connection = mock(Connection.class);
+        when(backendDataSource.getConnections(anyString(), anyString(), eq(1), 
any())).thenReturn(Collections.singletonList(connection));
+        databaseConnectionManager.getConnections("foo_db", "ds1", 0, 1, 
ConnectionMode.MEMORY_STRICTLY);
+        verify(connection).setReadOnly(true);
+        verify(connection).setTransactionIsolation(anyInt());
+    }
+    
+    @Test
+    void assertGetConnectionWithNullConnection() throws SQLException {
+        when(backendDataSource.getConnections(anyString(), anyString(), eq(1), 
any())).thenReturn(Collections.singletonList(null));
+        List<Connection> actualConnections = 
databaseConnectionManager.getConnections("foo_db", "ds1", 0, 1, 
ConnectionMode.MEMORY_STRICTLY);
+        assertThat(actualConnections, is(Collections.singletonList(null)));
+    }
+    
     @SneakyThrows(ReflectiveOperationException.class)
     private void setConnectionPostProcessors() {
         ConnectionPostProcessor connectionPostProcessor = 
mock(ConnectionPostProcessor.class);
@@ -198,6 +244,11 @@ class ProxyDatabaseConnectionManagerTest {
         
Plugins.getMemberAccessor().set(ProxyDatabaseConnectionManager.class.getDeclaredField("connectionPostProcessors"),
 databaseConnectionManager, connectionPostProcessors);
     }
     
+    @SneakyThrows(ReflectiveOperationException.class)
+    private void setTransactionHooks(final Map<ShardingSphereRule, 
TransactionHook> transactionHooks) {
+        
Plugins.getMemberAccessor().set(ProxyDatabaseConnectionManager.class.getDeclaredField("transactionHooks"),
 databaseConnectionManager, transactionHooks);
+    }
+    
     @SuppressWarnings("unchecked")
     @Test
     void assertCloseConnectionsCorrectlyWhenNotForceRollback() throws 
ReflectiveOperationException, SQLException {
@@ -219,28 +270,36 @@ class ProxyDatabaseConnectionManagerTest {
         assertTrue(connectionPostProcessors.isEmpty());
     }
     
-    @Test
-    void assertCloseConnectionsCorrectlyWhenForceRollbackAndNotInTransaction() 
throws SQLException {
-        connectionSession.getTransactionStatus().setInTransaction(false);
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("closeConnectionsWithForceRollbackArguments")
+    void assertCloseConnectionsWithForceRollback(final String scenario, final 
boolean inTransaction, final boolean rollbackFailed, final int 
expectedRollbackCount) throws SQLException {
+        
connectionSession.getTransactionStatus().setInTransaction(inTransaction);
         Connection connection = prepareCachedConnections();
-        databaseConnectionManager.closeConnections(true);
-        verify(connection, never()).rollback();
-    }
-    
-    @Test
-    void assertCloseConnectionsCorrectlyWhenForceRollbackAndInTransaction() 
throws SQLException {
-        connectionSession.getTransactionStatus().setInTransaction(true);
-        Connection connection = prepareCachedConnections();
-        databaseConnectionManager.closeConnections(true);
-        verify(connection).rollback();
+        if (rollbackFailed) {
+            doThrow(new SQLException("")).when(connection).rollback();
+        }
+        Collection<SQLException> actualExceptions = 
databaseConnectionManager.closeConnections(true);
+        assertTrue(actualExceptions.isEmpty());
+        verify(connection, times(expectedRollbackCount)).rollback();
+        verify(connection).close();
     }
     
-    @Test
-    void assertCloseConnectionsCorrectlyWhenSQLExceptionThrown() throws 
SQLException {
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("closeConnectionsWithCloseExceptionArguments")
+    void assertCloseConnectionsWithCloseException(final String scenario, final 
boolean connectionClosed, final boolean checkClosedFailed, final int 
expectedExceptionCount) throws SQLException {
         Connection connection = prepareCachedConnections();
-        SQLException sqlException = new SQLException("");
-        doThrow(sqlException).when(connection).close();
-        
assertTrue(databaseConnectionManager.closeConnections(false).contains(sqlException));
+        SQLException expectedException = new SQLException("");
+        doThrow(expectedException).when(connection).close();
+        if (checkClosedFailed) {
+            when(connection.isClosed()).thenThrow(new SQLException(""));
+        } else {
+            when(connection.isClosed()).thenReturn(connectionClosed);
+        }
+        Collection<SQLException> actualExceptions = 
databaseConnectionManager.closeConnections(false);
+        assertThat(actualExceptions.size(), is(expectedExceptionCount));
+        if (1 == expectedExceptionCount) {
+            assertThat(actualExceptions.iterator().next(), 
is(expectedException));
+        }
     }
     
     @SuppressWarnings("JDBCResourceOpenedButNotSafelyClosed")
@@ -267,6 +326,13 @@ class ProxyDatabaseConnectionManagerTest {
         verify(actualConnection.createStatement()).execute("SET key=value");
     }
     
+    @Test
+    void assertGetConnectionsWithEmptyConnectionAndSessionVariables() throws 
SQLException {
+        
connectionSession.getRequiredSessionVariableRecorder().setVariable("key", 
"value");
+        when(backendDataSource.getConnections(anyString(), anyString(), eq(1), 
any())).thenReturn(Collections.emptyList());
+        assertThrows(IndexOutOfBoundsException.class, () -> 
databaseConnectionManager.getConnections("foo_db", "ds1", 0, 1, 
ConnectionMode.CONNECTION_STRICTLY));
+    }
+    
     @SuppressWarnings("JDBCResourceOpenedButNotSafelyClosed")
     @Test
     void assertGetConnectionsAndFailedToReplaySessionVariables() throws 
SQLException {
@@ -285,6 +351,25 @@ class ProxyDatabaseConnectionManagerTest {
         }
     }
     
+    @SuppressWarnings("JDBCResourceOpenedButNotSafelyClosed")
+    @Test
+    void assertGetConnectionsAndFailedToReleaseConnection() throws 
SQLException {
+        
connectionSession.getRequiredSessionVariableRecorder().setVariable("key", 
"value");
+        SQLException expectedException = new SQLException("");
+        SQLException expectedNextException = new SQLException("");
+        Connection firstConnection = mock(Connection.class, 
RETURNS_DEEP_STUBS);
+        Connection secondConnection = mock(Connection.class, 
RETURNS_DEEP_STUBS);
+        
when(firstConnection.getMetaData().getDatabaseProductName()).thenReturn("PostgreSQL");
+        when(firstConnection.createStatement().execute("SET 
key=value")).thenThrow(expectedException);
+        
when(secondConnection.getMetaData().getDatabaseProductName()).thenReturn("PostgreSQL");
+        doThrow(expectedNextException).when(secondConnection).close();
+        
when(ProxyContext.getInstance().getBackendDataSource().getConnections(anyString(),
 anyString(), anyInt(), any(ConnectionMode.class)))
+                .thenReturn(Arrays.asList(firstConnection, secondConnection));
+        SQLException actualException = assertThrows(SQLException.class, () -> 
databaseConnectionManager.getConnections("foo_db", "", 0, 2, 
ConnectionMode.CONNECTION_STRICTLY));
+        assertThat(actualException, is(expectedException));
+        assertThat(actualException.getNextException(), 
is(expectedNextException));
+    }
+    
     @Test
     void assertGetConnectionsWithoutTransactions() throws SQLException {
         connectionSession.getTransactionStatus().setInTransaction(false);
@@ -318,13 +403,17 @@ class ProxyDatabaseConnectionManagerTest {
                 not(databaseConnectionManager.getConnections("foo_db", "ds1", 
1, 1, ConnectionMode.MEMORY_STRICTLY)));
     }
     
-    @Test
-    void assertHandleAutoCommit() {
-        when(connectionSession.isAutoCommit()).thenReturn(false);
-        connectionSession.getTransactionStatus().setInTransaction(false);
+    @ParameterizedTest(name = "{0}")
+    @MethodSource("handleAutoCommitArguments")
+    void assertHandleAutoCommit(final String scenario, final boolean 
autoCommit, final boolean inTransaction, final int 
expectedTransactionManagerCount) {
+        when(connectionSession.isAutoCommit()).thenReturn(autoCommit);
+        
connectionSession.getTransactionStatus().setInTransaction(inTransaction);
         try (MockedConstruction<ProxyBackendTransactionManager> 
mockedConstruction = mockConstruction(ProxyBackendTransactionManager.class)) {
             databaseConnectionManager.handleAutoCommit();
-            verify(mockedConstruction.constructed().get(0)).begin();
+            assertThat(mockedConstruction.constructed().size(), 
is(expectedTransactionManagerCount));
+            if (1 == expectedTransactionManagerCount) {
+                verify(mockedConstruction.constructed().get(0)).begin();
+            }
         }
     }
     
@@ -408,6 +497,32 @@ class ProxyDatabaseConnectionManagerTest {
         assertThat(getInUseProxyBackendHandlers(), 
is(Collections.singleton(inUseHandler)));
     }
     
+    @Test
+    void assertCloseExecutionResourcesInTransactionWhenClosed() throws 
BackendConnectionException, SQLException {
+        connectionSession.getTransactionStatus().setInTransaction(true);
+        ProxyBackendHandler inUseHandler = mock(ProxyBackendHandler.class);
+        getProxyBackendHandlers().add(inUseHandler);
+        getInUseProxyBackendHandlers().add(inUseHandler);
+        Connection cachedConnection = mock(Connection.class);
+        
databaseConnectionManager.getCachedConnections().put("ignoredDataSourceName", 
cachedConnection);
+        databaseConnectionManager.getClosed().set(true);
+        databaseConnectionManager.closeExecutionResources();
+        verify(inUseHandler).close();
+        verify(cachedConnection).rollback();
+        verify(cachedConnection).close();
+    }
+    
+    @Test
+    void assertCloseExecutionResourcesWithException() throws SQLException {
+        ProxyBackendHandler handler = mock(ProxyBackendHandler.class);
+        SQLException expectedException = new SQLException("");
+        doThrow(expectedException).when(handler).close();
+        getProxyBackendHandlers().add(handler);
+        BackendConnectionException actualException = 
assertThrows(BackendConnectionException.class, () -> 
databaseConnectionManager.closeExecutionResources());
+        assertThat(actualException.getExceptions().size(), is(1));
+        assertThat(actualException.getExceptions().iterator().next(), 
is(expectedException));
+    }
+    
     @SuppressWarnings("unchecked")
     @SneakyThrows(ReflectiveOperationException.class)
     private Collection<ProxyBackendHandler> getProxyBackendHandlers() {
@@ -473,6 +588,13 @@ class ProxyDatabaseConnectionManagerTest {
         assertThat(actualExceptions, 
is(Collections.singletonList(expectedException)));
     }
     
+    @Test
+    void assertCloseConnectionsWithoutCachedConnectionsAndVariables() {
+        
connectionSession.getRequiredSessionVariableRecorder().setVariable("key", 
"default");
+        databaseConnectionManager.closeConnections(false);
+        
assertFalse(connectionSession.getRequiredSessionVariableRecorder().isEmpty());
+    }
+    
     @Test
     void assertGetDataSourceNamesOfCachedConnections() {
         
databaseConnectionManager.getCachedConnections().put(connectionSession.getUsedDatabaseName()
 + ".ds_0", null);
@@ -482,4 +604,33 @@ class ProxyDatabaseConnectionManagerTest {
         Collections.sort(actual);
         assertThat(actual, is(Arrays.asList("ds_0", "ds_1", "ds_2")));
     }
+    
+    @Test
+    void assertGetDataSourceNamesWithoutCurrentDatabaseName() {
+        
databaseConnectionManager.getCachedConnections().put(connectionSession.getUsedDatabaseName()
 + ".ds_0", mock(Connection.class));
+        databaseConnectionManager.getCachedConnections().put("schema_1.ds_1", 
mock(Connection.class));
+        List<String> actual = new 
ArrayList<>(databaseConnectionManager.getUsedDataSourceNames());
+        assertThat(actual, is(Collections.singletonList("ds_0")));
+    }
+    
+    private static Stream<Arguments> 
closeConnectionsWithForceRollbackArguments() {
+        return Stream.of(
+                
Arguments.of("closeConnections_forceRollback_notInTransaction", false, false, 
0),
+                Arguments.of("closeConnections_forceRollback_inTransaction", 
true, false, 1),
+                Arguments.of("closeConnections_forceRollback_rollbackFailed", 
true, true, 1));
+    }
+    
+    private static Stream<Arguments> 
closeConnectionsWithCloseExceptionArguments() {
+        return Stream.of(
+                Arguments.of("closeConnections_closeException_notClosed", 
false, false, 1),
+                
Arguments.of("closeConnections_closeException_connectionClosed", true, false, 
0),
+                
Arguments.of("closeConnections_closeException_checkClosedFailed", false, true, 
1));
+    }
+    
+    private static Stream<Arguments> handleAutoCommitArguments() {
+        return Stream.of(
+                Arguments.of("handleAutoCommit_beginTransaction", false, 
false, 1),
+                Arguments.of("handleAutoCommit_autoCommitEnabled", true, 
false, 0),
+                Arguments.of("handleAutoCommit_inTransaction", false, true, 
0));
+    }
 }

Reply via email to