This is an automated email from the ASF dual-hosted git repository.
jimin pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/incubator-seata.git
The following commit(s) were added to refs/heads/2.x by this push:
new 2e8983a3e3 test: improve test coverage for server/console (#7774)
2e8983a3e3 is described below
commit 2e8983a3e321a698ef1cadd88f6f92ad4bc4e40f
Author: Eric Wang <[email protected]>
AuthorDate: Tue Nov 11 08:59:23 2025 +0300
test: improve test coverage for server/console (#7774)
---
.../server/console/exception/ConsoleException.java | 7 +-
.../impl/redis/BranchSessionRedisServiceImpl.java | 4 +-
.../impl/redis/GlobalLockRedisServiceImpl.java | 4 +-
.../impl/redis/GlobalSessionRedisServiceImpl.java | 6 +-
.../impl/db/BranchSessionDBServiceImplTest.java | 163 +++++++++++
.../impl/db/GlobalLockDBServiceImplTest.java | 324 +++++++++++++++++++++
.../impl/db/GlobalSessionDBServiceImplTest.java | 274 +++++++++++++++++
.../redis/BranchSessionRedisServiceImplTest.java | 184 ++++++++++++
.../impl/redis/GlobalLockRedisServiceImplTest.java | 292 +++++++++++++++++++
.../redis/GlobalSessionRedisServiceImplTest.java | 265 +++++++++++++++++
10 files changed, 1515 insertions(+), 8 deletions(-)
diff --git
a/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java
b/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java
index 50a65268ef..cfa9db1ab6 100644
---
a/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java
+++
b/server/src/main/java/org/apache/seata/server/console/exception/ConsoleException.java
@@ -25,10 +25,15 @@ public class ConsoleException extends RuntimeException {
private String logMessage;
public ConsoleException(Throwable cause, String logMessage) {
- super(cause);
+ super(logMessage, cause);
this.logMessage = logMessage;
}
+ @Override
+ public String getMessage() {
+ return logMessage;
+ }
+
public String getLogMessage() {
return logMessage;
}
diff --git
a/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java
b/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java
index e528bab566..fb53bacb94 100644
---
a/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java
+++
b/server/src/main/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImpl.java
@@ -44,7 +44,7 @@ public class BranchSessionRedisServiceImpl extends
AbstractBranchService impleme
@Override
public PageResult<BranchSessionVO> queryByXid(String xid) {
if (StringUtils.isBlank(xid)) {
- return PageResult.success();
+ return PageResult.success(new ArrayList<>(), 0, 1, 10);
}
List<BranchSessionVO> branchSessionVos = new ArrayList<>();
@@ -61,6 +61,6 @@ public class BranchSessionRedisServiceImpl extends
AbstractBranchService impleme
}
}
- return PageResult.success(branchSessionVos, branchSessionVos.size(),
0, branchSessionVos.size());
+ return PageResult.success(branchSessionVos, branchSessionVos.size(),
1, Math.max(branchSessionVos.size(), 10));
}
}
diff --git
a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java
b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java
index 92aea54e66..d2bb579034 100644
---
a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java
+++
b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImpl.java
@@ -20,6 +20,7 @@ import org.apache.seata.common.result.PageResult;
import org.apache.seata.common.result.SingleResult;
import org.apache.seata.common.util.BeanUtils;
import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.PageUtil;
import org.apache.seata.core.exception.TransactionException;
import org.apache.seata.server.console.entity.param.GlobalLockParam;
import org.apache.seata.server.console.entity.vo.GlobalLockVO;
@@ -41,7 +42,6 @@ import java.util.Map;
import static org.apache.seata.common.Constants.ROW_LOCK_KEY_SPLIT_CHAR;
import static
org.apache.seata.common.exception.FrameworkErrorCode.ParameterRequired;
-import static org.apache.seata.common.result.PageResult.checkPage;
import static org.apache.seata.common.util.StringUtils.isNotBlank;
import static
org.apache.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX;
import static
org.apache.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX;
@@ -58,10 +58,10 @@ public class GlobalLockRedisServiceImpl extends
AbstractLockService implements G
@Override
public PageResult<GlobalLockVO> query(GlobalLockParam param) {
+ PageUtil.checkParam(param.getPageNum(), param.getPageSize());
int total = 0;
List<GlobalLockVO> globalLockVos;
- checkPage(param);
if (isNotBlank(param.getXid())) {
globalLockVos = queryGlobalByXid(param.getXid());
total = globalLockVos.size();
diff --git
a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
index 60d4d391b5..0bd932aed7 100644
---
a/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
+++
b/server/src/main/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImpl.java
@@ -18,6 +18,7 @@ package org.apache.seata.server.console.impl.redis;
import org.apache.seata.common.result.PageResult;
import org.apache.seata.common.util.CollectionUtils;
+import org.apache.seata.common.util.PageUtil;
import org.apache.seata.core.model.GlobalStatus;
import org.apache.seata.server.console.entity.param.GlobalSessionParam;
import org.apache.seata.server.console.entity.vo.GlobalSessionVO;
@@ -37,7 +38,6 @@ import java.util.List;
import java.util.stream.Collectors;
import static
org.apache.seata.common.exception.FrameworkErrorCode.ParameterRequired;
-import static org.apache.seata.common.result.PageResult.checkPage;
import static org.apache.seata.common.util.StringUtils.isBlank;
import static org.apache.seata.common.util.StringUtils.isNotBlank;
import static
org.apache.seata.server.storage.SessionConverter.convertToGlobalSessionVo;
@@ -54,6 +54,8 @@ public class GlobalSessionRedisServiceImpl extends
AbstractGlobalService impleme
@Override
public PageResult<GlobalSessionVO> query(GlobalSessionParam param) {
+ PageUtil.checkParam(param.getPageNum(), param.getPageSize());
+
List<GlobalSessionVO> result = new ArrayList<>();
Long total = 0L;
if (param.getTimeStart() != null || param.getTimeEnd() != null) {
@@ -65,8 +67,6 @@ public class GlobalSessionRedisServiceImpl extends
AbstractGlobalService impleme
RedisTransactionStoreManager instance =
RedisTransactionStoreManagerFactory.getInstance();
- checkPage(param);
-
if (isBlank(param.getXid()) && param.getStatus() == null) {
total = instance.countByGlobalSessions(GlobalStatus.values());
globalSessions =
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImplTest.java
new file mode 100644
index 0000000000..7d34b978ac
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/db/BranchSessionDBServiceImplTest.java
@@ -0,0 +1,163 @@
+/*
+ * 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.seata.server.console.impl.db;
+
+import org.apache.seata.common.exception.StoreException;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.vo.BranchSessionVO;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for BranchSessionDBServiceImpl
+ */
+@TestPropertySource(properties = {"sessionMode=db", "lockMode=file"})
+class BranchSessionDBServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private BranchSessionDBServiceImpl branchSessionDBService;
+
+ @MockBean
+ private DataSource dataSource;
+
+ private Connection connection;
+ private PreparedStatement preparedStatement;
+ private ResultSet resultSet;
+
+ @BeforeEach
+ void setUp() throws SQLException {
+ // Inject mock DataSource to service
+ ReflectionTestUtils.setField(branchSessionDBService, "dataSource",
dataSource);
+
+ connection = mock(Connection.class);
+ preparedStatement = mock(PreparedStatement.class);
+ resultSet = mock(ResultSet.class);
+ }
+
+ @Test
+ void queryByXidSuccessTest() throws SQLException {
+ String xid = "test-xid-001";
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+
+ when(resultSet.next()).thenReturn(true, true, false);
+ when(resultSet.getString("xid")).thenReturn(xid);
+ when(resultSet.getLong("branch_id")).thenReturn(123456L);
+ when(resultSet.getString("application_id")).thenReturn("test-app");
+ when(resultSet.getString("transaction_id")).thenReturn("1001");
+
when(resultSet.getString("resource_id")).thenReturn("jdbc:mysql://localhost:3306/test");
+ when(resultSet.getString("lock_key")).thenReturn("tb_test:1");
+ when(resultSet.getString("branch_type")).thenReturn("AT");
+ when(resultSet.getInt("status")).thenReturn(1);
+ when(resultSet.getString("client_id")).thenReturn("192.168.1.1:8091");
+ when(resultSet.getString("application_data")).thenReturn("{}");
+
when(resultSet.getLong("gmt_create")).thenReturn(System.currentTimeMillis());
+
when(resultSet.getLong("gmt_modified")).thenReturn(System.currentTimeMillis());
+
+ PageResult<BranchSessionVO> result =
branchSessionDBService.queryByXid(xid);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+
+ verify(preparedStatement, times(1)).setObject(1, xid);
+ verify(resultSet, times(3)).next();
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryByXidEmptyResultTest() throws SQLException {
+ String xid = "test-xid-002";
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(resultSet.next()).thenReturn(false);
+
+ PageResult<BranchSessionVO> result =
branchSessionDBService.queryByXid(xid);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryByXidBlankXidTest() {
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
branchSessionDBService.queryByXid(""));
+ Assertions.assertEquals("xid should not be blank",
exception.getMessage());
+ }
+
+ @Test
+ void queryByXidNullXidTest() {
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
branchSessionDBService.queryByXid(null));
+ Assertions.assertEquals("xid should not be blank",
exception.getMessage());
+ }
+
+ @Test
+ void queryByXidSqlExceptionTest() throws SQLException {
+ String xid = "test-xid-003";
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenThrow(new
SQLException("Database connection error"));
+
+ StoreException exception =
+ Assertions.assertThrows(StoreException.class, () ->
branchSessionDBService.queryByXid(xid));
+ Assertions.assertNotNull(exception.getCause());
+ Assertions.assertTrue(exception.getCause() instanceof SQLException);
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryByXidConnectionExceptionTest() throws SQLException {
+ String xid = "test-xid-004";
+
+ when(dataSource.getConnection()).thenThrow(new SQLException("Cannot
get connection"));
+
+ StoreException exception =
+ Assertions.assertThrows(StoreException.class, () ->
branchSessionDBService.queryByXid(xid));
+ Assertions.assertNotNull(exception.getCause());
+ Assertions.assertTrue(exception.getCause() instanceof SQLException);
+ }
+}
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImplTest.java
new file mode 100644
index 0000000000..6c60f7aac9
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalLockDBServiceImplTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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.seata.server.console.impl.db;
+
+import org.apache.seata.common.exception.StoreException;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.result.SingleResult;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.param.GlobalLockParam;
+import org.apache.seata.server.console.entity.vo.GlobalLockVO;
+import org.apache.seata.server.console.exception.ConsoleException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * Test for GlobalLockDBServiceImpl
+ */
+@TestPropertySource(properties = {"lockMode=db", "sessionMode=file"})
+class GlobalLockDBServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private GlobalLockDBServiceImpl globalLockDBService;
+
+ @MockBean
+ private DataSource dataSource;
+
+ private Connection connection;
+ private PreparedStatement preparedStatement;
+ private PreparedStatement countPreparedStatement;
+ private ResultSet resultSet;
+ private ResultSet countResultSet;
+
+ @BeforeEach
+ void setUp() throws SQLException {
+ // Inject mock DataSource to service
+ ReflectionTestUtils.setField(globalLockDBService, "dataSource",
dataSource);
+
+ connection = org.mockito.Mockito.mock(Connection.class);
+ preparedStatement = org.mockito.Mockito.mock(PreparedStatement.class);
+ countPreparedStatement =
org.mockito.Mockito.mock(PreparedStatement.class);
+ resultSet = org.mockito.Mockito.mock(ResultSet.class);
+ countResultSet = org.mockito.Mockito.mock(ResultSet.class);
+ }
+
+ @Test
+ void querySuccessTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(true, true, false);
+ when(resultSet.getString("xid")).thenReturn("test-xid-001");
+ when(resultSet.getLong("transaction_id")).thenReturn(1001L);
+ when(resultSet.getLong("branch_id")).thenReturn(2001L);
+
when(resultSet.getString("resource_id")).thenReturn("jdbc:mysql://localhost:3306/test");
+ when(resultSet.getString("table_name")).thenReturn("tb_order");
+ when(resultSet.getString("pk")).thenReturn("1");
+
when(resultSet.getString("row_key")).thenReturn("jdbc:mysql://localhost:3306/test^^^tb_order^^^1");
+
when(resultSet.getLong("gmt_create")).thenReturn(System.currentTimeMillis());
+
when(resultSet.getLong("gmt_modified")).thenReturn(System.currentTimeMillis());
+
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(2);
+
+ PageResult<GlobalLockVO> result = globalLockDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+ Assertions.assertEquals(1, result.getPageNum());
+ Assertions.assertEquals(10, result.getPageSize());
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryWithMultipleParamsTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+ param.setTableName("tb_order");
+ param.setTransactionId("1001");
+ param.setBranchId("2001");
+ param.setTimeStart(System.currentTimeMillis() - 86400000L);
+ param.setTimeEnd(System.currentTimeMillis());
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(false);
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(0);
+
+ PageResult<GlobalLockVO> result = globalLockDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryEmptyResultTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(false);
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(0);
+
+ PageResult<GlobalLockVO> result = globalLockDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryInvalidPageNumTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(0);
+ param.setPageSize(10);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void queryInvalidPageSizeTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(0);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void querySqlExceptionTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenThrow(new
SQLException("Database error"));
+
+ StoreException exception =
+ Assertions.assertThrows(StoreException.class, () ->
globalLockDBService.query(param));
+ Assertions.assertNotNull(exception.getCause());
+ Assertions.assertTrue(exception.getCause() instanceof SQLException);
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void deleteLockSuccessTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeUpdate()).thenReturn(1);
+
+ SingleResult<Void> result = globalLockDBService.deleteLock(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+
+ verify(preparedStatement, times(1)).setString(eq(1), anyString());
+ verify(preparedStatement, times(1)).setString(eq(2),
eq("test-xid-001"));
+ verify(preparedStatement, times(1)).executeUpdate();
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void deleteLockMissingXidTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingBranchIdTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingTableNameTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setBranchId("2001");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingPkTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingResourceIdTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockSqlExceptionTest() throws SQLException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("test-xid-001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeUpdate()).thenThrow(new
SQLException("Database error"));
+
+ ConsoleException exception =
+ Assertions.assertThrows(ConsoleException.class, () ->
globalLockDBService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ Assertions.assertTrue(exception.getMessage().contains("delete global
lock"));
+
+ verify(connection, times(1)).close();
+ }
+}
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImplTest.java
new file mode 100644
index 0000000000..c91abfcdf4
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/db/GlobalSessionDBServiceImplTest.java
@@ -0,0 +1,274 @@
+/*
+ * 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.seata.server.console.impl.db;
+
+import org.apache.seata.common.exception.StoreException;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.param.GlobalSessionParam;
+import org.apache.seata.server.console.entity.vo.BranchSessionVO;
+import org.apache.seata.server.console.entity.vo.GlobalSessionVO;
+import org.apache.seata.server.console.service.BranchSessionService;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.test.context.TestPropertySource;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import javax.sql.DataSource;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@TestPropertySource(properties = {"sessionMode=db", "lockMode=file"})
+class GlobalSessionDBServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private GlobalSessionDBServiceImpl globalSessionDBService;
+
+ @MockBean
+ private DataSource dataSource;
+
+ @MockBean
+ private BranchSessionService branchSessionService;
+
+ private Connection connection;
+ private PreparedStatement preparedStatement;
+ private PreparedStatement countPreparedStatement;
+ private ResultSet resultSet;
+ private ResultSet countResultSet;
+
+ @BeforeEach
+ void setUp() throws SQLException {
+ // Inject mock DataSource to service
+ ReflectionTestUtils.setField(globalSessionDBService, "dataSource",
dataSource);
+
+ connection = mock(Connection.class);
+ preparedStatement = mock(PreparedStatement.class);
+ countPreparedStatement = mock(PreparedStatement.class);
+ resultSet = mock(ResultSet.class);
+ countResultSet = mock(ResultSet.class);
+ }
+
+ @Test
+ void querySuccessTest() throws SQLException {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+ param.setWithBranch(false);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(true, true, false);
+ when(resultSet.getString("xid")).thenReturn("test-xid-001");
+ when(resultSet.getLong("transaction_id")).thenReturn(1001L);
+ when(resultSet.getInt("status")).thenReturn(1);
+ when(resultSet.getString("application_id")).thenReturn("test-app");
+
when(resultSet.getString("transaction_service_group")).thenReturn("default_tx_group");
+
when(resultSet.getString("transaction_name")).thenReturn("test-transaction");
+ when(resultSet.getInt("timeout")).thenReturn(60000);
+
when(resultSet.getLong("begin_time")).thenReturn(System.currentTimeMillis());
+ when(resultSet.getString("application_data")).thenReturn("{}");
+
when(resultSet.getLong("gmt_create")).thenReturn(System.currentTimeMillis());
+
when(resultSet.getLong("gmt_modified")).thenReturn(System.currentTimeMillis());
+
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(2);
+
+ PageResult<GlobalSessionVO> result =
globalSessionDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+ Assertions.assertEquals(1, result.getPageNum());
+ Assertions.assertEquals(10, result.getPageSize());
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryWithBranchTest() throws SQLException {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+ param.setWithBranch(true);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(true, false);
+ when(resultSet.getString("xid")).thenReturn("test-xid-001");
+ when(resultSet.getLong("transaction_id")).thenReturn(1001L);
+ when(resultSet.getInt("status")).thenReturn(1);
+ when(resultSet.getString("application_id")).thenReturn("test-app");
+
when(resultSet.getString("transaction_service_group")).thenReturn("default_tx_group");
+
when(resultSet.getString("transaction_name")).thenReturn("test-transaction");
+ when(resultSet.getInt("timeout")).thenReturn(60000);
+
when(resultSet.getLong("begin_time")).thenReturn(System.currentTimeMillis());
+ when(resultSet.getString("application_data")).thenReturn("{}");
+
when(resultSet.getLong("gmt_create")).thenReturn(System.currentTimeMillis());
+
when(resultSet.getLong("gmt_modified")).thenReturn(System.currentTimeMillis());
+
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(1);
+
+ List<BranchSessionVO> branchSessions = new ArrayList<>();
+ BranchSessionVO branchVO = new BranchSessionVO();
+ branchVO.setXid("test-xid-001");
+ branchVO.setBranchId(2001L);
+ branchSessions.add(branchVO);
+
+ PageResult<BranchSessionVO> branchResult =
PageResult.success(branchSessions, 1, 1, 10);
+
when(branchSessionService.queryByXid("test-xid-001")).thenReturn(branchResult);
+
+ PageResult<GlobalSessionVO> result =
globalSessionDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+
Assertions.assertNotNull(result.getData().get(0).getBranchSessionVOs());
+ Assertions.assertEquals(1,
result.getData().get(0).getBranchSessionVOs().size());
+
+ verify(branchSessionService, times(1)).queryByXid("test-xid-001");
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryWithMultipleParamsTest() throws SQLException {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+ param.setApplicationId("test-app");
+ param.setStatus(1);
+ param.setTransactionName("test-transaction");
+ param.setTimeStart(System.currentTimeMillis() - 86400000L);
+ param.setTimeEnd(System.currentTimeMillis());
+ param.setWithBranch(false);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(false);
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(0);
+
+ PageResult<GlobalSessionVO> result =
globalSessionDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+
+ verify(connection, times(1)).close();
+ }
+
+ @Test
+ void queryEmptyResultTest() throws SQLException {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setWithBranch(false);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+ when(connection.prepareStatement(anyString()))
+ .thenReturn(preparedStatement)
+ .thenReturn(countPreparedStatement);
+ when(preparedStatement.executeQuery()).thenReturn(resultSet);
+ when(countPreparedStatement.executeQuery()).thenReturn(countResultSet);
+
+ when(resultSet.next()).thenReturn(false);
+ when(countResultSet.next()).thenReturn(true);
+ when(countResultSet.getInt(1)).thenReturn(0);
+
+ PageResult<GlobalSessionVO> result =
globalSessionDBService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryInvalidPageNumTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(0);
+ param.setPageSize(10);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalSessionDBService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void queryInvalidPageSizeTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(0);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalSessionDBService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void querySqlExceptionTest() throws SQLException {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("test-xid-001");
+ param.setWithBranch(false);
+
+ when(dataSource.getConnection()).thenReturn(connection);
+
when(connection.prepareStatement(anyString())).thenReturn(preparedStatement);
+ when(preparedStatement.executeQuery()).thenThrow(new
SQLException("Database error"));
+
+ StoreException exception =
+ Assertions.assertThrows(StoreException.class, () ->
globalSessionDBService.query(param));
+ Assertions.assertNotNull(exception.getCause());
+ Assertions.assertTrue(exception.getCause() instanceof SQLException);
+
+ verify(connection, times(1)).close();
+ }
+}
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImplTest.java
new file mode 100644
index 0000000000..015990b02c
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/redis/BranchSessionRedisServiceImplTest.java
@@ -0,0 +1,184 @@
+/*
+ * 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.seata.server.console.impl.redis;
+
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.core.store.BranchTransactionDO;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.vo.BranchSessionVO;
+import
org.apache.seata.server.storage.redis.store.RedisTransactionStoreManager;
+import
org.apache.seata.server.storage.redis.store.RedisTransactionStoreManagerFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
+
+/**
+ * Test for BranchSessionRedisServiceImpl
+ * Integration test that requires real Redis instance
+ * Set -DredisCaseEnabled=true to run these tests
+ */
+@EnabledIfSystemProperty(named = "redisCaseEnabled", matches = "true")
+@TestPropertySource(properties = {"sessionMode=redis", "lockMode=file"})
+class BranchSessionRedisServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private BranchSessionRedisServiceImpl branchSessionRedisService;
+
+ private RedisTransactionStoreManager redisTransactionStoreManager;
+
+ @BeforeEach
+ void setUp() {
+ redisTransactionStoreManager =
RedisTransactionStoreManagerFactory.getInstance();
+ }
+
+ @Test
+ void queryByXidSuccessTest() throws Exception {
+ String xid = "test-console-branch-xid-001";
+
+ // Insert test data
+ BranchTransactionDO branchDO1 = new BranchTransactionDO();
+ branchDO1.setXid(xid);
+ branchDO1.setBranchId(123456L);
+ branchDO1.setResourceGroupId("test-app");
+ branchDO1.setTransactionId(1001L);
+ branchDO1.setResourceId("jdbc:mysql://localhost:3306/test");
+ branchDO1.setBranchType("AT");
+ branchDO1.setStatus(1);
+ branchDO1.setClientId("192.168.1.1:8091");
+ branchDO1.setApplicationData("{}");
+
+ BranchTransactionDO branchDO2 = new BranchTransactionDO();
+ branchDO2.setXid(xid);
+ branchDO2.setBranchId(123457L);
+ branchDO2.setResourceGroupId("test-app");
+ branchDO2.setTransactionId(1001L);
+ branchDO2.setResourceId("jdbc:mysql://localhost:3306/test");
+ branchDO2.setBranchType("AT");
+ branchDO2.setStatus(1);
+ branchDO2.setClientId("192.168.1.1:8091");
+ branchDO2.setApplicationData("{}");
+
+ // Write to Redis via reflection
+ java.lang.reflect.Method insertMethod = redisTransactionStoreManager
+ .getClass()
+ .getDeclaredMethod("insertBranchTransactionDO",
BranchTransactionDO.class);
+ insertMethod.setAccessible(true);
+ insertMethod.invoke(redisTransactionStoreManager, branchDO1);
+ insertMethod.invoke(redisTransactionStoreManager, branchDO2);
+
+ try {
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid(xid);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+ Assertions.assertEquals(xid, result.getData().get(0).getXid());
+ } finally {
+ // Cleanup
+ java.lang.reflect.Method deleteMethod =
redisTransactionStoreManager
+ .getClass()
+ .getDeclaredMethod("deleteBranchTransactionDO",
BranchTransactionDO.class);
+ deleteMethod.setAccessible(true);
+ deleteMethod.invoke(redisTransactionStoreManager, branchDO1);
+ deleteMethod.invoke(redisTransactionStoreManager, branchDO2);
+ }
+ }
+
+ @Test
+ void queryByXidEmptyResultTest() {
+ String xid = "test-non-existent-xid-002";
+
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid(xid);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryByXidBlankXidTest() {
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid("");
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryByXidNullXidTest() {
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid(null);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryByXidWhiteSpaceXidTest() {
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid(" ");
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryByXidSingleResultTest() throws Exception {
+ String xid = "test-console-branch-xid-single";
+
+ BranchTransactionDO branchDO = new BranchTransactionDO();
+ branchDO.setXid(xid);
+ branchDO.setBranchId(999L);
+ branchDO.setResourceGroupId("single-app");
+ branchDO.setTransactionId(9999L);
+ branchDO.setResourceId("jdbc:mysql://localhost:3306/test");
+ branchDO.setBranchType("AT");
+ branchDO.setStatus(1);
+
+ java.lang.reflect.Method insertMethod = redisTransactionStoreManager
+ .getClass()
+ .getDeclaredMethod("insertBranchTransactionDO",
BranchTransactionDO.class);
+ insertMethod.setAccessible(true);
+ insertMethod.invoke(redisTransactionStoreManager, branchDO);
+
+ try {
+ PageResult<BranchSessionVO> result =
branchSessionRedisService.queryByXid(xid);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ Assertions.assertEquals(1, result.getTotal());
+ Assertions.assertEquals(999L,
Long.parseLong(result.getData().get(0).getBranchId()));
+ Assertions.assertEquals("single-app",
result.getData().get(0).getResourceGroupId());
+ } finally {
+ // Cleanup
+ java.lang.reflect.Method deleteMethod =
redisTransactionStoreManager
+ .getClass()
+ .getDeclaredMethod("deleteBranchTransactionDO",
BranchTransactionDO.class);
+ deleteMethod.setAccessible(true);
+ deleteMethod.invoke(redisTransactionStoreManager, branchDO);
+ }
+ }
+}
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImplTest.java
new file mode 100644
index 0000000000..9bd9b34268
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalLockRedisServiceImplTest.java
@@ -0,0 +1,292 @@
+/*
+ * 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.seata.server.console.impl.redis;
+
+import org.apache.seata.common.exception.FrameworkErrorCode;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.common.result.SingleResult;
+import org.apache.seata.core.exception.TransactionException;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.param.GlobalLockParam;
+import org.apache.seata.server.console.entity.vo.GlobalLockVO;
+import org.apache.seata.server.storage.redis.JedisPooledFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
+import redis.clients.jedis.Jedis;
+
+import java.util.Set;
+
+import static
org.apache.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX;
+import static
org.apache.seata.core.constants.RedisKeyConstants.DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX;
+import static org.apache.seata.core.constants.RedisKeyConstants.SPLIT;
+
+/**
+ * Test for GlobalLockRedisServiceImpl
+ * Integration test that requires real Redis instance
+ * Set -DredisCaseEnabled=true to run these tests
+ */
+@EnabledIfSystemProperty(named = "redisCaseEnabled", matches = "true")
+@TestPropertySource(properties = {"lockMode=redis", "sessionMode=file"})
+class GlobalLockRedisServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private GlobalLockRedisServiceImpl globalLockRedisService;
+
+ private Jedis jedis;
+
+ @BeforeEach
+ void setUp() {
+ jedis = JedisPooledFactory.getJedisInstance();
+ cleanupRedisLockData();
+ setupTestLockData();
+ }
+
+ @AfterEach
+ void tearDown() {
+ cleanupRedisLockData();
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+
+ private void setupTestLockData() {
+ String resourceId = "jdbc:mysql://localhost:3306/test";
+ String tableName = "tb_order";
+
+ String rowKey1 = DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX + resourceId +
SPLIT + tableName + SPLIT + "1";
+ jedis.hset(rowKey1, "xid", "127.0.0.1:8091:1001");
+ jedis.hset(rowKey1, "branchId", "2001");
+ jedis.hset(rowKey1, "tableName", tableName);
+ jedis.hset(rowKey1, "pk", "1");
+ jedis.hset(rowKey1, "resourceId", resourceId);
+ jedis.hset(rowKey1, "rowKey", tableName + ":1");
+
+ String rowKey2 = DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX + resourceId +
SPLIT + tableName + SPLIT + "2";
+ jedis.hset(rowKey2, "xid", "127.0.0.1:8091:1001");
+ jedis.hset(rowKey2, "branchId", "2002");
+ jedis.hset(rowKey2, "tableName", tableName);
+ jedis.hset(rowKey2, "pk", "2");
+ jedis.hset(rowKey2, "resourceId", resourceId);
+ jedis.hset(rowKey2, "rowKey", tableName + ":2");
+
+ String globalLockKey = DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX +
"127.0.0.1:8091:1001";
+ jedis.hset(globalLockKey, "2001", rowKey1);
+ jedis.hset(globalLockKey, "2002", rowKey2);
+ }
+
+ private void cleanupRedisLockData() {
+ Set<String> keys = jedis.keys(DEFAULT_REDIS_SEATA_ROW_LOCK_PREFIX +
"*");
+ if (keys != null && !keys.isEmpty()) {
+ jedis.del(keys.toArray(new String[0]));
+ }
+
+ keys = jedis.keys(DEFAULT_REDIS_SEATA_GLOBAL_LOCK_PREFIX + "*");
+ if (keys != null && !keys.isEmpty()) {
+ jedis.del(keys.toArray(new String[0]));
+ }
+ }
+
+ @Test
+ void queryByXidSuccessTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+
+ PageResult<GlobalLockVO> result = globalLockRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+ }
+
+ @Test
+ void queryByXidEmptyResultTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1002");
+
+ PageResult<GlobalLockVO> result = globalLockRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryByRowKeySuccessTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ PageResult<GlobalLockVO> result = globalLockRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ Assertions.assertEquals(1, result.getTotal());
+ Assertions.assertEquals("127.0.0.1:8091:1001",
result.getData().get(0).getXid());
+ Assertions.assertEquals("tb_order",
result.getData().get(0).getTableName());
+ }
+
+ @Test
+ void queryByRowKeyEmptyResultTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setTableName("tb_order");
+ param.setPk("999");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ PageResult<GlobalLockVO> result = globalLockRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryParameterErrorTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setTableName("tb_order");
+
+ PageResult<GlobalLockVO> result = globalLockRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertFalse(result.isSuccess());
+
Assertions.assertEquals(FrameworkErrorCode.ParameterRequired.getErrCode(),
result.getCode());
+ Assertions.assertTrue(result.getMessage().contains("only three
parameters"));
+ }
+
+ @Test
+ void queryInvalidPageNumTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(0);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void queryInvalidPageSizeTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setPageNum(1);
+ param.setPageSize(0);
+ param.setXid("127.0.0.1:8091:1001");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockSuccessTest() throws TransactionException {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("127.0.0.1:8091:1001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ SingleResult<Void> result = globalLockRedisService.deleteLock(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ }
+
+ @Test
+ void deleteLockMissingXidTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingBranchIdTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("127.0.0.1:8091:1001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingTableNameTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("127.0.0.1:8091:1001");
+ param.setBranchId("2001");
+ param.setPk("1");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingPkTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("127.0.0.1:8091:1001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setResourceId("jdbc:mysql://localhost:3306/test");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void deleteLockMissingResourceIdTest() {
+ GlobalLockParam param = new GlobalLockParam();
+ param.setXid("127.0.0.1:8091:1001");
+ param.setBranchId("2001");
+ param.setTableName("tb_order");
+ param.setPk("1");
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalLockRedisService.deleteLock(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+}
diff --git
a/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
new file mode 100644
index 0000000000..a3794dc580
--- /dev/null
+++
b/server/src/test/java/org/apache/seata/server/console/impl/redis/GlobalSessionRedisServiceImplTest.java
@@ -0,0 +1,265 @@
+/*
+ * 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.seata.server.console.impl.redis;
+
+import org.apache.seata.common.exception.FrameworkErrorCode;
+import org.apache.seata.common.result.PageResult;
+import org.apache.seata.core.model.GlobalStatus;
+import org.apache.seata.core.store.GlobalTransactionDO;
+import org.apache.seata.server.BaseSpringBootTest;
+import org.apache.seata.server.console.entity.param.GlobalSessionParam;
+import org.apache.seata.server.console.entity.vo.GlobalSessionVO;
+import org.apache.seata.server.storage.redis.JedisPooledFactory;
+import
org.apache.seata.server.storage.redis.store.RedisTransactionStoreManager;
+import
org.apache.seata.server.storage.redis.store.RedisTransactionStoreManagerFactory;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.condition.EnabledIfSystemProperty;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.test.context.TestPropertySource;
+import redis.clients.jedis.Jedis;
+
+import java.util.Set;
+
+/**
+ * Test for GlobalSessionRedisServiceImpl
+ * Integration test that requires real Redis instance
+ * Set -DredisCaseEnabled=true to run these tests
+ */
+@EnabledIfSystemProperty(named = "redisCaseEnabled", matches = "true")
+@TestPropertySource(properties = {"sessionMode=redis", "lockMode=file"})
+class GlobalSessionRedisServiceImplTest extends BaseSpringBootTest {
+
+ @Autowired
+ private GlobalSessionRedisServiceImpl globalSessionRedisService;
+
+ private RedisTransactionStoreManager redisTransactionStoreManager;
+ private Jedis jedis;
+
+ @BeforeEach
+ void setUp() {
+ redisTransactionStoreManager =
RedisTransactionStoreManagerFactory.getInstance();
+ jedis = JedisPooledFactory.getJedisInstance();
+ cleanupRedisSessionData();
+ }
+
+ @AfterEach
+ void tearDown() {
+ cleanupRedisSessionData();
+ if (jedis != null) {
+ jedis.close();
+ }
+ }
+
+ private void cleanupRedisSessionData() {
+ Set<String> keys = jedis.keys("SEATA_GLOBAL_*");
+ if (keys != null && !keys.isEmpty()) {
+ jedis.del(keys.toArray(new String[0]));
+ }
+
+ keys = jedis.keys("SEATA_STATUS_*");
+ if (keys != null && !keys.isEmpty()) {
+ for (String key : keys) {
+ jedis.del(key);
+ }
+ }
+
+ jedis.del("SEATA_BEGIN_TRANSACTIONS");
+ }
+
+ private void createTestGlobalTransaction(String xid, long transactionId,
GlobalStatus status) throws Exception {
+ GlobalTransactionDO globalTransactionDO = new GlobalTransactionDO();
+ globalTransactionDO.setXid(xid);
+ globalTransactionDO.setTransactionId(transactionId);
+ globalTransactionDO.setStatus(status.getCode());
+ globalTransactionDO.setApplicationId("test-app");
+ globalTransactionDO.setTransactionServiceGroup("default_tx_group");
+ globalTransactionDO.setTransactionName("test-transaction-" +
transactionId);
+ globalTransactionDO.setTimeout(60000);
+ globalTransactionDO.setBeginTime(System.currentTimeMillis());
+
+ java.lang.reflect.Method method = redisTransactionStoreManager
+ .getClass()
+ .getDeclaredMethod("insertGlobalTransactionDO",
GlobalTransactionDO.class);
+ method.setAccessible(true);
+ method.invoke(redisTransactionStoreManager, globalTransactionDO);
+ }
+
+ @Test
+ void queryAllSessionsTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L,
GlobalStatus.Begin);
+ createTestGlobalTransaction("127.0.0.1:8091:1002", 1002L,
GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(2, result.getData().size());
+ Assertions.assertEquals(2, result.getTotal());
+ Assertions.assertEquals(1, result.getPageNum());
+ Assertions.assertEquals(10, result.getPageSize());
+ }
+
+ @Test
+ void queryByXidTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L,
GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ Assertions.assertEquals(1, result.getTotal());
+ Assertions.assertEquals("127.0.0.1:8091:1001",
result.getData().get(0).getXid());
+ }
+
+ @Test
+ void queryByStatusTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L,
GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setStatus(GlobalStatus.Begin.getCode());
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ Assertions.assertEquals(1, result.getTotal());
+ }
+
+ @Test
+ void queryByXidAndStatusTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L,
GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+ param.setStatus(GlobalStatus.Begin.getCode());
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ Assertions.assertEquals(1, result.getTotal());
+ }
+
+ @Test
+ void queryEmptyResultTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:9999");
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+
+ @Test
+ void queryTimeRangeNotSupportedTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setTimeStart(System.currentTimeMillis() - 86400000L);
+ param.setTimeEnd(System.currentTimeMillis());
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertFalse(result.isSuccess());
+
Assertions.assertEquals(FrameworkErrorCode.ParameterRequired.getErrCode(),
result.getCode());
+ Assertions.assertTrue(result.getMessage().contains("not supported
according to time range query"));
+ }
+
+ @Test
+ void queryWithBranchTest() throws Exception {
+ createTestGlobalTransaction("127.0.0.1:8091:1001", 1001L,
GlobalStatus.Begin);
+
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setXid("127.0.0.1:8091:1001");
+ param.setWithBranch(true);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(1, result.getData().size());
+ }
+
+ @Test
+ void queryInvalidPageNumTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(0);
+ param.setPageSize(10);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalSessionRedisService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void queryInvalidPageSizeTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(0);
+
+ IllegalArgumentException exception =
+ Assertions.assertThrows(IllegalArgumentException.class, () ->
globalSessionRedisService.query(param));
+ Assertions.assertNotNull(exception.getMessage());
+ }
+
+ @Test
+ void queryAllSessionsEmptyResultTest() {
+ GlobalSessionParam param = new GlobalSessionParam();
+ param.setPageNum(1);
+ param.setPageSize(10);
+ param.setWithBranch(false);
+
+ PageResult<GlobalSessionVO> result =
globalSessionRedisService.query(param);
+
+ Assertions.assertNotNull(result);
+ Assertions.assertTrue(result.isSuccess());
+ Assertions.assertEquals(0, result.getData().size());
+ Assertions.assertEquals(0, result.getTotal());
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]