diff --git a/changes/en-us/2.x.md b/changes/en-us/2.x.md index 63d4bd04ef2..fc50bf8fd6f 100644 --- a/changes/en-us/2.x.md +++ b/changes/en-us/2.x.md @@ -29,6 +29,7 @@ Add changes here for all PR submitted to the 2.x branch. ### test: +- [[#6519](https://github.com/apache/incubator-seata/pull/6519)] improve the test case coverage of saga module to 70% Thanks to these contributors for their code commits. Please report an unintended omission. @@ -44,5 +45,7 @@ Thanks to these contributors for their code commits. Please report an unintended - [tanyaofei](https://github.com/tanyaofei) - [wanghongzhou](https://github.com/wanghongzhou) - [ggbocoder](https://github.com/ggbocoder) +- [xjlgod](https://github.com/xjlgod) + Also, we receive many valuable issues, questions and advices from our community. Thanks for you all. \ No newline at end of file diff --git a/changes/zh-cn/2.x.md b/changes/zh-cn/2.x.md index f93b3ee6931..db12f1dc4bd 100644 --- a/changes/zh-cn/2.x.md +++ b/changes/zh-cn/2.x.md @@ -29,6 +29,8 @@ ### security: ### test: +- [[#6519](https://github.com/apache/incubator-seata/pull/6519)] 增加saga模块的测试用例覆盖率 + 非常感谢以下 contributors 的代码贡献。若有无意遗漏,请报告。 @@ -44,6 +46,7 @@ - [tanyaofei](https://github.com/tanyaofei) - [wanghongzhou](https://github.com/wanghongzhou) - [ggbocoder](https://github.com/ggbocoder) +- [xjlgod](https://github.com/xjlgod) 同时,我们收到了社区反馈的很多有价值的issue和建议,非常感谢大家。 \ No newline at end of file diff --git a/saga/seata-saga-engine-store/src/main/java/org/apache/seata/saga/engine/store/db/AbstractStore.java b/saga/seata-saga-engine-store/src/main/java/org/apache/seata/saga/engine/store/db/AbstractStore.java index 638a47345a7..9541144f825 100644 --- a/saga/seata-saga-engine-store/src/main/java/org/apache/seata/saga/engine/store/db/AbstractStore.java +++ b/saga/seata-saga-engine-store/src/main/java/org/apache/seata/saga/engine/store/db/AbstractStore.java @@ -16,7 +16,7 @@ */ package org.apache.seata.saga.engine.store.db; -import org.apache.seata.common.util.BeanUtils; +import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; @@ -25,12 +25,10 @@ import java.util.Arrays; import java.util.List; -import javax.sql.DataSource; - import org.apache.seata.common.exception.StoreException; +import org.apache.seata.common.util.BeanUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - /** * Abstract store * diff --git a/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/pcext/utils/LoopTaskUtils.java b/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/pcext/utils/LoopTaskUtils.java index 37479b65964..e71818f1f1a 100644 --- a/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/pcext/utils/LoopTaskUtils.java +++ b/saga/seata-saga-engine/src/main/java/org/apache/seata/saga/engine/pcext/utils/LoopTaskUtils.java @@ -45,6 +45,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + /** * Loop Task Util * diff --git a/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/handler/DefaultRouterHandlerTest.java b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/handler/DefaultRouterHandlerTest.java new file mode 100644 index 00000000000..47f2b91e025 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/handler/DefaultRouterHandlerTest.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.proctrl.handler; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.seata.common.exception.FrameworkException; +import org.apache.seata.saga.proctrl.ProcessRouter; +import org.apache.seata.saga.proctrl.ProcessType; +import org.apache.seata.saga.proctrl.impl.ProcessContextImpl; +import org.apache.seata.saga.proctrl.mock.MockProcessRouter; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * DefaultRouterHandlerTest + */ +public class DefaultRouterHandlerTest { + @Test + public void testRouteOfFrameworkException() { + ProcessContextImpl context = new ProcessContextImpl(); + DefaultRouterHandler defaultRouterHandler = new DefaultRouterHandler(); + Assertions.assertThrows(FrameworkException.class, () -> defaultRouterHandler.route(context)); + } + + @Test + public void testRouteOfException() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable("exception", new Object()); + DefaultRouterHandler defaultRouterHandler = new DefaultRouterHandler(); + Map processRouters = new HashMap<>(); + ProcessRouter processRouter = new MockProcessRouter(); + processRouters.put(ProcessType.STATE_LANG.getCode(), processRouter); + defaultRouterHandler.setProcessRouters(processRouters); + Assertions.assertThrows(RuntimeException.class, () -> defaultRouterHandler.route(context)); + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/impl/ProcessContextImplTest.java b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/impl/ProcessContextImplTest.java new file mode 100644 index 00000000000..1d457e5b9e6 --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/impl/ProcessContextImplTest.java @@ -0,0 +1,120 @@ +/* + * 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.proctrl.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * ProcessContextImplTest + */ +public class ProcessContextImplTest { + + @Test + public void testGetVariableFromParent() { + ProcessContextImpl context = new ProcessContextImpl(); + ProcessContextImpl parentContext = new ProcessContextImpl(); + parentContext.setVariable("key", "value"); + context.setParent(parentContext); + Assertions.assertEquals("value", context.getVariable("key")); + } + + @Test + public void testSetVariable() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable("key", "value"); + context.setVariable("key", "value1"); + Assertions.assertEquals("value1", context.getVariable("key")); + context.removeVariable("key"); + ProcessContextImpl parentContext = new ProcessContextImpl(); + parentContext.setVariable("key", "value"); + context.setParent(parentContext); + Assertions.assertEquals("value", context.getVariable("key")); + } + + @Test + public void testGetVariables() { + ProcessContextImpl context = new ProcessContextImpl(); + ProcessContextImpl parentContext = new ProcessContextImpl(); + parentContext.setVariable("key", "value"); + context.setParent(parentContext); + Assertions.assertEquals(1, context.getVariables().size()); + } + + @Test + public void testSetVariables() { + ProcessContextImpl context = new ProcessContextImpl(); + Map map = new HashMap<>(); + map.put("key", "value"); + context.setVariables(map); + Assertions.assertEquals(1, context.getVariables().size()); + } + + @Test + public void testGetVariableLocally() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable("key", "value"); + Assertions.assertEquals("value", context.getVariableLocally("key")); + } + + + @Test + public void testSetVariablesLocally() { + ProcessContextImpl context = new ProcessContextImpl(); + Map map = new HashMap<>(); + map.put("key", "value"); + context.setVariablesLocally(map); + Assertions.assertEquals("value", context.getVariableLocally("key")); + } + + @Test + public void testHasVariable() { + ProcessContextImpl context = new ProcessContextImpl(); + Assertions.assertFalse(context.hasVariable("key")); + } + + @Test + public void testRemoveVariable() { + ProcessContextImpl context = new ProcessContextImpl(); + ProcessContextImpl parentContext = new ProcessContextImpl(); + parentContext.setVariable("key", "value"); + context.setParent(parentContext); + context.setVariable("key1", "value1"); + context.removeVariable("key"); + context.removeVariable("key1"); + Assertions.assertEquals(0, context.getVariables().size()); + } + + @Test + public void testRemoveVariableLocally() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable("key", "value"); + context.removeVariableLocally("key"); + Assertions.assertEquals(0, context.getVariables().size()); + } + + @Test + public void testClearLocally() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable("key", "value"); + context.clearLocally(); + Assertions.assertEquals(0, context.getVariables().size()); + } +} \ No newline at end of file diff --git a/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/mock/MockProcessRouter.java b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/mock/MockProcessRouter.java index 966a84dfbc0..97a2459ee79 100644 --- a/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/mock/MockProcessRouter.java +++ b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/mock/MockProcessRouter.java @@ -39,6 +39,9 @@ public Instruction route(ProcessContext context) throws FrameworkException { return null;//end process } } + if (context.hasVariable("exception")) { + throw new RuntimeException("exception"); + } return instruction; } } diff --git a/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/process/impl/CustomizeBusinessProcessorTest.java b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/process/impl/CustomizeBusinessProcessorTest.java new file mode 100644 index 00000000000..b4b42bb034b --- /dev/null +++ b/saga/seata-saga-processctrl/src/test/java/org/apache/seata/saga/proctrl/process/impl/CustomizeBusinessProcessorTest.java @@ -0,0 +1,56 @@ +/* + * 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.proctrl.process.impl; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.seata.common.exception.FrameworkException; +import org.apache.seata.saga.proctrl.ProcessContext; +import org.apache.seata.saga.proctrl.ProcessType; +import org.apache.seata.saga.proctrl.handler.ProcessHandler; +import org.apache.seata.saga.proctrl.handler.RouterHandler; +import org.apache.seata.saga.proctrl.impl.ProcessContextImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * CustomizeBusinessProcessorTest + */ +public class CustomizeBusinessProcessorTest { + + @Test + public void testProcessFail() { + CustomizeBusinessProcessor customizeBusinessProcessor = new CustomizeBusinessProcessor(); + ProcessContext processContext = new ProcessContextImpl(); + processContext.setVariable(ProcessContext.VAR_NAME_PROCESS_TYPE, ProcessType.STATE_LANG); + Map processHandlerMap = new HashMap<>(1); + processHandlerMap.put(ProcessType.STATE_LANG.getCode(), null); + customizeBusinessProcessor.setProcessHandlers(processHandlerMap); + Assertions.assertThrows(FrameworkException.class, () -> customizeBusinessProcessor.process(processContext)); + } + + @Test + public void testRouteFail() { + CustomizeBusinessProcessor customizeBusinessProcessor = new CustomizeBusinessProcessor(); + ProcessContext processContext = new ProcessContextImpl(); + Map routerHandlerMap = new HashMap<>(1); + routerHandlerMap.put(ProcessType.STATE_LANG.getCode(), null); + customizeBusinessProcessor.setRouterHandlers(routerHandlerMap); + Assertions.assertDoesNotThrow(() -> customizeBusinessProcessor.route(processContext)); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/DbStateMachineConfigTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/DbStateMachineConfigTest.java new file mode 100644 index 00000000000..979676f6f6c --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/DbStateMachineConfigTest.java @@ -0,0 +1,61 @@ +/* + * 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.engine.config; + +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.SQLException; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + + +import static org.mockito.Mockito.when; + +/** + * DbStateMachineConfigTest + */ +public class DbStateMachineConfigTest { + @Test + public void testGetDbTypeFromDataSource() throws SQLException { + Connection connection = Mockito.mock(Connection.class); + DatabaseMetaData databaseMetaData = Mockito.mock(DatabaseMetaData.class); + when(connection.getMetaData()).thenReturn(databaseMetaData); + when(databaseMetaData.getDatabaseProductName()).thenReturn("test"); + MockDataSource mockDataSource = new MockDataSource(); + mockDataSource.setConnection(connection); + Assertions.assertEquals(DbStateMachineConfig.getDbTypeFromDataSource(mockDataSource), "test"); + } + + @Test + public void testAfterPropertiesSet() throws Exception { + DbStateMachineConfig dbStateMachineConfig = new DbStateMachineConfig(); + Connection connection = Mockito.mock(Connection.class); + DatabaseMetaData databaseMetaData = Mockito.mock(DatabaseMetaData.class); + when(connection.getMetaData()).thenReturn(databaseMetaData); + when(databaseMetaData.getDatabaseProductName()).thenReturn("test"); + MockDataSource mockDataSource = new MockDataSource(); + mockDataSource.setConnection(connection); + dbStateMachineConfig.setDataSource(mockDataSource); + dbStateMachineConfig.setApplicationId("test"); + dbStateMachineConfig.setTxServiceGroup("test"); + + Assertions.assertDoesNotThrow(dbStateMachineConfig::afterPropertiesSet); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/MockDataSource.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/MockDataSource.java new file mode 100644 index 00000000000..480337df8c8 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/config/MockDataSource.java @@ -0,0 +1,80 @@ +/* + * 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.engine.config; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.util.logging.Logger; + +/** + * MockDataSource + */ +public class MockDataSource implements DataSource { + private Connection connection; + + public void setConnection(Connection connection) { + this.connection = connection; + } + + @Override + public Connection getConnection() throws SQLException { + return connection; + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return null; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + + } + + @Override + public int getLoginTimeout() throws SQLException { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + return null; + } +} diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionFactoryTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionFactoryTest.java new file mode 100644 index 00000000000..c299095e13f --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionFactoryTest.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.engine.expression.spel; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +/** + * SpringELExpressionFactoryTest + */ +public class SpringELExpressionFactoryTest { + @Test + public void testCreateExpression() { + SpringELExpressionFactory factory = new SpringELExpressionFactory(null); + Assertions.assertNotNull(factory.createExpression("'Hello World'.concat('!')")); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionObject.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionObject.java new file mode 100644 index 00000000000..b8bb541e35f --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionObject.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.engine.expression.spel; + +/** + * SpringELExpressionObject + */ +public class SpringELExpressionObject { + private String name; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionTest.java new file mode 100644 index 00000000000..68e09e9e462 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/expression/spel/SpringELExpressionTest.java @@ -0,0 +1,55 @@ +/* + * 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.engine.expression.spel; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.springframework.expression.Expression; +import org.springframework.expression.ExpressionParser; +import org.springframework.expression.spel.standard.SpelExpressionParser; + +/** + * SpringELExpressionTest + */ +public class SpringELExpressionTest { + @Test + public void testGetValue() { + ExpressionParser parser = new SpelExpressionParser(); + Expression defaultExpression = parser.parseExpression("'Hello World'.concat('!')"); + String value = (String) new SpringELExpression(defaultExpression).getValue(null); + Assertions.assertEquals(value, "Hello World!"); + } + + @Test + void testSetValue() { + ExpressionParser parser = new SpelExpressionParser(); + String expression = "name"; + SpringELExpressionObject springELExpressionObject = new SpringELExpressionObject(); + Expression defaultExpression = parser.parseExpression(expression); + SpringELExpression springELExpression = new SpringELExpression(defaultExpression); + springELExpression.setValue("test", springELExpressionObject); + Assertions.assertEquals(springELExpressionObject.getName(), "test"); + } + + @Test + void testGetExpressionString() { + ExpressionParser parser = new SpelExpressionParser(); + Expression defaultExpression = parser.parseExpression("'Hello World'.concat('!')"); + SpringELExpression springELExpression = new SpringELExpression(defaultExpression); + Assertions.assertEquals(springELExpression.getExpressionString(), "'Hello World'.concat('!')"); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/MockService.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/MockService.java new file mode 100644 index 00000000000..030933b499c --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/MockService.java @@ -0,0 +1,40 @@ +/* + * 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.engine.invoker.impl; + +/** + * MockService + */ +public class MockService { + private int times; + + public String mockInvoke(String param) { + return param; + } + + public boolean mockInvoke(boolean param) { + return param; + } + public String mockInvokeRetry(String param) { + times++; + if (times > 2) { + return param; + } + throw new RuntimeException("mockInvokeRetry"); + } +} diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/SpringBeanServiceInvokerTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/SpringBeanServiceInvokerTest.java new file mode 100644 index 00000000000..3d0b4879099 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/invoker/impl/SpringBeanServiceInvokerTest.java @@ -0,0 +1,160 @@ +/* + * 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.engine.invoker.impl; + +import java.util.Collections; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import org.apache.seata.saga.statelang.domain.impl.AbstractTaskState; +import org.apache.seata.saga.statelang.domain.impl.ServiceTaskStateImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; +import org.springframework.context.ApplicationContext; + +import static org.mockito.Mockito.anyString; +import static org.mockito.Mockito.when; + +/** + * SpringBeanServiceInvokerTest + */ +public class SpringBeanServiceInvokerTest { + @Test + public void testInvokeByClassParam() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{"param"}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvoke"); + serviceTaskState.setParameterTypes(Collections.singletonList("java.lang.String")); + MockService mockService = new MockService(); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setThreadPoolExecutor(new ThreadPoolExecutor(1, + 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>())); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + String output = (String) springBeanServiceInvoker.invoke(serviceTaskState, input); + Assertions.assertEquals(output, "param"); + } + + @Test + public void testInvokeByPrimitiveParam() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{false}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvoke"); + serviceTaskState.setParameterTypes(Collections.singletonList("boolean")); + MockService mockService = new MockService(); + serviceTaskState.setMethod(mockService.getClass().getMethod("mockInvoke", boolean.class)); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setThreadPoolExecutor(new ThreadPoolExecutor(1, + 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>())); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + boolean output = (boolean) springBeanServiceInvoker.invoke(serviceTaskState, input); + Assertions.assertEquals(output, false); + } + + @Test + public void testInvokeRetryFailed() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{"param"}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvokeRetry"); + serviceTaskState.setParameterTypes(Collections.singletonList("java.lang.String")); + AbstractTaskState.RetryImpl retry = new AbstractTaskState.RetryImpl(); + retry.setMaxAttempts(3); + retry.setExceptions(Collections.singletonList("java.lang.NullPoint")); + serviceTaskState.setRetry(Collections.singletonList(retry)); + MockService mockService = new MockService(); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setThreadPoolExecutor(new ThreadPoolExecutor(1, + 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>())); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + Assertions.assertThrows(java.lang.RuntimeException.class, () -> springBeanServiceInvoker.invoke(serviceTaskState, input)); + } + + @Test + public void testInvokeRetrySuccess() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{"param"}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvokeRetry"); + serviceTaskState.setParameterTypes(Collections.singletonList("java.lang.String")); + AbstractTaskState.RetryImpl retry = new AbstractTaskState.RetryImpl(); + retry.setMaxAttempts(3); + retry.setExceptions(Collections.singletonList("java.lang.RuntimeException")); + serviceTaskState.setRetry(Collections.singletonList(retry)); + MockService mockService = new MockService(); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setThreadPoolExecutor(new ThreadPoolExecutor(1, + 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>())); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + String output = (String) springBeanServiceInvoker.invoke(serviceTaskState, input); + Assertions.assertEquals(output, "param"); + } + + @Test + public void testInvokeAsync() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{"param"}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvoke"); + serviceTaskState.setParameterTypes(Collections.singletonList("java.lang.String")); + serviceTaskState.setAsync(true); + MockService mockService = new MockService(); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setThreadPoolExecutor(new ThreadPoolExecutor(1, + 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>())); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + String output = (String) springBeanServiceInvoker.invoke(serviceTaskState, input); + Assertions.assertEquals(output, null); + } + + @Test + public void testInvokeAsyncButSync() throws Throwable { + SpringBeanServiceInvoker springBeanServiceInvoker = new SpringBeanServiceInvoker(); + Object[] input = new Object[]{"param"}; + ServiceTaskStateImpl serviceTaskState = new ServiceTaskStateImpl(); + serviceTaskState.setServiceName("mockService"); + serviceTaskState.setServiceMethod("mockInvoke"); + serviceTaskState.setParameterTypes(Collections.singletonList("java.lang.String")); + serviceTaskState.setAsync(true); + MockService mockService = new MockService(); + ApplicationContext applicationContext = Mockito.mock(ApplicationContext.class); + when(applicationContext.getBean(anyString())).thenReturn(mockService); + springBeanServiceInvoker.setApplicationContext(applicationContext); + + String output = (String) springBeanServiceInvoker.invoke(serviceTaskState, input); + Assertions.assertEquals(output, "param"); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/DbAndReportTcStateLogStoreTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/DbAndReportTcStateLogStoreTest.java new file mode 100644 index 00000000000..db6b2dc7754 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/DbAndReportTcStateLogStoreTest.java @@ -0,0 +1,140 @@ +/* + * 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.engine.store.db; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; + +import org.apache.seata.saga.engine.config.DbStateMachineConfig; +import org.apache.seata.saga.engine.sequence.UUIDSeqGenerator; +import org.apache.seata.saga.proctrl.impl.ProcessContextImpl; +import org.apache.seata.saga.statelang.domain.DomainConstants; +import org.apache.seata.saga.statelang.domain.impl.StateInstanceImpl; +import org.apache.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; + +/** + * DbAndReportTcStateLogStoreTest + */ +public class DbAndReportTcStateLogStoreTest { + private DbAndReportTcStateLogStore dbAndReportTcStateLogStore; + + @BeforeEach + public void setUp() { + DbAndReportTcStateLogStore mock = Mockito.spy(DbAndReportTcStateLogStore.class); + dbAndReportTcStateLogStore = mock; + dbAndReportTcStateLogStore.setSeqGenerator(new UUIDSeqGenerator()); + dbAndReportTcStateLogStore.setSagaTransactionalTemplate(new MockSagaTransactionTemplate()); + dbAndReportTcStateLogStore.setTablePrefix("test_"); + Mockito.doReturn(new StateInstanceImpl()).when(mock).selectOne(any(), any(), any(), any()); + Mockito.doReturn(Collections.singletonList(new StateInstanceImpl())).when(mock).selectList(any(), any(), any()); + Mockito.doReturn(1).when(mock).executeUpdate(any(), any(), any()); + Mockito.doReturn(1).when(mock).executeUpdate(any(), any(), any()); + } + + @Test + public void testRecordStateMachineStarted() { + DbAndReportTcStateLogStore dbAndReportTcStateLogStore = new DbAndReportTcStateLogStore(); + StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl(); + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, new DbStateMachineConfig()); + Assertions.assertThrows(NullPointerException.class, + () -> dbAndReportTcStateLogStore.recordStateMachineStarted(stateMachineInstance, context)); + } + + @Test + public void testRecordStateMachineFinished() { + DbAndReportTcStateLogStore dbAndReportTcStateLogStore = new DbAndReportTcStateLogStore(); + StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl(); + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, new DbStateMachineConfig()); + Assertions.assertThrows(NullPointerException.class, + () -> dbAndReportTcStateLogStore.recordStateMachineFinished(stateMachineInstance, context)); + } + + @Test + public void testRecordStateMachineRestarted() { + DbAndReportTcStateLogStore dbAndReportTcStateLogStore = new DbAndReportTcStateLogStore(); + StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl(); + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, new DbStateMachineConfig()); + Assertions.assertThrows(NullPointerException.class, + () -> dbAndReportTcStateLogStore.recordStateMachineRestarted(stateMachineInstance, context)); + } + + @Test + public void testRecordStateStarted() { + DbAndReportTcStateLogStore dbAndReportTcStateLogStore = new DbAndReportTcStateLogStore(); + StateInstanceImpl stateMachineInstance = new StateInstanceImpl(); + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, new DbStateMachineConfig()); + Assertions.assertThrows(NullPointerException.class, + () -> dbAndReportTcStateLogStore.recordStateStarted(stateMachineInstance, context)); + } + + @Test + public void testRecordStateFinished() { + DbAndReportTcStateLogStore dbAndReportTcStateLogStore = new DbAndReportTcStateLogStore(); + StateInstanceImpl stateMachineInstance = new StateInstanceImpl(); + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_CONFIG, new DbStateMachineConfig()); + Assertions.assertThrows(NullPointerException.class, + () -> dbAndReportTcStateLogStore.recordStateFinished(stateMachineInstance, context)); + } + + @Test + public void testGetStateMachineInstance() { + Assertions.assertDoesNotThrow(() -> dbAndReportTcStateLogStore.getStateInstance("test", "test")); + } + + @Test + public void testGetStateMachineInstanceByBusinessKey() { + StateMachineInstanceImpl stateMachineInstance = new StateMachineInstanceImpl(); + stateMachineInstance.setStateMap(new HashMap<>()); + stateMachineInstance.setStateList(new ArrayList<>()); + Mockito.doReturn(stateMachineInstance).when(dbAndReportTcStateLogStore).selectOne(any(), any(), any(), any()); + dbAndReportTcStateLogStore.getStateMachineInstanceByBusinessKey("test", "test"); + } + + @Test + public void testQueryStateMachineInstanceByParentId() { + Assertions.assertDoesNotThrow(() -> dbAndReportTcStateLogStore.queryStateMachineInstanceByParentId("test")); + } + + @Test + public void testGetStateInstance() { + Assertions.assertDoesNotThrow(() -> dbAndReportTcStateLogStore.getStateInstance("test", "test")); + } + + @Test + public void testQueryStateInstanceListByMachineInstanceId() { + Assertions.assertDoesNotThrow(() -> dbAndReportTcStateLogStore.queryStateInstanceListByMachineInstanceId("test")); + } + + @Test + public void testClearUp() { + ProcessContextImpl context = new ProcessContextImpl(); + context.setVariable(DomainConstants.VAR_NAME_STATEMACHINE_INST, new StateMachineInstanceImpl()); + Assertions.assertDoesNotThrow(() -> dbAndReportTcStateLogStore.clearUp(context)); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/MockSagaTransactionTemplate.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/MockSagaTransactionTemplate.java new file mode 100644 index 00000000000..75a27a7b809 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/store/db/MockSagaTransactionTemplate.java @@ -0,0 +1,86 @@ +/* + * 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.engine.store.db; + +import java.util.Random; + +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.saga.engine.tm.MockGlobalTransaction; +import org.apache.seata.saga.engine.tm.SagaTransactionalTemplate; +import org.apache.seata.tm.api.GlobalTransaction; +import org.apache.seata.tm.api.TransactionalExecutor.ExecutionException; +import org.apache.seata.tm.api.transaction.TransactionInfo; + +/** + * MockSagaTransactionTemplate + */ +public class MockSagaTransactionTemplate implements SagaTransactionalTemplate { + + @Override + public void commitTransaction(GlobalTransaction tx) throws ExecutionException { + + } + + @Override + public void rollbackTransaction(GlobalTransaction tx, Throwable ex) throws TransactionException, ExecutionException { + + } + + @Override + public GlobalTransaction beginTransaction(TransactionInfo txInfo) throws ExecutionException { + GlobalTransaction globalTransaction = new MockGlobalTransaction(); + try { + globalTransaction.begin(); + } catch (TransactionException e) { + e.printStackTrace(); + } + return globalTransaction; + } + + @Override + public GlobalTransaction reloadTransaction(String xid) throws ExecutionException, TransactionException { + return new MockGlobalTransaction(xid, GlobalStatus.UnKnown); + } + + @Override + public void reportTransaction(GlobalTransaction tx, GlobalStatus globalStatus) throws ExecutionException { + + } + + @Override + public long branchRegister(String resourceId, String clientId, String xid, String applicationData, String lockKeys) + throws TransactionException { + return new Random().nextLong(); + } + + @Override + public void branchReport(String xid, long branchId, BranchStatus status, String applicationData) throws TransactionException { + + } + + @Override + public void triggerAfterCompletion(GlobalTransaction tx) { + + } + + @Override + public void cleanUp(GlobalTransaction tx) { + + } +} diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/DefaultSagaTransactionalTemplateTest.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/DefaultSagaTransactionalTemplateTest.java new file mode 100644 index 00000000000..4c06b4b9069 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/DefaultSagaTransactionalTemplateTest.java @@ -0,0 +1,122 @@ +/* + * 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.engine.tm; + +import java.util.Collections; + +import org.apache.seata.core.model.BranchStatus; +import org.apache.seata.core.model.BranchType; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.core.model.Resource; +import org.apache.seata.core.model.ResourceManager; +import org.apache.seata.rm.DefaultResourceManager; +import org.apache.seata.tm.api.GlobalTransactionContext; +import org.apache.seata.tm.api.transaction.TransactionHookManager; +import org.apache.seata.tm.api.transaction.TransactionInfo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; +import org.mockito.Mockito; + +import static org.mockito.ArgumentMatchers.any; + +/** + * DefaultSagaTransactionalTemplateTest + */ +public class DefaultSagaTransactionalTemplateTest { + private static SagaTransactionalTemplate sagaTransactionalTemplate; + + @BeforeEach + public void init() { + sagaTransactionalTemplate = new DefaultSagaTransactionalTemplate(); + } + + @Test + public void testCommitTransaction() { + MockGlobalTransaction mockGlobalTransaction = new MockGlobalTransaction(); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.commitTransaction(mockGlobalTransaction)); + } + + @Test + public void testRollbackTransaction() { + MockedStatic enhancedTransactionHookManager = Mockito.mockStatic(TransactionHookManager.class); + enhancedTransactionHookManager.when(TransactionHookManager::getHooks).thenReturn(Collections.singletonList(new MockTransactionHook())); + MockGlobalTransaction mockGlobalTransaction = new MockGlobalTransaction(); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.rollbackTransaction(mockGlobalTransaction, null)); + enhancedTransactionHookManager.close(); + } + + @Test + public void testBeginTransaction() { + MockedStatic enhancedServiceLoader = Mockito.mockStatic(GlobalTransactionContext.class); + enhancedServiceLoader.when(GlobalTransactionContext::getCurrentOrCreate).thenReturn(new MockGlobalTransaction()); + MockedStatic enhancedTransactionHookManager = Mockito.mockStatic(TransactionHookManager.class); + enhancedTransactionHookManager.when(TransactionHookManager::getHooks).thenReturn(Collections.singletonList(new MockTransactionHook())); + TransactionInfo transactionInfo = new TransactionInfo(); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.beginTransaction(transactionInfo)); + enhancedServiceLoader.close(); + enhancedTransactionHookManager.close(); + } + + @Test + public void testReloadTransaction() { + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.reloadTransaction("")); + } + + @Test + public void testReportTransaction() { + MockGlobalTransaction mockGlobalTransaction = new MockGlobalTransaction(); + GlobalStatus globalStatus = GlobalStatus.Committed; + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.reportTransaction(mockGlobalTransaction, globalStatus)); + } + + @Test + public void testBranchRegister() { + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.doNothing().when(resourceManager).registerResource(any(Resource.class)); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.SAGA, resourceManager); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.branchRegister("", + "", "", "", "")); + } + + @Test + public void testBranchReport() { + ResourceManager resourceManager = Mockito.mock(ResourceManager.class); + Mockito.doNothing().when(resourceManager).registerResource(any(Resource.class)); + DefaultResourceManager.get(); + DefaultResourceManager.mockResourceManager(BranchType.SAGA, resourceManager); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.branchReport("", + 0, BranchStatus.Unknown, "")); + } + + @Test + public void testTriggerAfterCompletion() { + MockedStatic enhancedTransactionHookManager = Mockito.mockStatic(TransactionHookManager.class); + enhancedTransactionHookManager.when(TransactionHookManager::getHooks).thenReturn(Collections.singletonList(new MockTransactionHook())); + MockGlobalTransaction mockGlobalTransaction = new MockGlobalTransaction(); + Assertions.assertDoesNotThrow(() -> sagaTransactionalTemplate.triggerAfterCompletion(mockGlobalTransaction)); + enhancedTransactionHookManager.close(); + } + + @Test + public void testCleanUp() { + MockGlobalTransaction mockGlobalTransaction = new MockGlobalTransaction(); + sagaTransactionalTemplate.cleanUp(mockGlobalTransaction); + } +} \ No newline at end of file diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockGlobalTransaction.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockGlobalTransaction.java new file mode 100644 index 00000000000..90a7c8a4076 --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockGlobalTransaction.java @@ -0,0 +1,123 @@ +/* + * 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.engine.tm; + +import org.apache.seata.core.context.RootContext; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.model.GlobalStatus; +import org.apache.seata.saga.engine.sequence.UUIDSeqGenerator; +import org.apache.seata.tm.api.GlobalTransaction; +import org.apache.seata.tm.api.GlobalTransactionRole; +import org.apache.seata.tm.api.transaction.SuspendedResourcesHolder; + +/** + * MockGlobalTransaction + */ +public class MockGlobalTransaction implements GlobalTransaction { + + private String xid; + private GlobalStatus status; + private long createTime; + + private static UUIDSeqGenerator uuidSeqGenerator = new UUIDSeqGenerator(); + + public MockGlobalTransaction() {} + + public MockGlobalTransaction(String xid) { + this.xid = xid; + } + + public MockGlobalTransaction(String xid, GlobalStatus status) { + this.xid = xid; + this.status = status; + } + + @Override + public void begin() throws TransactionException { + begin(60000); + } + + @Override + public void begin(int timeout) throws TransactionException { + this.createTime = System.currentTimeMillis(); + status = GlobalStatus.Begin; + xid = uuidSeqGenerator.generate(null); + RootContext.bind(xid); + } + + @Override + public void begin(int timeout, String name) throws TransactionException { + + } + + @Override + public void commit() throws TransactionException { + + } + + @Override + public void rollback() throws TransactionException { + + } + + @Override + public SuspendedResourcesHolder suspend() throws TransactionException { + return null; + } + + @Override + public SuspendedResourcesHolder suspend(boolean clean) + throws TransactionException { + return null; + } + + @Override + public void resume(SuspendedResourcesHolder suspendedResourcesHolder) + throws TransactionException { + + } + + @Override + public GlobalStatus getStatus() throws TransactionException { + return status; + } + + @Override + public String getXid() { + return xid; + } + + @Override + public void globalReport(GlobalStatus globalStatus) throws TransactionException { + + } + + @Override + public GlobalStatus getLocalStatus() { + return status; + } + + @Override + public GlobalTransactionRole getGlobalTransactionRole() { + return GlobalTransactionRole.Launcher; + } + + @Override + public long getCreateTime() { + return createTime; + } +} diff --git a/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockTransactionHook.java b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockTransactionHook.java new file mode 100644 index 00000000000..c57b73ea6ce --- /dev/null +++ b/saga/seata-saga-spring/src/test/java/org/apache/seata/saga/engine/tm/MockTransactionHook.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.engine.tm; + +import org.apache.seata.tm.api.transaction.TransactionHook; + +/** + * MockTransactionHook + */ +public class MockTransactionHook implements TransactionHook { + @Override + public void beforeBegin() { + + } + + @Override + public void afterBegin() { + + } + + @Override + public void beforeCommit() { + + } + + @Override + public void afterCommit() { + + } + + @Override + public void beforeRollback() { + + } + + @Override + public void afterRollback() { + + } + + @Override + public void afterCompletion() { + + } +} diff --git a/saga/seata-saga-spring/src/test/resources/file.conf b/saga/seata-saga-spring/src/test/resources/file.conf new file mode 100644 index 00000000000..94863827468 --- /dev/null +++ b/saga/seata-saga-spring/src/test/resources/file.conf @@ -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. +# + +transport { + # tcp, unix-domain-socket + type = "TCP" + #NIO, NATIVE + server = "NIO" + #enable heartbeat + heartbeat = true + # the tm client batch send request enable + enableTmClientBatchSendRequest = true + # the rm client batch send request enable + enableRmClientBatchSendRequest = true + #thread factory for netty + threadFactory { + bossThreadPrefix = "NettyBoss" + workerThreadPrefix = "NettyServerNIOWorker" + serverExecutorThread-prefix = "NettyServerBizHandler" + shareBossWorker = false + clientSelectorThreadPrefix = "NettyClientSelector" + clientSelectorThreadSize = 1 + clientWorkerThreadPrefix = "NettyClientWorkerThread" + # netty boss thread size + bossThreadSize = 1 + #auto default pin or 8 + workerThreadSize = "default" + } + shutdown { + # when destroy server, wait seconds + wait = 3 + } + serialization = "seata" + compressor = "none" + + enableRmClientChannelCheckFailFast = false + enableTmClientChannelCheckFailFast = false +} + + +service { + #transaction service group mapping + vgroupMapping.default_tx_group = "default" + vgroupMapping.mock_tx_group = "mock" + #only support when registry.type=file, please don't set multiple addresses + default.grouplist = "127.0.0.1:8091" + mock.grouplist = "127.0.0.1:8099" + #disable seata + disableGlobalTransaction = false +} + +client { + rm { + reportSuccessEnable = false + sagaBranchRegisterEnable = false + sagaJsonParser = jackson + sagaRetryPersistModeUpdate = false + sagaCompensatePersistModeUpdate = false + } + loadBalance { + type = "XID" + virtualNodes = 10 + } +} diff --git a/saga/seata-saga-spring/src/test/resources/registry.conf b/saga/seata-saga-spring/src/test/resources/registry.conf new file mode 100644 index 00000000000..bab6e8ec0ef --- /dev/null +++ b/saga/seata-saga-spring/src/test/resources/registry.conf @@ -0,0 +1,90 @@ +# +# 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. +# + +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3 + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file.conf" + } +} diff --git a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserTests.java b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserTests.java index 45702f40f72..358785d1634 100644 --- a/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserTests.java +++ b/saga/seata-saga-statelang/src/test/java/org/apache/seata/saga/statelang/parser/StateParserTests.java @@ -19,9 +19,13 @@ import java.io.IOException; import java.io.InputStream; import java.util.Date; +import java.util.HashMap; import java.util.Map; +import org.apache.seata.common.util.BeanUtils; import org.apache.seata.saga.statelang.domain.StateMachine; +import org.apache.seata.saga.statelang.domain.StateMachineInstance; +import org.apache.seata.saga.statelang.domain.impl.StateMachineInstanceImpl; import org.apache.seata.saga.statelang.parser.utils.DesignerJsonTransformer; import org.apache.seata.saga.statelang.parser.utils.IOUtils; import org.apache.seata.saga.statelang.validator.ValidationException; @@ -117,6 +121,22 @@ public void testRecursiveSubStateMachine() throws IOException { Assertions.assertTrue(e.getMessage().endsWith("call itself")); } + @Test + public void testGenerateTracingGraphJson() throws Exception { + InputStream inputStream = getInputStreamByPath("statelang/simple_statemachine_with_layout.json"); + String json = IOUtils.toString(inputStream, "UTF-8"); + StateMachine stateMachine = StateMachineParserFactory.getStateMachineParser(null).parse(json); + Map machineMap = BeanUtils.objectToMap(stateMachine); + StateMachineInstance instance = (StateMachineInstance) BeanUtils.mapToObject(machineMap, StateMachineInstanceImpl.class); + Map context = new HashMap<>(); + context.put("test", "test"); + stateMachine.setContent(json); + instance.setStateMachine(stateMachine); + JsonParser jsonParser = JsonParserFactory.getJsonParser("fastjson"); + String graphJson = DesignerJsonTransformer.generateTracingGraphJson(instance, jsonParser); + Assertions.assertNotNull(graphJson); + } + private InputStream getInputStreamByPath(String path) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); if (classLoader == null) {