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

yowuwuuuw 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 0ce2f9b452 test: add UT for seata-saga-statelang module (#7777)
0ce2f9b452 is described below

commit 0ce2f9b452e0fda03a95a6619cf8066423083651
Author: lokidundun <[email protected]>
AuthorDate: Sun Nov 23 15:33:01 2025 +0800

    test: add UT for seata-saga-statelang module (#7777)
---
 changes/en-us/2.x.md                               |   1 +
 changes/zh-cn/2.x.md                               |   1 +
 .../saga/statelang/domain/ExecutionStatusTest.java |  38 +++++
 .../saga/statelang/domain/RecoverStrategyTest.java |  31 ++++
 .../seata/saga/statelang/domain/StateTypeTest.java |  52 ++++++
 .../domain/impl/AbstractTaskStateTest.java         | 152 +++++++++++++++++
 .../saga/statelang/domain/impl/BaseStateTest.java  | 190 +++++++++++++++++++++
 .../statelang/domain/impl/ChoiceStateImplTest.java | 111 ++++++++++++
 .../CompensateSubStateMachineStateImplTest.java    |  34 ++++
 .../impl/CompensationTriggerStateImplTest.java     |  34 ++++
 .../domain/impl/FailEndStateImplTest.java          |  44 +++++
 .../domain/impl/LoopStartStateImplTest.java        |  32 ++++
 .../domain/impl/ScriptTaskStateImplTest.java       |  46 +++++
 .../domain/impl/ServiceTaskStateImplTest.java      |  59 +++++++
 .../domain/impl/StateInstanceImplTest.java         | 105 ++++++++++++
 .../domain/impl/StateMachineImplTest.java          |  53 ++++++
 .../domain/impl/StateMachineInstanceImplTest.java  |  58 +++++++
 .../domain/impl/SubStateMachineImplTest.java       |  42 +++++
 .../domain/impl/SucceedEndStateImplTest.java       |  32 ++++
 .../statelang/parser/StateParserFactoryTest.java   |  76 +++++++++
 .../parser/utils/DesignerJsonTransformerTest.java  |  73 ++++++++
 .../saga/statelang/parser/utils/IOUtilsTest.java   |  68 ++++++++
 .../saga/statelang/validator/RuleFactoryTest.java  |  44 +++++
 .../validator/ValidationExceptionTest.java         |  78 +++++++++
 .../validator/impl/FiniteTerminationRuleTest.java  |  78 +++++++++
 .../impl/NoRecursiveSubStateMachineRuleTest.java   |  49 ++++++
 .../validator/impl/StateNameExistsRuleTest.java    |  50 ++++++
 27 files changed, 1631 insertions(+)

diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md
index af11f8b06e..d8c4431a83 100644
--- a/changes/en-us/2.x.md
+++ b/changes/en-us/2.x.md
@@ -107,6 +107,7 @@ Add changes here for all PR submitted to the 2.x branch.
 - [[#7757](https://github.com/apache/incubator-seata/pull/7757)] add UT for 
undo module
 - [[#7763](https://github.com/apache/incubator-seata/pull/7763)] add UT for 
RegistryNamingServerProperties and RegistryMetadataProperties
 - [[#7764](https://github.com/apache/incubator-seata/pull/7764)] add some UT 
for server/coordinator module
+- [[#7777](https://github.com/apache/incubator-seata/pull/7777)] add UT for 
seata-saga-statelang module
 - [[#7776](https://github.com/apache/incubator-seata/pull/7776)] add UT for XA 
module
 - [[#7788](https://github.com/apache/incubator-seata/pull/7788)] add some UT 
for rm-datasource module
 - [[#7774](https://github.com/apache/incubator-seata/pull/7774)] add some UT 
for server/console module
diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md
index 5dbdd006ea..f8acfd59d0 100644
--- a/changes/zh-cn/2.x.md
+++ b/changes/zh-cn/2.x.md
@@ -106,6 +106,7 @@
 - [[#7757](https://github.com/apache/incubator-seata/pull/7757)] 为 undo 模块添加单测
 - [[#7763](https://github.com/apache/incubator-seata/pull/7763)] 为 
RegistryNamingServerProperties 和 RegistryMetadataProperties 添加单测
 - [[#7764](https://github.com/apache/incubator-seata/pull/7764)] 为 
server/coordinator 模块添加单测
+- [[#7777](https://github.com/apache/incubator-seata/pull/7777)] 为 
seata-saga-statelang 模块添加单测
 - [[#7776](https://github.com/apache/incubator-seata/pull/7776)] 为 XA 模块添加单测
 - [[#7788](https://github.com/apache/incubator-seata/pull/7788)] 为 
rm-datasource 模块添加单测
 - [[#7774](https://github.com/apache/incubator-seata/pull/7774)] 为 
server/console 模块添加单测
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/ExecutionStatusTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/ExecutionStatusTest.java
new file mode 100644
index 0000000000..5ac629e053
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/ExecutionStatusTest.java
@@ -0,0 +1,38 @@
+/*
+ * 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.saga.statelang.domain;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class ExecutionStatusTest {
+
+    @Test
+    public void testExecutionStatusGetStatusString() {
+        assertEquals("Running", ExecutionStatus.RU.getStatusString());
+        assertEquals("Succeed", ExecutionStatus.SU.getStatusString());
+        assertEquals("Failed", ExecutionStatus.FA.getStatusString());
+        assertEquals("Unknown", ExecutionStatus.UN.getStatusString());
+        assertEquals("Skipped", ExecutionStatus.SK.getStatusString());
+    }
+
+    @Test
+    public void testExecutionStatusConstants() {
+        assertEquals(5, ExecutionStatus.values().length);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/RecoverStrategyTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/RecoverStrategyTest.java
new file mode 100644
index 0000000000..a8ffef882d
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/RecoverStrategyTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.saga.statelang.domain;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class RecoverStrategyTest {
+
+    @Test
+    public void testRecoverStrategyConstants() {
+        assertEquals(2, RecoverStrategy.values().length);
+        assertSame(RecoverStrategy.valueOf("Compensate"), 
RecoverStrategy.Compensate);
+        assertSame(RecoverStrategy.valueOf("Forward"), 
RecoverStrategy.Forward);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/StateTypeTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/StateTypeTest.java
new file mode 100644
index 0000000000..d23d2d41cf
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/StateTypeTest.java
@@ -0,0 +1,52 @@
+/*
+ * 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.saga.statelang.domain;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class StateTypeTest {
+
+    @Test
+    public void testStateTypeValues() {
+        assertEquals("ServiceTask", StateType.SERVICE_TASK.getValue());
+        assertEquals("Choice", StateType.CHOICE.getValue());
+        assertEquals("Fail", StateType.FAIL.getValue());
+        assertEquals("Succeed", StateType.SUCCEED.getValue());
+        assertEquals("CompensationTrigger", 
StateType.COMPENSATION_TRIGGER.getValue());
+        assertEquals("SubStateMachine", 
StateType.SUB_STATE_MACHINE.getValue());
+        assertEquals("CompensateSubMachine", 
StateType.SUB_MACHINE_COMPENSATION.getValue());
+        assertEquals("ScriptTask", StateType.SCRIPT_TASK.getValue());
+        assertEquals("LoopStart", StateType.LOOP_START.getValue());
+    }
+
+    @Test
+    public void testGetStateType() {
+        assertEquals(StateType.SERVICE_TASK, 
StateType.getStateType("ServiceTask"));
+        assertEquals(StateType.CHOICE, StateType.getStateType("choice"));
+        assertEquals(StateType.SCRIPT_TASK, 
StateType.getStateType("SCRIPTTASK"));
+    }
+
+    @Test
+    public void testGetStateTypeWithInvalidValue() {
+        assertThrows(IllegalArgumentException.class, () -> {
+            StateType.getStateType("UnknownType");
+        });
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/AbstractTaskStateTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/AbstractTaskStateTest.java
new file mode 100644
index 0000000000..39f71a3a38
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/AbstractTaskStateTest.java
@@ -0,0 +1,152 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.TaskState;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class AbstractTaskStateTest {
+
+    private static class TestAbstractTaskState extends AbstractTaskState {}
+
+    @Test
+    public void testAbstractTaskStateGettersAndSetters() {
+        AbstractTaskState taskState = new TestAbstractTaskState();
+        List<TaskState.Retry> retries = new ArrayList<>();
+        List<TaskState.ExceptionMatch> catches = new ArrayList<>();
+        List<Object> input = new ArrayList<>();
+        Map<String, Object> output = new HashMap<>();
+        Map<String, String> status = new HashMap<>();
+        List<Object> inputExpressions = new ArrayList<>();
+        Map<String, Object> outputExpressions = new HashMap<>();
+        TaskState.Loop loop = new AbstractTaskState.LoopImpl();
+
+        taskState.setCompensateState("compensateState1");
+        taskState.setForCompensation(true);
+        taskState.setRetry(retries);
+        taskState.setCatches(catches);
+        taskState.setInput(input);
+        taskState.setOutput(output);
+        taskState.setStatus(status);
+        taskState.setPersist(false);
+        taskState.setRetryPersistModeUpdate(true);
+        taskState.setCompensatePersistModeUpdate(false);
+        taskState.setInputExpressions(inputExpressions);
+        taskState.setOutputExpressions(outputExpressions);
+        taskState.setLoop(loop);
+
+        assertEquals("compensateState1", taskState.getCompensateState());
+        assertTrue(taskState.isForCompensation());
+        Assertions.assertSame(retries, taskState.getRetry());
+        Assertions.assertSame(catches, taskState.getCatches());
+        Assertions.assertSame(input, taskState.getInput());
+        Assertions.assertSame(output, taskState.getOutput());
+        Assertions.assertSame(status, taskState.getStatus());
+        Assertions.assertFalse(taskState.isPersist());
+        assertTrue(taskState.isRetryPersistModeUpdate());
+        Assertions.assertFalse(taskState.isCompensatePersistModeUpdate());
+        Assertions.assertSame(inputExpressions, 
taskState.getInputExpressions());
+        Assertions.assertSame(outputExpressions, 
taskState.getOutputExpressions());
+        Assertions.assertSame(loop, taskState.getLoop());
+    }
+
+    @Test
+    public void testSetCompensateState_WithNonBlankValue() {
+        AbstractTaskState taskState = new TestAbstractTaskState();
+        taskState.setForUpdate(false);
+
+        taskState.setCompensateState("compensateState2");
+
+        assertEquals("compensateState2", taskState.getCompensateState());
+        assertTrue(taskState.isForUpdate());
+    }
+
+    @Test
+    public void testSetCompensateState_WithBlankValue() {
+        AbstractTaskState taskState = new TestAbstractTaskState();
+        taskState.setForUpdate(true);
+
+        taskState.setCompensateState("");
+
+        assertEquals("", taskState.getCompensateState());
+        assertTrue(taskState.isForUpdate());
+    }
+
+    @Test
+    public void testRetryImpl() {
+        AbstractTaskState.RetryImpl retry = new AbstractTaskState.RetryImpl();
+        List<String> exceptions = new ArrayList<>();
+        exceptions.add("Exception1");
+        List<Class<? extends Exception>> exceptionClasses = new ArrayList<>();
+        exceptionClasses.add(RuntimeException.class);
+
+        retry.setExceptions(exceptions);
+        retry.setExceptionClasses(exceptionClasses);
+        retry.setIntervalSeconds(1.5);
+        retry.setMaxAttempts(3);
+        retry.setBackoffRate(2.0);
+
+        Assertions.assertSame(exceptions, retry.getExceptions());
+        Assertions.assertSame(exceptionClasses, retry.getExceptionClasses());
+        assertEquals(1.5, retry.getIntervalSeconds());
+        assertEquals(3, retry.getMaxAttempts());
+        assertEquals(2.0, retry.getBackoffRate());
+    }
+
+    @Test
+    public void testExceptionMatchImpl() {
+        AbstractTaskState.ExceptionMatchImpl exceptionMatch = new 
AbstractTaskState.ExceptionMatchImpl();
+        List<String> exceptions = new ArrayList<>();
+        exceptions.add("Exception2");
+        List<Class<? extends Exception>> exceptionClasses = new ArrayList<>();
+        exceptionClasses.add(IllegalArgumentException.class);
+
+        exceptionMatch.setExceptions(exceptions);
+        exceptionMatch.setExceptionClasses(exceptionClasses);
+        exceptionMatch.setNext("nextState");
+
+        Assertions.assertSame(exceptions, exceptionMatch.getExceptions());
+        Assertions.assertSame(exceptionClasses, 
exceptionMatch.getExceptionClasses());
+        assertEquals("nextState", exceptionMatch.getNext());
+    }
+
+    @Test
+    public void testLoopImpl() {
+        AbstractTaskState.LoopImpl loop = new AbstractTaskState.LoopImpl();
+
+        loop.setParallel(5);
+        loop.setCollection("collectionVar");
+        loop.setElementVariableName("element");
+        loop.setElementIndexName("index");
+        loop.setCompletionCondition("${count > 10}");
+
+        assertEquals(5, loop.getParallel());
+        assertEquals("collectionVar", loop.getCollection());
+        assertEquals("element", loop.getElementVariableName());
+        assertEquals("index", loop.getElementIndexName());
+        assertEquals("${count > 10}", loop.getCompletionCondition());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/BaseStateTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/BaseStateTest.java
new file mode 100644
index 0000000000..009c5461cc
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/BaseStateTest.java
@@ -0,0 +1,190 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateMachine;
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+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.assertSame;
+
+public class BaseStateTest {
+    private static class TestBaseState extends BaseState {}
+
+    @Test
+    public void testBaseStateAllProperties() {
+        BaseState baseState = new TestBaseState();
+        StateMachine mockStateMachine = new MockStateMachine();
+        Map<String, Object> extensions = new HashMap<>();
+        extensions.put("timeout", 3000);
+        extensions.put("async", false);
+        StateType testType = StateType.SERVICE_TASK;
+
+        baseState.setName("orderCreateState");
+        baseState.setComment("orderCreateStatus");
+        baseState.setNext("orderPayState");
+        baseState.setExtensions(extensions);
+        baseState.setStateMachine(mockStateMachine);
+        baseState.setType(testType);
+
+        assertEquals("orderCreateState", baseState.getName(), "name can not be 
matched");
+        assertEquals("orderCreateStatus", baseState.getComment(), "comment can 
not be matched");
+        assertEquals("orderPayState", baseState.getNext(), "next can not be 
matched");
+        assertSame(extensions, baseState.getExtensions(), "extensions can not 
be matched");
+        assertEquals(3000, baseState.getExtensions().get("timeout"), 
"extension timeout can not be matched");
+        assertSame(mockStateMachine, baseState.getStateMachine(), 
"stateMachine can not be matched");
+        assertEquals(testType, baseState.getType(), "type can not be matched");
+        assertEquals(StateType.SERVICE_TASK, baseState.getType(), "type should 
be SERVICE_TASK");
+    }
+
+    @Test
+    public void testBaseStateNullProperties() {
+        BaseState baseState = new TestBaseState();
+
+        baseState.setName(null);
+        baseState.setComment(null);
+        baseState.setNext(null);
+        baseState.setExtensions(null);
+        baseState.setStateMachine(null);
+        baseState.setType(null);
+
+        Assertions.assertNull(baseState.getName(), "name should be null");
+        Assertions.assertNull(baseState.getComment(), "comment should be 
null");
+        Assertions.assertNull(baseState.getNext(), "next should be null");
+        Assertions.assertNull(baseState.getExtensions(), "extensions should be 
null");
+        Assertions.assertNull(baseState.getStateMachine(), "stateMachine 
should be null");
+        Assertions.assertNull(baseState.getType(), "type should be null");
+    }
+
+    private static class MockStateMachine implements StateMachine {
+        private String name;
+        private String id;
+
+        @Override
+        public String getName() {
+            return this.name;
+        }
+
+        @Override
+        public String getComment() {
+            return "mockComment";
+        }
+
+        @Override
+        public String getStartState() {
+            return "mockStartState";
+        }
+
+        @Override
+        public void setStartState(String startState) {}
+
+        @Override
+        public String getVersion() {
+            return "1.0";
+        }
+
+        @Override
+        public void setVersion(String version) {}
+
+        @Override
+        public Map<String, org.apache.seata.saga.statelang.domain.State> 
getStates() {
+            return new HashMap<>();
+        }
+
+        @Override
+        public org.apache.seata.saga.statelang.domain.State getState(String 
name) {
+            return null;
+        }
+
+        @Override
+        public String getId() {
+            return this.id;
+        }
+
+        @Override
+        public void setId(String id) {
+            this.id = id;
+        }
+
+        @Override
+        public String getTenantId() {
+            return "mockTenantId";
+        }
+
+        @Override
+        public void setTenantId(String tenantId) {}
+
+        @Override
+        public String getAppName() {
+            return "mockAppName";
+        }
+
+        @Override
+        public String getType() {
+            return "STATE_LANG";
+        }
+
+        @Override
+        public Status getStatus() {
+            return Status.AC;
+        }
+
+        @Override
+        public org.apache.seata.saga.statelang.domain.RecoverStrategy 
getRecoverStrategy() {
+            return 
org.apache.seata.saga.statelang.domain.RecoverStrategy.Compensate;
+        }
+
+        @Override
+        public void 
setRecoverStrategy(org.apache.seata.saga.statelang.domain.RecoverStrategy 
recoverStrategy) {}
+
+        @Override
+        public String getContent() {
+            return "mockContent";
+        }
+
+        @Override
+        public void setContent(String content) {}
+
+        @Override
+        public boolean isPersist() {
+            return true;
+        }
+
+        @Override
+        public java.util.Date getGmtCreate() {
+            return new java.util.Date();
+        }
+
+        @Override
+        public void setGmtCreate(java.util.Date gmtCreate) {}
+
+        @Override
+        public Boolean isRetryPersistModeUpdate() {
+            return true;
+        }
+
+        @Override
+        public Boolean isCompensatePersistModeUpdate() {
+            return false;
+        }
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ChoiceStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ChoiceStateImplTest.java
new file mode 100644
index 0000000000..d013345362
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ChoiceStateImplTest.java
@@ -0,0 +1,111 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.ChoiceState;
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+/**
+ * Unit tests for ChoiceStateImpl
+ */
+public class ChoiceStateImplTest {
+
+    @Test
+    public void testConstructor() {
+        ChoiceStateImpl choiceState = new ChoiceStateImpl();
+        assertEquals(StateType.CHOICE, choiceState.getType());
+    }
+
+    @Test
+    public void testChoices() {
+        ChoiceStateImpl choiceState = new ChoiceStateImpl();
+        List<ChoiceState.Choice> choices = new ArrayList<>();
+        ChoiceState.Choice choice = new ChoiceStateImpl.ChoiceImpl();
+        choices.add(choice);
+
+        choiceState.setChoices(choices);
+        assertNotNull(choiceState.getChoices());
+        assertEquals(1, choiceState.getChoices().size());
+        Assertions.assertSame(choice, choiceState.getChoices().get(0));
+    }
+
+    @Test
+    public void testDefaultChoice() {
+        ChoiceStateImpl choiceState = new ChoiceStateImpl();
+        String defaultChoice = "defaultNextState";
+
+        choiceState.setDefaultChoice(defaultChoice);
+        assertEquals(defaultChoice, choiceState.getDefault());
+    }
+
+    @Test
+    public void testChoiceEvaluators() {
+        ChoiceStateImpl choiceState = new ChoiceStateImpl();
+        Map<Object, String> evaluators = new HashMap<>();
+        Object key = new Object();
+        String value = "nextState1";
+        evaluators.put(key, value);
+
+        choiceState.setChoiceEvaluators(evaluators);
+        assertNotNull(choiceState.getChoiceEvaluators());
+        assertEquals(1, choiceState.getChoiceEvaluators().size());
+        assertEquals(value, choiceState.getChoiceEvaluators().get(key));
+    }
+
+    @Test
+    public void testChoiceImpl() {
+        ChoiceStateImpl.ChoiceImpl choice = new ChoiceStateImpl.ChoiceImpl();
+        String expression = "${a > b}";
+        String next = "nextState2";
+
+        choice.setExpression(expression);
+        choice.setNext(next);
+
+        assertEquals(expression, choice.getExpression());
+        assertEquals(next, choice.getNext());
+    }
+
+    @Test
+    public void testNullValues() {
+        ChoiceStateImpl choiceState = new ChoiceStateImpl();
+
+        choiceState.setChoices(null);
+        Assertions.assertNull(choiceState.getChoices());
+
+        choiceState.setDefaultChoice(null);
+        Assertions.assertNull(choiceState.getDefault());
+
+        choiceState.setChoiceEvaluators(null);
+        Assertions.assertNull(choiceState.getChoiceEvaluators());
+
+        ChoiceStateImpl.ChoiceImpl choice = new ChoiceStateImpl.ChoiceImpl();
+        choice.setExpression(null);
+        choice.setNext(null);
+        Assertions.assertNull(choice.getExpression());
+        Assertions.assertNull(choice.getNext());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImplTest.java
new file mode 100644
index 0000000000..87912b68a1
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensateSubStateMachineStateImplTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for CompensateSubStateMachineStateImpl
+ */
+public class CompensateSubStateMachineStateImplTest {
+
+    @Test
+    public void testCompensateSubStateMachineStateImpl() {
+        // Test constructor sets correct type
+        CompensateSubStateMachineStateImpl compensateState = new 
CompensateSubStateMachineStateImpl();
+        Assertions.assertEquals(StateType.SUB_MACHINE_COMPENSATION, 
compensateState.getType());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensationTriggerStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensationTriggerStateImplTest.java
new file mode 100644
index 0000000000..ff35c5cb3c
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/CompensationTriggerStateImplTest.java
@@ -0,0 +1,34 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for CompensationTriggerStateImpl
+ */
+public class CompensationTriggerStateImplTest {
+
+    @Test
+    public void testCompensationTriggerStateImpl() {
+        // Test constructor sets correct type
+        CompensationTriggerStateImpl triggerState = new 
CompensationTriggerStateImpl();
+        Assertions.assertEquals(StateType.COMPENSATION_TRIGGER, 
triggerState.getType());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/FailEndStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/FailEndStateImplTest.java
new file mode 100644
index 0000000000..ae3508a7f3
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/FailEndStateImplTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for FailEndStateImpl
+ */
+public class FailEndStateImplTest {
+
+    @Test
+    public void testFailEndStateImpl() {
+        // Test constructor sets correct type
+        FailEndStateImpl failEndState = new FailEndStateImpl();
+        Assertions.assertEquals(StateType.FAIL, failEndState.getType());
+
+        // Test error code
+        String errorCode = "ERROR_500";
+        failEndState.setErrorCode(errorCode);
+        Assertions.assertEquals(errorCode, failEndState.getErrorCode());
+
+        // Test message
+        String message = "Operation failed";
+        failEndState.setMessage(message);
+        Assertions.assertEquals(message, failEndState.getMessage());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/LoopStartStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/LoopStartStateImplTest.java
new file mode 100644
index 0000000000..5f63155571
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/LoopStartStateImplTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class LoopStartStateImplTest {
+
+    @Test
+    public void testLoopStartStateImpl() {
+        LoopStartStateImpl loopStartState = new LoopStartStateImpl();
+
+        assertEquals(StateType.LOOP_START, loopStartState.getType(), 
"LoopStartStateImpl should be LOOP_START");
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ScriptTaskStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ScriptTaskStateImplTest.java
new file mode 100644
index 0000000000..3e184f5257
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ScriptTaskStateImplTest.java
@@ -0,0 +1,46 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+public class ScriptTaskStateImplTest {
+
+    @Test
+    public void testScriptTaskStateImplDefault() {
+        ScriptTaskStateImpl scriptTask = new ScriptTaskStateImpl();
+
+        assertEquals(StateType.SCRIPT_TASK, scriptTask.getType(), 
"ScriptTaskStateImpl should be SCRIPT_TASK");
+        assertEquals("groovy", scriptTask.getScriptType(), "default should be 
groovy");
+        assertNull(scriptTask.getScriptContent());
+    }
+
+    @Test
+    public void testScriptTaskStateImplCustom() {
+        ScriptTaskStateImpl scriptTask = new ScriptTaskStateImpl();
+
+        scriptTask.setScriptType("python");
+        scriptTask.setScriptContent("print('hello')");
+
+        assertEquals("python", scriptTask.getScriptType());
+        assertEquals("print('hello')", scriptTask.getScriptContent());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ServiceTaskStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ServiceTaskStateImplTest.java
new file mode 100644
index 0000000000..80d0c92035
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/ServiceTaskStateImplTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class ServiceTaskStateImplTest {
+
+    @Test
+    public void testServiceTaskStateImplProperties() throws 
NoSuchMethodException {
+        ServiceTaskStateImpl serviceTask = new ServiceTaskStateImpl();
+        List<String> paramTypes = Arrays.asList("java.lang.String", "int");
+        Method testMethod = 
getClass().getMethod("testServiceTaskStateImplProperties");
+        Map<Object, String> evaluators = new HashMap<>();
+        evaluators.put("SUCCESS", "nextState");
+
+        serviceTask.setServiceType("Dubbo");
+        serviceTask.setServiceName("OrderService");
+        serviceTask.setServiceMethod("createOrder");
+        serviceTask.setParameterTypes(paramTypes);
+        serviceTask.setMethod(testMethod);
+        serviceTask.setStatusEvaluators(evaluators);
+        serviceTask.setAsync(true);
+
+        assertEquals(StateType.SERVICE_TASK, serviceTask.getType(), 
"ServiceTaskStateImpl should be SERVICE_TASK");
+        assertEquals("Dubbo", serviceTask.getServiceType());
+        assertEquals("OrderService", serviceTask.getServiceName());
+        assertEquals("createOrder", serviceTask.getServiceMethod());
+        assertSame(paramTypes, serviceTask.getParameterTypes());
+        assertSame(testMethod, serviceTask.getMethod());
+        assertSame(evaluators, serviceTask.getStatusEvaluators());
+        Assertions.assertTrue(serviceTask.isAsync());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateInstanceImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateInstanceImplTest.java
new file mode 100644
index 0000000000..22d5e4a558
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateInstanceImplTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.ExecutionStatus;
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+public class StateInstanceImplTest {
+
+    @Test
+    public void testStateInstanceImplProperties() {
+        StateInstanceImpl instance = new StateInstanceImpl();
+        Date now = new Date();
+        Exception testException = new RuntimeException("test");
+        StateInstanceImpl compensationState = new StateInstanceImpl();
+        compensationState.setStatus(ExecutionStatus.SU);
+
+        instance.setId("TEST_ID");
+        instance.setMachineInstanceId("MACHINE_TEST_ID");
+        instance.setName("TEST_STATE");
+        instance.setType(StateType.SERVICE_TASK);
+        instance.setServiceName("TestService");
+        instance.setServiceMethod("testMethod");
+        instance.setServiceType("SpringBean");
+        instance.setBusinessKey("BUSINESS_123");
+        instance.setGmtStarted(now);
+        instance.setGmtUpdated(now);
+        instance.setGmtEnd(now);
+        instance.setForUpdate(true);
+        instance.setException(testException);
+        instance.setSerializedException("serialized_exception");
+        instance.setInputParams("input");
+        instance.setSerializedInputParams("serialized_input");
+        instance.setOutputParams("output");
+        instance.setSerializedOutputParams("serialized_output");
+        instance.setStateIdCompensatedFor("COMPENSATE_FOR_1");
+        instance.setStateIdRetriedFor("RETRY_FOR_1");
+        instance.setCompensationState(compensationState);
+        instance.setIgnoreStatus(true);
+
+        assertEquals("TEST_ID", instance.getId());
+        assertEquals("MACHINE_TEST_ID", instance.getMachineInstanceId());
+        assertEquals("TEST_STATE", instance.getName());
+        assertEquals(StateType.SERVICE_TASK, instance.getType());
+        assertEquals("TestService", instance.getServiceName());
+        assertEquals("testMethod", instance.getServiceMethod());
+        assertEquals("SpringBean", instance.getServiceType());
+        assertEquals("BUSINESS_123", instance.getBusinessKey());
+        assertSame(now, instance.getGmtStarted());
+        assertSame(now, instance.getGmtUpdated());
+        assertSame(now, instance.getGmtEnd());
+        Assertions.assertTrue(instance.isForUpdate());
+        assertSame(testException, instance.getException());
+        assertEquals("serialized_exception", 
instance.getSerializedException());
+        assertEquals("input", instance.getInputParams());
+        assertEquals("serialized_input", instance.getSerializedInputParams());
+        assertEquals("output", instance.getOutputParams());
+        assertEquals("serialized_output", 
instance.getSerializedOutputParams());
+        assertEquals("COMPENSATE_FOR_1", instance.getStateIdCompensatedFor());
+        assertEquals("RETRY_FOR_1", instance.getStateIdRetriedFor());
+        assertSame(compensationState, instance.getCompensationState());
+        Assertions.assertTrue(instance.isIgnoreStatus());
+    }
+
+    @Test
+    public void testIsForCompensation() {
+        StateInstanceImpl instance = new StateInstanceImpl();
+
+        Assertions.assertFalse(instance.isForCompensation());
+
+        instance.setStateIdCompensatedFor("COMPENSATE_FOR_1");
+        Assertions.assertTrue(instance.isForCompensation());
+    }
+
+    @Test
+    public void testGetCompensationStatus() {
+        StateInstanceImpl instance = new StateInstanceImpl();
+
+        StateInstanceImpl compensationState = new StateInstanceImpl();
+        compensationState.setStatus(ExecutionStatus.FA);
+        instance.setCompensationState(compensationState);
+        assertEquals(ExecutionStatus.FA, instance.getCompensationStatus());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineImplTest.java
new file mode 100644
index 0000000000..30effa0527
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineImplTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.State;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class StateMachineImplTest {
+
+    @Test
+    public void testStateMachineImpl() {
+        StateMachineImpl machine = new StateMachineImpl();
+        String testId = "MACHINE_ID_456";
+        String testName = "TestMachine";
+        Date createTime = new Date();
+
+        machine.setId(testId);
+        machine.setName(testName);
+        machine.setGmtCreate(createTime);
+
+        assertEquals(testId, machine.getId());
+        assertEquals(testName, machine.getName());
+        assertEquals(createTime, machine.getGmtCreate());
+
+        State mockState = mock(State.class);
+        String stateName = "State1";
+        machine.putState(stateName, mockState);
+
+        Map<String, State> states = machine.getStates();
+        assertEquals(1, states.size());
+        assertEquals(mockState, machine.getState(stateName));
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineInstanceImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineInstanceImplTest.java
new file mode 100644
index 0000000000..34050f50ce
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/StateMachineInstanceImplTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.ExecutionStatus;
+import org.apache.seata.saga.statelang.domain.StateInstance;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.Date;
+import java.util.Map;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class StateMachineInstanceImplTest {
+
+    @Test
+    public void testStateMachineInstanceImpl() {
+        StateMachineInstanceImpl instance = new StateMachineInstanceImpl();
+        Date now = new Date();
+        String testId = "TEST_ID_123";
+        ExecutionStatus status = ExecutionStatus.SU;
+
+        instance.setId(testId);
+        instance.setGmtStarted(now);
+        instance.setStatus(status);
+        instance.setRunning(true);
+
+        assertEquals(testId, instance.getId());
+        assertEquals(now, instance.getGmtStarted());
+        assertEquals(status, instance.getStatus());
+        Assertions.assertTrue(instance.isRunning());
+
+        StateInstance mockState = mock(StateInstance.class);
+        String stateId = "STATE_1";
+        instance.putStateInstance(stateId, mockState);
+
+        Map<String, StateInstance> stateMap = instance.getStateMap();
+        assertEquals(1, stateMap.size());
+        assertEquals(mockState, stateMap.get(stateId));
+        Assertions.assertTrue(instance.getStateList().contains(mockState));
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SubStateMachineImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SubStateMachineImplTest.java
new file mode 100644
index 0000000000..9ef9e27a35
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SubStateMachineImplTest.java
@@ -0,0 +1,42 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.apache.seata.saga.statelang.domain.TaskState;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.mock;
+
+public class SubStateMachineImplTest {
+
+    @Test
+    public void testSubStateMachineImpl() {
+        SubStateMachineImpl subMachine = new SubStateMachineImpl();
+
+        assertEquals(StateType.SUB_STATE_MACHINE, subMachine.getType());
+
+        String machineName = "TestSubMachine";
+        subMachine.setStateMachineName(machineName);
+        assertEquals(machineName, subMachine.getStateMachineName());
+
+        TaskState mockCompensateState = mock(TaskState.class);
+        subMachine.setCompensateStateObject(mockCompensateState);
+        assertEquals(mockCompensateState, 
subMachine.getCompensateStateObject());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SucceedEndStateImplTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SucceedEndStateImplTest.java
new file mode 100644
index 0000000000..a2a0a67347
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/domain/impl/SucceedEndStateImplTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.saga.statelang.domain.impl;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class SucceedEndStateImplTest {
+
+    @Test
+    public void testSucceedEndStateImpl() {
+        SucceedEndStateImpl state = new SucceedEndStateImpl();
+
+        assertEquals(StateType.SUCCEED, state.getType());
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserFactoryTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserFactoryTest.java
new file mode 100644
index 0000000000..69561a69ed
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserFactoryTest.java
@@ -0,0 +1,76 @@
+/*
+ * 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.saga.statelang.parser;
+
+import org.apache.seata.saga.statelang.domain.StateType;
+import org.apache.seata.saga.statelang.parser.impl.ChoiceStateParser;
+import 
org.apache.seata.saga.statelang.parser.impl.CompensateSubStateMachineStateParser;
+import 
org.apache.seata.saga.statelang.parser.impl.CompensationTriggerStateParser;
+import org.apache.seata.saga.statelang.parser.impl.FailEndStateParser;
+import org.apache.seata.saga.statelang.parser.impl.ScriptTaskStateParser;
+import org.apache.seata.saga.statelang.parser.impl.ServiceTaskStateParser;
+import org.apache.seata.saga.statelang.parser.impl.SubStateMachineParser;
+import org.apache.seata.saga.statelang.parser.impl.SucceedEndStateParser;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * test StateParserFactory
+ */
+public class StateParserFactoryTest {
+
+    @Test
+    public void testGetStateParser_ExistingTypes() {
+        StateParser serviceTaskParser = 
StateParserFactory.getStateParser(StateType.SERVICE_TASK);
+        assertNotNull(serviceTaskParser, "ServiceTaskStateParser should not be 
null");
+        assertTrue(serviceTaskParser instanceof ServiceTaskStateParser, 
"Parser should be ServiceTaskStateParser");
+
+        StateParser choiceParser = 
StateParserFactory.getStateParser(StateType.CHOICE);
+        assertNotNull(choiceParser, "ChoiceStateParser should not be null");
+        assertTrue(choiceParser instanceof ChoiceStateParser, "Parser should 
be ChoiceStateParser");
+
+        StateParser compensationTriggerParser = 
StateParserFactory.getStateParser(StateType.COMPENSATION_TRIGGER);
+        assertNotNull(compensationTriggerParser, 
"CompensationTriggerStateParser should not be null");
+        assertTrue(
+                compensationTriggerParser instanceof 
CompensationTriggerStateParser,
+                "Parser should be CompensationTriggerStateParser");
+
+        StateParser failEndParser = 
StateParserFactory.getStateParser(StateType.FAIL);
+        assertNotNull(failEndParser, "FailEndStateParser should not be null");
+        assertTrue(failEndParser instanceof FailEndStateParser, "Parser should 
be FailEndStateParser");
+
+        StateParser succeedEndParser = 
StateParserFactory.getStateParser(StateType.SUCCEED);
+        assertNotNull(succeedEndParser, "SucceedEndStateParser should not be 
null");
+        assertTrue(succeedEndParser instanceof SucceedEndStateParser, "Parser 
should be SucceedEndStateParser");
+
+        StateParser subStateMachineParser = 
StateParserFactory.getStateParser(StateType.SUB_STATE_MACHINE);
+        assertNotNull(subStateMachineParser, "SubStateMachineParser should not 
be null");
+        assertTrue(subStateMachineParser instanceof SubStateMachineParser, 
"Parser should be SubStateMachineParser");
+
+        StateParser subCompensationParser = 
StateParserFactory.getStateParser(StateType.SUB_MACHINE_COMPENSATION);
+        assertNotNull(subCompensationParser, 
"CompensateSubStateMachineStateParser should not be null");
+        assertTrue(
+                subCompensationParser instanceof 
CompensateSubStateMachineStateParser,
+                "Parser should be CompensateSubStateMachineStateParser");
+
+        StateParser scriptTaskParser = 
StateParserFactory.getStateParser(StateType.SCRIPT_TASK);
+        assertNotNull(scriptTaskParser, "ScriptTaskStateParser should not be 
null");
+        assertTrue(scriptTaskParser instanceof ScriptTaskStateParser, "Parser 
should be ScriptTaskStateParser");
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformerTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformerTest.java
new file mode 100644
index 0000000000..677fe19c29
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/DesignerJsonTransformerTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.saga.statelang.parser.utils;
+
+import org.apache.seata.common.exception.FrameworkException;
+import org.apache.seata.saga.statelang.parser.JsonParser;
+import org.apache.seata.saga.statelang.parser.JsonParserFactory;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashMap;
+import java.util.Map;
+
+public class DesignerJsonTransformerTest {
+
+    @Test
+    public void testToStandardJsonWithDesignerJson() throws IOException {
+        InputStream inputStream = 
getInputStreamByPath("statelang/simple_statemachine.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+        JsonParser jsonParser = JsonParserFactory.getJsonParser("jackson");
+        Map<String, Object> designerJson = jsonParser.parse(json, Map.class, 
true);
+
+        Map<String, Object> standardJson = 
DesignerJsonTransformer.toStandardJson(designerJson);
+
+        Assertions.assertNotNull(standardJson);
+        Assertions.assertTrue(standardJson.containsKey("States"));
+        Assertions.assertTrue(standardJson.containsKey("StartState"));
+        Assertions.assertEquals("simpleTestStateMachine", 
standardJson.get("Name"));
+    }
+
+    @Test
+    public void testToStandardJsonWithNonDesignerJson() {
+        Map<String, Object> normalJson = new HashMap<>();
+        normalJson.put("Name", "normal");
+        normalJson.put("States", new HashMap<>());
+
+        Map<String, Object> result = 
DesignerJsonTransformer.toStandardJson(normalJson);
+
+        Assertions.assertSame(normalJson, result);
+    }
+
+    @Test
+    public void testGenerateTracingGraphJsonWithNullInstance() {
+        JsonParser parser = JsonParserFactory.getJsonParser("jackson");
+        FrameworkException e = Assertions.assertThrows(
+                FrameworkException.class, () -> 
DesignerJsonTransformer.generateTracingGraphJson(null, parser));
+        Assertions.assertEquals("StateMachineInstance is not exits", 
e.getMessage());
+    }
+
+    private InputStream getInputStreamByPath(String path) {
+        ClassLoader classLoader = 
Thread.currentThread().getContextClassLoader();
+        if (classLoader == null) {
+            classLoader = Thread.currentThread().getClass().getClassLoader();
+        }
+        return classLoader.getResourceAsStream(path);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/IOUtilsTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/IOUtilsTest.java
new file mode 100644
index 0000000000..925c56e9dd
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/utils/IOUtilsTest.java
@@ -0,0 +1,68 @@
+/*
+ * 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.saga.statelang.parser.utils;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+
+public class IOUtilsTest {
+
+    @Test
+    public void testToStringWithUtf8() throws IOException {
+        String content = "test the method of toString";
+        ByteArrayInputStream inputStream = new 
ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8));
+
+        String result = IOUtils.toString(inputStream, "UTF-8");
+
+        Assertions.assertEquals(content, result);
+    }
+
+    @Test
+    public void testCopyInputStreamToWriter() throws IOException {
+        String content = "Hello World! This is a test for copy method.";
+        ByteArrayInputStream inputStream = new 
ByteArrayInputStream(content.getBytes());
+        StringWriter writer = new StringWriter();
+
+        IOUtils.copy(inputStream, writer);
+
+        Assertions.assertEquals(content, writer.toString());
+    }
+
+    @Test
+    public void testCopyReaderToWriter() throws IOException {
+        String content = "Test copy from Reader to Writer";
+        ByteArrayInputStream inputStream = new 
ByteArrayInputStream(content.getBytes());
+        StringWriter writer = new StringWriter();
+
+        int count = IOUtils.copy(new java.io.InputStreamReader(inputStream), 
writer);
+
+        Assertions.assertEquals(content.length(), count);
+        Assertions.assertEquals(content, writer.toString());
+    }
+
+    @Test
+    public void testToStringWithEmptyStream() throws IOException {
+        ByteArrayInputStream inputStream = new ByteArrayInputStream(new 
byte[0]);
+        String result = IOUtils.toString(inputStream, "UTF-8");
+        Assertions.assertEquals("", result);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/RuleFactoryTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/RuleFactoryTest.java
new file mode 100644
index 0000000000..f140cefd6c
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/RuleFactoryTest.java
@@ -0,0 +1,44 @@
+/*
+ * 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.saga.statelang.validator;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+public class RuleFactoryTest {
+
+    @Test
+    public void testGetRules() {
+        List<Rule> rules = RuleFactory.getRules();
+        Assertions.assertNotNull(rules);
+        Assertions.assertFalse(rules.isEmpty(), "Rule list should not be 
empty");
+
+        for (Rule rule : rules) {
+            Assertions.assertNotNull(rule.getName(), "Rule name should not be 
null");
+            Assertions.assertFalse(rule.getName().trim().isEmpty(), "Rule name 
should not be empty");
+        }
+    }
+
+    @Test
+    public void testRuleFactorySingleton() {
+        List<Rule> rules1 = RuleFactory.getRules();
+        List<Rule> rules2 = RuleFactory.getRules();
+        Assertions.assertSame(rules1, rules2, "Rule list should be singleton");
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/ValidationExceptionTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/ValidationExceptionTest.java
new file mode 100644
index 0000000000..70b4d90bfe
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/ValidationExceptionTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.saga.statelang.validator;
+
+import org.apache.seata.saga.statelang.domain.StateMachine;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class ValidationExceptionTest {
+
+    @Test
+    public void testExceptionWithMessage() {
+        Rule mockRule = new TestRule("TestRule", "Please check the state 
machine definition");
+        ValidationException exception = new ValidationException(mockRule, 
"Invalid state configuration");
+
+        Assertions.assertEquals(
+                "Rule [TestRule]: Invalid state configuration, hints: Please 
check the state machine definition",
+                exception.getMessage());
+    }
+
+    @Test
+    public void testExceptionWithCause() {
+        Rule mockRule = new TestRule("ErrorRule", null);
+        Throwable cause = new IllegalArgumentException("Invalid parameter");
+        ValidationException exception = new ValidationException(mockRule, 
"Processing failed", cause);
+
+        Assertions.assertEquals("Rule [ErrorRule]: Processing failed", 
exception.getMessage());
+        Assertions.assertSame(cause, exception.getCause());
+        Assertions.assertEquals("Invalid parameter", 
exception.getCause().getMessage());
+    }
+
+    @Test
+    public void testExceptionWithoutHint() {
+        Rule mockRule = new TestRule("NoHintRule", null);
+        ValidationException exception = new ValidationException(mockRule, "No 
hint provided");
+
+        Assertions.assertEquals("Rule [NoHintRule]: No hint provided", 
exception.getMessage());
+    }
+
+    private static class TestRule implements Rule {
+        private final String name;
+        private final String hint;
+
+        public TestRule(String name, String hint) {
+            this.name = name;
+            this.hint = hint;
+        }
+
+        @Override
+        public boolean validate(StateMachine stateMachine) {
+            return false;
+        }
+
+        @Override
+        public String getName() {
+            return name;
+        }
+
+        @Override
+        public String getHint() {
+            return hint;
+        }
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/FiniteTerminationRuleTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/FiniteTerminationRuleTest.java
new file mode 100644
index 0000000000..0d2857d3f0
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/FiniteTerminationRuleTest.java
@@ -0,0 +1,78 @@
+/*
+ * 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.saga.statelang.validator.impl;
+
+import org.apache.seata.saga.statelang.parser.StateMachineParserFactory;
+import org.apache.seata.saga.statelang.parser.utils.IOUtils;
+import org.apache.seata.saga.statelang.validator.ValidationException;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link FiniteTerminationRule}
+ */
+public class FiniteTerminationRuleTest {
+
+    @Test
+    public void testValidFiniteTermination() throws IOException {
+        // Test state machine with valid termination (no infinite loops)
+        InputStream inputStream = 
getInputStreamByPath("statelang/simple_statemachine.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+
+        Assertions.assertDoesNotThrow(() -> {
+            StateMachineParserFactory.getStateMachineParser(null).parse(json);
+        });
+    }
+
+    @Test
+    public void testSingleStateInfiniteLoop() throws IOException {
+        // Test state machine with single state looping (A -> A)
+        InputStream inputStream = 
getInputStreamByPath("statelang/simple_statemachine_with_single_infinite_loop.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+
+        Throwable e = Assertions.assertThrows(ValidationException.class, () -> 
{
+            StateMachineParserFactory.getStateMachineParser(null).parse(json);
+        });
+        Assertions.assertTrue(
+                e.getMessage().contains("infinite loop") && 
e.getMessage().contains("without outgoing flow to end"));
+    }
+
+    @Test
+    public void testMultiStateInfiniteLoop() throws IOException {
+        // Test state machine with multi-state loop (A -> B -> C -> A)
+        InputStream inputStream =
+                
getInputStreamByPath("statelang/simple_statemachine_with_multiple_infinite_loop.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+
+        Throwable e = Assertions.assertThrows(ValidationException.class, () -> 
{
+            StateMachineParserFactory.getStateMachineParser(null).parse(json);
+        });
+        Assertions.assertTrue(
+                e.getMessage().contains("infinite loop") && 
e.getMessage().contains("without outgoing flow to end"));
+    }
+
+    private InputStream getInputStreamByPath(String path) {
+        ClassLoader classLoader = 
Thread.currentThread().getContextClassLoader();
+        if (classLoader == null) {
+            classLoader = Thread.currentThread().getClass().getClassLoader();
+        }
+        return classLoader.getResourceAsStream(path);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRuleTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRuleTest.java
new file mode 100644
index 0000000000..c766ab987f
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/NoRecursiveSubStateMachineRuleTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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.saga.statelang.validator.impl;
+
+import org.apache.seata.saga.statelang.parser.StateMachineParserFactory;
+import org.apache.seata.saga.statelang.parser.utils.IOUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link NoRecursiveSubStateMachineRule}
+ */
+public class NoRecursiveSubStateMachineRuleTest {
+
+    @Test
+    public void testValidSubStateMachine() throws IOException {
+        InputStream inputStream = 
getInputStreamByPath("statelang/simple_statemachine.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+
+        Assertions.assertDoesNotThrow(() -> {
+            StateMachineParserFactory.getStateMachineParser(null).parse(json);
+        });
+    }
+
+    private InputStream getInputStreamByPath(String path) {
+        ClassLoader classLoader = 
Thread.currentThread().getContextClassLoader();
+        if (classLoader == null) {
+            classLoader = Thread.currentThread().getClass().getClassLoader();
+        }
+        return classLoader.getResourceAsStream(path);
+    }
+}
diff --git 
a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/StateNameExistsRuleTest.java
 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/StateNameExistsRuleTest.java
new file mode 100644
index 0000000000..8e7f4ab66a
--- /dev/null
+++ 
b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/validator/impl/StateNameExistsRuleTest.java
@@ -0,0 +1,50 @@
+/*
+ * 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.saga.statelang.validator.impl;
+
+import org.apache.seata.saga.statelang.parser.StateMachineParserFactory;
+import org.apache.seata.saga.statelang.parser.utils.IOUtils;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Unit tests for {@link StateNameExistsRule}
+ */
+public class StateNameExistsRuleTest {
+
+    @Test
+    public void testAllStateNamesExist() throws IOException {
+        // Test state machine with all subsequent states existing
+        InputStream inputStream = 
getInputStreamByPath("statelang/simple_statemachine.json");
+        String json = IOUtils.toString(inputStream, "UTF-8");
+
+        Assertions.assertDoesNotThrow(() -> {
+            StateMachineParserFactory.getStateMachineParser(null).parse(json);
+        });
+    }
+
+    private InputStream getInputStreamByPath(String path) {
+        ClassLoader classLoader = 
Thread.currentThread().getContextClassLoader();
+        if (classLoader == null) {
+            classLoader = Thread.currentThread().getClass().getClassLoader();
+        }
+        return classLoader.getResourceAsStream(path);
+    }
+}


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

Reply via email to