This is an automated email from the ASF dual-hosted git repository.

funky-eyes 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 9c235df8f1 test : add integration tx api mock tests (#8123)
9c235df8f1 is described below

commit 9c235df8f17fdb6bd379fa0a0002d1e59b5caa9a
Author: legendpei <[email protected]>
AuthorDate: Tue Jun 2 09:24:13 2026 +0800

    test : add integration tx api mock tests (#8123)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 integration-tx-api/pom.xml                         |   5 +
 .../store/db/CommonFenceStoreDataBaseDAOTest.java  | 198 +++++++++++++++++++++
 .../tx/api/interceptor/ActionContextUtilTest.java  | 173 ++++++++++++++++++
 .../rm/tcc/api/BusinessActionContextTest.java      | 102 +++++++++++
 .../rm/tcc/api/BusinessActionContextUtilTest.java  | 145 +++++++++++++++
 7 files changed, 625 insertions(+)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index 6173486a13..9cdb1687f5 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -97,6 +97,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#8003](https://github.com/apache/incubator-seata/pull/8003)] enhance Nacos 
registry service test coverage
 - [[#7915](https://github.com/apache/incubator-seata/pull/7915)] add unit 
tests for saga-engine module
 - [[#8051](https://github.com/apache/incubator-seata/pull/8051)] isolate 
compatibility tests from transport protocol interference
+- [[#8123](https://github.com/apache/incubator-seata/pull/8123)] add 
integration tx api mock tests
 
 ### refactor:
 
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 7afc5bbfcb..285ccfc0e0 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -101,6 +101,7 @@
 - [[#8003](https://github.com/apache/incubator-seata/pull/8003)] 增强 Nacos 
注册服务测试覆盖率
 - [[#7915](https://github.com/apache/incubator-seata/pull/7915)] 为 saga-engine 
模块添加单元测试
 - [[#8051](https://github.com/apache/incubator-seata/pull/8051)] 
隔离兼容性测试与传输协议的干扰
+- [[#8123](https://github.com/apache/incubator-seata/pull/8123)] 
为integration-tx-api模块添加测试
 
 
 ### refactor:
diff --git a/integration-tx-api/pom.xml b/integration-tx-api/pom.xml
index f5e67b49e5..4620018300 100644
--- a/integration-tx-api/pom.xml
+++ b/integration-tx-api/pom.xml
@@ -66,6 +66,11 @@
             <groupId>net.bytebuddy</groupId>
             <artifactId>byte-buddy</artifactId>
         </dependency>
+        <dependency>
+            <groupId>com.fasterxml.jackson.core</groupId>
+            <artifactId>jackson-databind</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
     
 </project>
diff --git 
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
new file mode 100644
index 0000000000..b1451e7964
--- /dev/null
+++ 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/fence/store/db/CommonFenceStoreDataBaseDAOTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.integration.tx.api.fence.store.db;
+
+import org.apache.seata.common.DefaultValues;
+import org.apache.seata.common.exception.DataAccessException;
+import org.apache.seata.common.exception.StoreException;
+import 
org.apache.seata.integration.tx.api.fence.exception.CommonFenceException;
+import org.apache.seata.integration.tx.api.fence.store.CommonFenceDO;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.SQLIntegrityConstraintViolationException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class CommonFenceStoreDataBaseDAOTest {
+
+    private CommonFenceStoreDataBaseDAO dao;
+
+    @BeforeEach
+    public void setUp() {
+        dao = (CommonFenceStoreDataBaseDAO) 
CommonFenceStoreDataBaseDAO.getInstance();
+        dao.setLogTableName(DefaultValues.DEFAULT_COMMON_FENCE_LOG_TABLE_NAME);
+    }
+
+    @Test
+    public void testQueryCommonFenceDO() throws SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        ResultSet resultSet = mock(ResultSet.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeQuery()).thenReturn(resultSet);
+        when(resultSet.next()).thenReturn(true);
+        when(resultSet.getString("xid")).thenReturn("xid");
+        when(resultSet.getLong("branch_id")).thenReturn(1L);
+        when(resultSet.getInt("status")).thenReturn(2);
+
+        CommonFenceDO fenceDO = dao.queryCommonFenceDO(connection, "xid", 1L);
+
+        assertEquals("xid", fenceDO.getXid());
+        assertEquals(1L, fenceDO.getBranchId());
+        assertEquals(2, fenceDO.getStatus());
+        verify(statement).setString(1, "xid");
+        verify(statement).setLong(2, 1L);
+    }
+
+    @Test
+    public void testQueryCommonFenceDOReturnsNullWhenNoRow() throws 
SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        ResultSet resultSet = mock(ResultSet.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeQuery()).thenReturn(resultSet);
+        when(resultSet.next()).thenReturn(false);
+
+        assertNull(dao.queryCommonFenceDO(connection, "xid", 1L));
+    }
+
+    @Test
+    public void testQueryCommonFenceDOWrapsSqlException() throws SQLException {
+        Connection connection = mock(Connection.class);
+        when(connection.prepareStatement(anyString())).thenThrow(new 
SQLException("failed"));
+
+        assertThrows(DataAccessException.class, () -> 
dao.queryCommonFenceDO(connection, "xid", 1L));
+    }
+
+    @Test
+    public void testQueryEndStatusXidsByDate() throws SQLException {
+        Connection connection = mock(Connection.class);
+        DatabaseMetaData metaData = mock(DatabaseMetaData.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        ResultSet resultSet = mock(ResultSet.class);
+        when(connection.getMetaData()).thenReturn(metaData);
+        
when(metaData.getURL()).thenReturn("jdbc:oracle:thin:@localhost:1521:xe");
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeQuery()).thenReturn(resultSet);
+        when(resultSet.next()).thenReturn(true, true, false);
+        when(resultSet.getString("xid")).thenReturn("xid1", "xid2");
+
+        Set<String> xids = dao.queryEndStatusXidsByDate(connection, new 
Date(1000L), 10);
+
+        assertEquals(2, xids.size());
+        assertTrue(xids.contains("xid1"));
+        assertTrue(xids.contains("xid2"));
+        verify(statement).setTimestamp(1, new Timestamp(1000L));
+        verify(statement).setInt(2, 10);
+    }
+
+    @Test
+    public void testInsertCommonFenceDO() throws SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeUpdate()).thenReturn(1, 0);
+
+        assertTrue(dao.insertCommonFenceDO(connection, newFenceDO()));
+        assertFalse(dao.insertCommonFenceDO(connection, newFenceDO()));
+        verify(statement, times(2)).setString(1, "xid");
+        verify(statement, times(2)).setLong(2, 1L);
+        verify(statement, times(2)).setString(3, "prepare");
+        verify(statement, times(2)).setInt(4, 1);
+    }
+
+    @Test
+    public void testInsertCommonFenceDOWrapsDuplicateKey() throws SQLException 
{
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeUpdate()).thenThrow(new 
SQLIntegrityConstraintViolationException("duplicate"));
+
+        assertThrows(CommonFenceException.class, () -> 
dao.insertCommonFenceDO(connection, newFenceDO()));
+    }
+
+    @Test
+    public void testUpdateAndDeleteCommonFenceDO() throws SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeUpdate()).thenReturn(1, 0, 1);
+
+        assertTrue(dao.updateCommonFenceDO(connection, "xid", 1L, 2, 1));
+        assertFalse(dao.updateCommonFenceDO(connection, "xid", 1L, 2, 1));
+        assertTrue(dao.deleteCommonFenceDO(connection, "xid", 1L));
+
+        verify(statement, times(2)).setInt(1, 2);
+        verify(statement, times(2)).setString(3, "xid");
+        verify(statement, times(2)).setLong(4, 1L);
+        verify(statement, times(2)).setInt(5, 1);
+    }
+
+    @Test
+    public void testDeleteTCCFenceDO() throws SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        when(statement.executeUpdate()).thenReturn(2);
+
+        assertEquals(2, dao.deleteTCCFenceDO(connection, Arrays.asList("xid1", 
"xid2")));
+        verify(statement).setString(1, "xid1");
+        verify(statement).setString(2, "xid2");
+    }
+
+    @Test
+    public void testStoreMethodsWrapSqlException() throws SQLException {
+        Connection connection = mock(Connection.class);
+        PreparedStatement statement = mock(PreparedStatement.class);
+        when(connection.prepareStatement(anyString())).thenReturn(statement);
+        doThrow(new SQLException("failed")).when(statement).executeUpdate();
+
+        assertThrows(StoreException.class, () -> 
dao.insertCommonFenceDO(connection, newFenceDO()));
+        assertThrows(StoreException.class, () -> 
dao.updateCommonFenceDO(connection, "xid", 1L, 2, 1));
+        assertThrows(StoreException.class, () -> 
dao.deleteCommonFenceDO(connection, "xid", 1L));
+        assertThrows(StoreException.class, () -> 
dao.deleteTCCFenceDO(connection, Arrays.asList("xid1", "xid2")));
+    }
+
+    private static CommonFenceDO newFenceDO() {
+        CommonFenceDO fenceDO = new CommonFenceDO();
+        fenceDO.setXid("xid");
+        fenceDO.setBranchId(1L);
+        fenceDO.setActionName("prepare");
+        fenceDO.setStatus(1);
+        return fenceDO;
+    }
+}
diff --git 
a/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtilTest.java
 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtilTest.java
new file mode 100644
index 0000000000..cac02e5c20
--- /dev/null
+++ 
b/integration-tx-api/src/test/java/org/apache/seata/integration/tx/api/interceptor/ActionContextUtilTest.java
@@ -0,0 +1,173 @@
+/*
+ * 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.integration.tx.api.interceptor;
+
+import org.apache.seata.common.exception.FrameworkException;
+import org.apache.seata.rm.tcc.api.BusinessActionContext;
+import org.apache.seata.rm.tcc.api.BusinessActionContextParameter;
+import org.apache.seata.rm.tcc.api.ParamType;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class ActionContextUtilTest {
+
+    @Test
+    public void testGetByIndex() {
+        assertEquals(
+                "first", ActionContextUtil.getByIndex(ParamType.PARAM, 
"names", Collections.singletonList("first"), 0));
+        assertNull(ActionContextUtil.getByIndex(ParamType.PARAM, "names", 
Collections.emptyList(), 0));
+        assertNull(ActionContextUtil.getByIndex(ParamType.PARAM, "names", 
Collections.singletonList("first"), 1));
+        assertEquals("plain", ActionContextUtil.getByIndex(ParamType.FIELD, 
"name", "plain", 0));
+    }
+
+    @Test
+    public void testPutActionContextHandlesValuesAndDetectsChanges() {
+        Map<String, Object> context = new HashMap<>();
+
+        assertFalse(ActionContextUtil.putActionContext(context, "nullValue", 
null));
+        assertTrue(ActionContextUtil.putActionContext(context, "name", 
"seata"));
+        assertFalse(ActionContextUtil.putActionContext(context, "name", 
"seata"));
+
+        assertTrue(ActionContextUtil.putActionContext(context, "payload", new 
Payload("alice", 7)));
+        String payload = (String) context.get("payload");
+        assertTrue(payload.contains("\"name\":\"alice\""));
+        assertTrue(payload.contains("\"count\":7"));
+    }
+
+    @Test
+    public void testPutActionContextWithoutHandle() {
+        Map<String, Object> context = new HashMap<>();
+        Payload payload = new Payload("bob", 9);
+
+        assertTrue(ActionContextUtil.putActionContextWithoutHandle(context, 
"payload", payload));
+        assertEquals(payload, context.get("payload"));
+        assertFalse(ActionContextUtil.putActionContextWithoutHandle(context, 
"payload", payload));
+        assertFalse(ActionContextUtil.putActionContextWithoutHandle(context, 
"payload", null));
+    }
+
+    @Test
+    public void testConvertActionContext() {
+        Payload payload = new Payload("carol", 11);
+
+        assertThrows(
+                IllegalArgumentException.class, () -> 
ActionContextUtil.convertActionContext("count", 1, int.class));
+        assertNull(ActionContextUtil.convertActionContext("payload", null, 
Payload.class));
+        assertEquals(payload, 
ActionContextUtil.convertActionContext("payload", payload, Payload.class));
+        assertEquals("12", ActionContextUtil.convertActionContext("count", 12, 
String.class));
+
+        Payload fromJson =
+                ActionContextUtil.convertActionContext("payload", 
"{\"name\":\"dave\",\"count\":13}", Payload.class);
+        assertEquals("dave", fromJson.getName());
+        assertEquals(13, fromJson.getCount());
+
+        assertThrows(
+                FrameworkException.class,
+                () -> ActionContextUtil.convertActionContext("payload", 
"not-json", Payload.class));
+    }
+
+    @Test
+    public void testLoadParamByAnnotationAndPutToContext() throws 
NoSuchMethodException {
+        BusinessActionContextParameter propertyAnnotation = 
getMethod("withPropertyParam", PropertyParam.class)
+                .getParameters()[0]
+                .getAnnotation(BusinessActionContextParameter.class);
+        Map<String, Object> context = new HashMap<>();
+
+        ActionContextUtil.loadParamByAnnotationAndPutToContext(
+                ParamType.PARAM, "param", new PropertyParam(), 
propertyAnnotation, context);
+
+        assertEquals("fieldValue", context.get("fieldName"));
+
+        BusinessActionContextParameter aliasAnnotation = 
getMethod("withAliasParam", String.class)
+                .getParameters()[0]
+                .getAnnotation(BusinessActionContextParameter.class);
+        ActionContextUtil.loadParamByAnnotationAndPutToContext(
+                ParamType.PARAM, "origin", "value", aliasAnnotation, context);
+
+        assertEquals("value", context.get("aliasName"));
+    }
+
+    @Test
+    public void testGetTwoPhaseArgsRequiresAnnotationForNonContextParameter() 
throws NoSuchMethodException {
+        Method validMethod = getMethod("twoPhase", 
BusinessActionContext.class, String.class);
+        String[] keys = ActionContextUtil.getTwoPhaseArgs(validMethod, 
validMethod.getParameterTypes());
+
+        assertNull(keys[0]);
+        assertEquals("code", keys[1]);
+
+        Method invalidMethod = getMethod("missingAnnotation", 
BusinessActionContext.class, String.class);
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> ActionContextUtil.getTwoPhaseArgs(invalidMethod, 
invalidMethod.getParameterTypes()));
+    }
+
+    private static Method getMethod(String name, Class<?>... parameterTypes) 
throws NoSuchMethodException {
+        return SampleAction.class.getDeclaredMethod(name, parameterTypes);
+    }
+
+    public static class Payload {
+        private String name;
+        private int count;
+
+        public Payload() {}
+
+        public Payload(String name, int count) {
+            this.name = name;
+            this.count = count;
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+
+        public int getCount() {
+            return count;
+        }
+
+        public void setCount(int count) {
+            this.count = count;
+        }
+    }
+
+    public static class PropertyParam {
+        @BusinessActionContextParameter(paramName = "fieldName")
+        private final String field = "fieldValue";
+    }
+
+    public static class SampleAction {
+        public void 
withPropertyParam(@BusinessActionContextParameter(isParamInProperty = true) 
PropertyParam param) {}
+
+        public void withAliasParam(@BusinessActionContextParameter(paramName = 
"aliasName") String param) {}
+
+        public void twoPhase(BusinessActionContext context, 
@BusinessActionContextParameter("code") String code) {}
+
+        public void missingAnnotation(BusinessActionContext context, String 
code) {}
+    }
+}
diff --git 
a/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextTest.java
 
b/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextTest.java
new file mode 100644
index 0000000000..7242540557
--- /dev/null
+++ 
b/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.rm.tcc.api;
+
+import org.apache.seata.core.model.BranchType;
+import org.junit.jupiter.api.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class BusinessActionContextTest {
+
+    @Test
+    public void testBranchIdAccessors() {
+        BusinessActionContext context = new BusinessActionContext();
+
+        assertEquals(-1, context.getBranchId());
+
+        context.setBranchId(10L);
+        assertEquals(10L, context.getBranchId());
+
+        context.setBranchId("11");
+        assertEquals(11L, context.getBranchId());
+    }
+
+    @Test
+    @SuppressWarnings("deprecation")
+    public void testAddActionContextUpdatesFlagOnlyWhenChanged() {
+        Map<String, Object> actionContext = new HashMap<>();
+        BusinessActionContext context = new BusinessActionContext("xid", "1", 
actionContext);
+
+        assertFalse(context.addActionContext("name", null));
+        assertNull(context.getUpdated());
+
+        assertTrue(context.addActionContext("name", "seata"));
+        assertTrue(context.getUpdated());
+        context.setUpdated(null);
+
+        assertFalse(context.addActionContext("name", "seata"));
+        assertNull(context.getUpdated());
+    }
+
+    @Test
+    public void testGetActionContextWithTargetClass() {
+        Map<String, Object> actionContext = new HashMap<>();
+        actionContext.put("count", 3);
+        actionContext.put("payload", "{\"name\":\"payload-name\"}");
+        BusinessActionContext context = new BusinessActionContext("xid", "2", 
actionContext);
+
+        assertEquals(3, context.getActionContext("count", Integer.class));
+        assertEquals(
+                "payload-name",
+                context.getActionContext("payload", Payload.class).getName());
+    }
+
+    @Test
+    public void testToStringIncludesCoreFields() {
+        BusinessActionContext context = new BusinessActionContext("xid", "3", 
new HashMap<>());
+        context.setActionName("prepare");
+        context.setDelayReport(true);
+        context.setUpdated(false);
+        context.setBranchType(BranchType.TCC);
+
+        String value = context.toString();
+
+        assertTrue(value.contains("xid:xid"));
+        assertTrue(value.contains("branch_Id:3"));
+        assertTrue(value.contains("action_name:prepare"));
+        assertTrue(value.contains("branch_type:TCC"));
+    }
+
+    public static class Payload {
+        private String name;
+
+        public String getName() {
+            return name;
+        }
+
+        public void setName(String name) {
+            this.name = name;
+        }
+    }
+}
diff --git 
a/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextUtilTest.java
 
b/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextUtilTest.java
new file mode 100644
index 0000000000..ccf1be85c4
--- /dev/null
+++ 
b/integration-tx-api/src/test/java/org/apache/seata/rm/tcc/api/BusinessActionContextUtilTest.java
@@ -0,0 +1,145 @@
+/*
+ * 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.rm.tcc.api;
+
+import org.apache.seata.common.Constants;
+import org.apache.seata.common.exception.FrameworkException;
+import org.apache.seata.core.exception.TransactionException;
+import org.apache.seata.core.model.BranchStatus;
+import org.apache.seata.core.model.BranchType;
+import org.apache.seata.rm.DefaultResourceManager;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.MockedStatic;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.mockStatic;
+import static org.mockito.Mockito.verify;
+
+public class BusinessActionContextUtilTest {
+
+    @AfterEach
+    public void tearDown() {
+        BusinessActionContextUtil.clear();
+    }
+
+    @Test
+    public void testContextHolder() {
+        BusinessActionContext context = new BusinessActionContext();
+
+        BusinessActionContextUtil.setContext(context);
+        assertSame(context, BusinessActionContextUtil.getContext());
+
+        BusinessActionContextUtil.clear();
+        assertNull(BusinessActionContextUtil.getContext());
+    }
+
+    @Test
+    public void testAddContextReturnsFalseForEmptyOrDelayedContext() {
+        
assertFalse(BusinessActionContextUtil.addContext(Collections.emptyMap()));
+        assertFalse(BusinessActionContextUtil.addContext("key", null));
+
+        BusinessActionContext context = newActionContext();
+        context.setDelayReport(true);
+        BusinessActionContextUtil.setContext(context);
+
+        assertFalse(BusinessActionContextUtil.addContext("name", "seata"));
+        assertTrue(context.getUpdated());
+        assertEquals("seata", context.getActionContext().get("name"));
+    }
+
+    @Test
+    public void testAddContextReportsImmediatelyWhenChanged() throws 
TransactionException {
+        BusinessActionContext context = newActionContext();
+        BusinessActionContextUtil.setContext(context);
+        DefaultResourceManager resourceManager = 
mock(DefaultResourceManager.class);
+
+        try (MockedStatic<DefaultResourceManager> mocked = 
mockStatic(DefaultResourceManager.class)) {
+            
mocked.when(DefaultResourceManager::get).thenReturn(resourceManager);
+
+            assertTrue(BusinessActionContextUtil.addContext("name", "seata"));
+        }
+
+        assertNull(context.getUpdated());
+        verify(resourceManager)
+                .branchReport(eq(BranchType.TCC), eq("xid"), eq(1L), 
eq(BranchStatus.Registered), anyString());
+    }
+
+    @Test
+    public void testReportContextSkipsWhenNotUpdated() throws 
TransactionException {
+        BusinessActionContext context = newActionContext();
+        DefaultResourceManager resourceManager = 
mock(DefaultResourceManager.class);
+
+        try (MockedStatic<DefaultResourceManager> mocked = 
mockStatic(DefaultResourceManager.class)) {
+            
mocked.when(DefaultResourceManager::get).thenReturn(resourceManager);
+
+            assertFalse(BusinessActionContextUtil.reportContext(context));
+        }
+    }
+
+    @Test
+    public void testReportContextWrapsTransactionException() throws 
TransactionException {
+        BusinessActionContext context = newActionContext();
+        context.setUpdated(true);
+        DefaultResourceManager resourceManager = 
mock(DefaultResourceManager.class);
+
+        try (MockedStatic<DefaultResourceManager> mocked = 
mockStatic(DefaultResourceManager.class)) {
+            
mocked.when(DefaultResourceManager::get).thenReturn(resourceManager);
+            org.mockito.Mockito.doThrow(new TransactionException("failed"))
+                    .when(resourceManager)
+                    .branchReport(eq(BranchType.TCC), eq("xid"), eq(1L), 
eq(BranchStatus.Registered), anyString());
+
+            assertThrows(FrameworkException.class, () -> 
BusinessActionContextUtil.reportContext(context));
+        }
+    }
+
+    @Test
+    public void testGetBusinessActionContext() {
+        String applicationData = "{\"" + Constants.TX_ACTION_CONTEXT + 
"\":{\"name\":\"seata\"}}";
+
+        BusinessActionContext context =
+                BusinessActionContextUtil.getBusinessActionContext("xid", 2L, 
"prepare", applicationData);
+
+        assertEquals("xid", context.getXid());
+        assertEquals(2L, context.getBranchId());
+        assertEquals("prepare", context.getActionName());
+        assertEquals("seata", context.getActionContext("name"));
+
+        BusinessActionContext emptyContext =
+                BusinessActionContextUtil.getBusinessActionContext("xid", 3L, 
"prepare", "");
+        assertTrue(emptyContext.getActionContext().isEmpty());
+    }
+
+    private static BusinessActionContext newActionContext() {
+        Map<String, Object> actionContext = new HashMap<>();
+        BusinessActionContext context = new BusinessActionContext("xid", "1", 
actionContext);
+        context.setBranchType(BranchType.TCC);
+        return context;
+    }
+}


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to