Repository: tapdata/tapdata (language: python)

Issue:
Release v3.5.14



Discussion / hints:

Fix patch:
diff --git a/iengine/iengine-common/src/main/java/com/tapdata/entity/AppType.java b/iengine/iengine-common/src/main/java/com/tapdata/entity/AppType.java
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/manager/tm/src/main/java/com/tapdata/tm/monitoringlogs/service/MonitoringLogsServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/monitoringlogs/service/MonitoringLogsServiceImpl.java
index 4593a0f088..80c53d5e20 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/monitoringlogs/service/MonitoringLogsServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/monitoringlogs/service/MonitoringLogsServiceImpl.java
@@ -326,7 +326,7 @@ public void startTaskErrorLog(TaskDto taskDto, UserDetail user, Object e, Level
         ;
         String msg;
         if (e instanceof BizException) {
-            msg = MessageUtil.getMessage(((BizException) e).getErrorCode());
+            msg = ((BizException) e).getMessage();
             if(((BizException) e).getErrorCode()!=null){
                 builder.fullErrorCode(((BizException) e).getErrorCode());
             }
diff --git a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
index a3f6427c52..9631c1d56f 100644
--- a/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
+++ b/manager/tm/src/main/java/com/tapdata/tm/task/service/impl/TaskScheduleServiceImpl.java
@@ -1,5 +1,6 @@
 package com.tapdata.tm.task.service.impl;
 
+import cn.hutool.core.bean.BeanUtil;
 import com.alibaba.fastjson.JSON;
 import com.tapdata.manager.common.utils.JsonUtil;
 import com.tapdata.tm.Settings.constant.CategoryEnum;
@@ -183,6 +184,7 @@ public CalculationEngineVo cloudTaskLimitNum(TaskDto taskDto, UserDetail user, b
                 if (stateMachineResult.isOk()) {
                     throw new BizException("Task.ScheduleLimit");
                 }
+
             }
         }
 
@@ -198,4 +200,29 @@ public CalculationEngineVo cloudTaskLimitNum(TaskDto taskDto, UserDetail user, b
         return calculationEngineVo;
     }
 
+    public void handleScheduleLimit(WorkerDto workerDto, UserDetail user) {
+        UserDetail finalUser = user;
+        List<Worker> workerList = workerService.findAvailableAgent(finalUser);
+        if (workerList.size() == 1) {
+            if (workerList.get(0).getProcessId().equals(workerDto.getProcessId())) {
+                throw new BizException("Task.ScheduleLimit");
+            } else {
+                throw new BizException("Task.ManuallyScheduleLimit", workerList.get(0).getProcessId());
+            }
+        }
+        List<String> availableAgent = new ArrayList();
+        workerList.stream().forEach(worker -> {
+            if (!worker.getProcessId().equals(workerDto.getProcessId())) {
+                int runningTaskNum = taskService.runningTaskNum(worker.getProcessId(), finalUser);
+                WorkerDto workerDtoTemp = new WorkerDto();
+                BeanUtil.copyProperties(worker, workerDtoTemp);
+                int limitTaskNumTemp = workerService.getLimitTaskNum(workerDtoTemp, finalUser);
+                if (runningTaskNum < limitTaskNumTemp) {
+                    availableAgent.add(worker.getTcmInfo().getAgentName());
+                }
+            }
+        });
+        throw new BizException("Task.ManuallyScheduleLimit", JSON.toJSONString(availableAgent));
+    }
+
 }
diff --git a/manager/tm/src/main/resources/messages.properties b/manager/tm/src/main/resources/messages.properties
index d57770d0d8..f569b2b905 100644
--- a/manager/tm/src/main/resources/messages.properties
+++ b/manager/tm/src/main/resources/messages.properties
@@ -47,6 +47,7 @@ Task.ImportFormatError=Task import format is incorrect
 Task.nodeRefresh=The task node has been refreshed
 Task.DateProcessConfigInvalid=Time processing node selection time type exception
 Task.ScheduleLimit=The number of tasks that the engine can be called exceeds the limit. Please upgrade your subscription to obtain more tasks
+Task.ManuallyScheduleLimit=The number of tasks that the engine is manually specified by the user can be called exceeds the limit. Please specify another engine {0}
 Task.ResetAgentNotFound=The reset task did not find an available engine, please start/restart the engine and try again
 Task.ResetStatusInvalid=The current status of the task that needs to be reset has been updated, please refresh the page and try again
 LogCollect.ConfigUpdateError=Current conditions do not allow modification
diff --git a/manager/tm/src/main/resources/messages_zh_CN.properties b/manager/tm/src/main/resources/messages_zh_CN.properties
index ea3227ec67..3ca525050c 100644
--- a/manager/tm/src/main/resources/messages_zh_CN.properties
+++ b/manager/tm/src/main/resources/messages_zh_CN.properties
@@ -63,6 +63,7 @@ Task.ImportFormatError=\u4EFB\u52A1\u5BFC\u5165\u683C\u5F0F\u6709\u8BEF
 Task.nodeRefresh=\u4EFB\u52A1\u8282\u70B9\u5DF2\u7ECF\u88AB\u5237\u65B0
 Task.DateProcessConfigInvalid=\u65F6\u95F4\u5904\u7406\u8282\u70B9\u9009\u62E9\u65F6\u95F4\u7C7B\u578B\u5F02\u5E38
 Task.ScheduleLimit=\u5F15\u64CE\u53EF\u4EE5\u88AB\u8C03\u7528\u7684\u4EFB\u52A1\u8D85\u8FC7\u4E86\u9650\u5236\u6570\uFF0C\u8BF7\u5347\u7EA7\u8BA2\u9605\u4EE5\u83B7\u53D6\u66F4\u591A\u4EFB\u52A1\u6570\u91CF
+Task.ManuallyScheduleLimit=\u7528\u6237\u6307\u5b9a\u5f15\u64ce\u53ef\u4ee5\u88ab\u8c03\u7528\u7684\u4efb\u52a1\u8d85\u8fc7\u4e86\u9650\u5236\u6570\uff0c\u8bf7\u66ff\u6362\u5176\u4ed6\u5f15\u64ce {0}
 Task.ResetAgentNotFound=\u91CD\u7F6E\u4EFB\u52A1\u6CA1\u6709\u627E\u5230\u53EF\u7528\u7684\u5F15\u64CE\uFF0C\u8BF7\u542F\u52A8/\u91CD\u542F\u5F15\u64CE\u540E\u518D\u8BD5
 Task.ResetStatusInvalid=\u5F53\u524D\u9700\u8981\u91CD\u7F6E\u7684\u4EFB\u52A1\u72B6\u6001\u88AB\u66F4\u65B0\uFF0C\u8BF7\u5237\u65B0\u9875\u9762\u518D\u8BD5
 LogCollect.ConfigUpdateError=\u5F53\u524D\u6761\u4EF6\u4E0D\u5141\u8BB8\u4FEE\u6539


Test patch:
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
index 83d55270cc..a2a258b2b5 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/data/pdk/HazelcastSourcePdkDataNodeTest.java
@@ -11,15 +11,11 @@
 import com.tapdata.entity.task.context.DataProcessorContext;
 import com.tapdata.entity.task.context.ProcessorBaseContext;
 import com.tapdata.tm.commons.dag.Node;
+import com.tapdata.tm.commons.dag.nodes.TableNode;
 import io.tapdata.aspect.BatchReadFuncAspect;
 import io.tapdata.aspect.SourceStateAspect;
-import io.tapdata.aspect.taskmilestones.SnapshotReadBeginAspect;
-import io.tapdata.aspect.taskmilestones.SnapshotReadEndAspect;
-import io.tapdata.aspect.taskmilestones.SnapshotReadTableBeginAspect;
-import io.tapdata.aspect.taskmilestones.SnapshotReadTableEndAspect;
-import io.tapdata.aspect.taskmilestones.SnapshotReadTableErrorAspect;
+import io.tapdata.aspect.taskmilestones.*;
 import io.tapdata.entity.aspect.AspectInterceptResult;
-import com.tapdata.tm.commons.dag.nodes.TableNode;
 import io.tapdata.entity.codec.filter.TapCodecsFilterManager;
 import io.tapdata.entity.event.TapEvent;
 import io.tapdata.entity.event.ddl.TapDDLEvent;
@@ -32,12 +28,12 @@
 import io.tapdata.entity.schema.TapTable;
 import io.tapdata.entity.schema.type.TapDateTime;
 import io.tapdata.entity.schema.value.DateTime;
-import io.tapdata.pdk.apis.entity.QueryOperator;
 import io.tapdata.error.TaskProcessorExCode_11;
 import io.tapdata.exception.NodeException;
 import io.tapdata.exception.TapCodeException;
 import io.tapdata.observable.logging.ObsLogger;
 import io.tapdata.pdk.apis.context.TapConnectorContext;
+import io.tapdata.pdk.apis.entity.QueryOperator;
 import io.tapdata.pdk.apis.entity.TapTimeForm;
 import io.tapdata.pdk.apis.entity.TapTimeUnit;
 import io.tapdata.pdk.apis.functions.ConnectorFunctions;
@@ -47,17 +43,13 @@
 import io.tapdata.pdk.apis.functions.connector.source.ExecuteCommandFunction;
 import io.tapdata.pdk.apis.functions.connector.target.QueryByAdvanceFilterFunction;
 import io.tapdata.pdk.core.api.ConnectorNode;
-import io.tapdata.schema.TapTableMap;
 import io.tapdata.pdk.core.entity.params.PDKMethodInvoker;
 import io.tapdata.pdk.core.monitor.PDKInvocationMonitor;
 import io.tapdata.pdk.core.utils.CommonUtils;
+import io.tapdata.schema.TapTableMap;
 import lombok.SneakyThrows;
 import org.junit.Assert;
-import org.junit.jupiter.api.Assertions;
-import org.junit.jupiter.api.BeforeEach;
-import org.junit.jupiter.api.DisplayName;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.*;
 import org.mockito.MockedStatic;
 import org.springframework.test.util.ReflectionTestUtils;
 
@@ -73,18 +65,8 @@
 import java.util.concurrent.locks.ReentrantLock;
 
 import static org.junit.jupiter.api.Assertions.*;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.doNothing;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.mockStatic;
-import static org.mockito.Mockito.spy;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
 import static org.mockito.internal.verification.VerificationModeFactory.times;
 
 
@@ -242,6 +224,60 @@ void testIsNotPollingCDC() {
 		}
 	}
 
+	@Test
+	@DisplayName("test QueryOperator ")
+	void testQueryOperator() {
+		String dateTime = "2023-12-20 12:23:20";
+		QueryOperator queryOperator = new QueryOperator("createTime", dateTime, 1);
+		queryOperator(dateTime, queryOperator);
+		Assert.assertTrue(queryOperator.getOriginalValue() == dateTime);
+	}
+
+
+	@Test
+	@DisplayName("test QueryOperator HasOriginalValue ")
+	void testQueryOperatorHasOriginalValue() {
+		String dateTime = "2021-12-20 12:23:24";
+		QueryOperator queryOperator = new QueryOperator("createTime", dateTime, 1);
+		queryOperator.setOriginalValue(dateTime);
+		queryOperator(dateTime, queryOperator);
+		LocalDateTime localDateTime;
+		String datetimeFormat = "yyyy-MM-dd HH:mm:ss";
+		try {
+			localDateTime = LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(datetimeFormat));
+		} catch (Exception e) {
+			throw new RuntimeException("The input string format is incorrect, expected format: " + datetimeFormat + ", actual value: " + dateTime);
+		}
+		ZonedDateTime gmtZonedDateTime = localDateTime.atZone(ZoneId.of("GMT"));
+		DateTime expectedValue = new DateTime(gmtZonedDateTime);
+		Assert.assertTrue(expectedValue.compareTo((DateTime) queryOperator.getValue()) == 0);
+	}
+
+
+	public void queryOperator(String dateTime,QueryOperator queryOperator){
+		List<QueryOperator> conditions = new ArrayList<>();
+
+		conditions.add(queryOperator);
+		TableNode tableNodeTemp =new TableNode();
+		tableNodeTemp.setIsFilter(true);
+		tableNodeTemp.setConditions(conditions);
+		tableNodeTemp.setTableName("test");
+		when(dataProcessorContext.getNode()).thenReturn((Node) tableNodeTemp);
+		TapTable tapTable = new TapTable();
+		LinkedHashMap<String, TapField> nameFieldMap = new LinkedHashMap<>();
+		TapField tapField = new TapField("createTime", "TapDateTime");
+		tapField.setTapType(new TapDateTime());
+		nameFieldMap.put("createTime",tapField);
+		tapTable.setNameFieldMap(nameFieldMap);
+		tapTable.setName("test");
+		tapTable.setId("test");
+		TapTableMap<String, TapTable>  tapTableMap = TapTableMap.create("test",tapTable);
+
+		when(dataProcessorContext.getTapTableMap()).thenReturn(tapTableMap);
+		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode,"batchFilterRead");
+
+	}
+
 	@Nested
 	@DisplayName("test HazelcastSourcePdkDataNode by MergeHazelcastSourcePdkDataNode")
 	class MergeHazelcastSourcePdkDataNodeTest {
@@ -1335,59 +1371,6 @@ void testExceptionButNotSupportDebug() {
 		}
 	}
 
-	@Test
-	@DisplayName("test QueryOperator ")
-	void testQueryOperator() {
-		String dateTime = "2023-12-20 12:23:20";
-		QueryOperator queryOperator = new QueryOperator("createTime", dateTime, 1);
-		queryOperator(dateTime, queryOperator);
-		Assert.assertTrue(queryOperator.getOriginalValue() == dateTime);
-	}
-
-
-	@Test
-	@DisplayName("test QueryOperator HasOriginalValue ")
-	void testQueryOperatorHasOriginalValue() {
-		String dateTime = "2021-12-20 12:23:24";
-		QueryOperator queryOperator = new QueryOperator("createTime", dateTime, 1);
-		queryOperator.setOriginalValue(dateTime);
-		queryOperator(dateTime, queryOperator);
-		LocalDateTime localDateTime;
-		String datetimeFormat = "yyyy-MM-dd HH:mm:ss";
-		try {
-			localDateTime = LocalDateTime.parse(dateTime, DateTimeFormatter.ofPattern(datetimeFormat));
-		} catch (Exception e) {
-			throw new RuntimeException("The input string format is incorrect, expected format: " + datetimeFormat + ", actual value: " + dateTime);
-		}
-		ZonedDateTime gmtZonedDateTime = localDateTime.atZone(ZoneId.of("GMT"));
-		DateTime expectedValue = new DateTime(gmtZonedDateTime);
-		Assert.assertTrue(expectedValue.compareTo((DateTime) queryOperator.getValue()) == 0);
-	}
-
-
-	public void queryOperator(String dateTime,QueryOperator queryOperator){
-		List<QueryOperator> conditions = new ArrayList<>();
-
-		conditions.add(queryOperator);
-		TableNode tableNodeTemp =new TableNode();
-		tableNodeTemp.setIsFilter(true);
-		tableNodeTemp.setConditions(conditions);
-		tableNodeTemp.setTableName("test");
-		when(dataProcessorContext.getNode()).thenReturn((Node) tableNodeTemp);
-		TapTable tapTable = new TapTable();
-		LinkedHashMap<String, TapField> nameFieldMap = new LinkedHashMap<>();
-		TapField tapField = new TapField("createTime", "TapDateTime");
-		tapField.setTapType(new TapDateTime());
-		nameFieldMap.put("createTime",tapField);
-		tapTable.setNameFieldMap(nameFieldMap);
-		tapTable.setName("test");
-		tapTable.setId("test");
-		TapTableMap<String, TapTable>  tapTableMap =TapTableMap.create("test",tapTable);
-
-		when(dataProcessorContext.getTapTableMap()).thenReturn(tapTableMap);
-		ReflectionTestUtils.invokeMethod(hazelcastSourcePdkDataNode,"batchFilterRead");
-
-	}
 	@Nested
 	class timeTransformationTest{
 		@DisplayName("Query the data of the previous day")
diff --git a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
index cff06d1a31..83ff5c7c51 100644
--- a/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
+++ b/iengine/iengine-app/src/test/java/io/tapdata/flow/engine/V2/node/hazelcast/processor/HazelcastMergeNodeTest.java
@@ -76,6 +76,7 @@ void beforeEach() {
 		mergeTableNode = new MergeTableNode();
 		mergeTableNode.setMergeProperties(new ArrayList<>());
 		when(dataProcessorContext.getNode()).thenReturn((Node) mergeTableNode);
+		CommonUtils.setProperty("app_type", "DAAS");
 		hazelcastMergeNode = new HazelcastMergeNode(dataProcessorContext);
 		ReflectionTestUtils.setField(hazelcastMergeNode, "obsLogger", mockObsLogger);
 		ReflectionTestUtils.setField(hazelcastMergeNode, "clientMongoOperator", mockClientMongoOperator);
diff --git a/manager/tm/src/test/java/com/tapdata/tm/message/service/MessageServiceTest.java b/manager/tm/src/test/java/com/tapdata/tm/message/service/MessageServiceTest.java
index 2f78def6aa..a0902d10bc 100644
--- a/manager/tm/src/test/java/com/tapdata/tm/message/service/MessageServiceTest.java
+++ b/manager/tm/src/test/java/com/tapdata/tm/message/service/MessageServiceTest.java
@@ -438,7 +438,7 @@ void testInformUser_SendingEmailByDass() throws InvocationTargetException, Illeg
         verify(mockMailUtils,times(1)).sendHtmlMail(Arrays.asList("test@test.com"),"Hi, username: ", "name", resultClickHref,SystemEnum.SYNC, MsgTypeEnum.CONNECTED);
     }
     @Test
-    void testInformUser_SendingEmailByMessageDto() throws InvocationTargetException, IllegalAccessException{
+    void testInformUser_SendingEmailByMessageDto() throws InvocationTargetException, IllegalAccessException {
         try(MockedStatic<SpringContextHelper> springContextHelperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)){
             springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(UserService.class)).thenReturn(mockUserService);
             Notification notification = new Notification();
@@ -478,7 +478,7 @@ void testInformUser_SendingEmailLimitByMessageDto() throws InvocationTargetExcep
         }
     }
     @Test
-    void testInformUser_SendingEmailBoundaryByMessageDto() throws InvocationTargetException, IllegalAccessException{
+    void testInformUser_SendingEmailBoundaryByMessageDto() throws InvocationTargetException, IllegalAccessException {
         try(MockedStatic<SpringContextHelper> springContextHelperMockedStatic = Mockito.mockStatic(SpringContextHelper.class)){
             springContextHelperMockedStatic.when(()->SpringContextHelper.getBean(UserService.class)).thenReturn(mockUserService);
             Notification notification = new Notification();
@@ -541,7 +541,6 @@ void testInformUser_SendingEmailOnAgentExpiring() throws InvocationTargetExcepti
                     "agent-xxxxxx",null,
                     "The instance (agent-xxxxxx) you subscribed to will expire in 1 days to avoid affecting the running of your tasks. Please renew in time.");
         }
-
     }
     @Test
     void testInformUser_SendingEmailOnAgentExpired() throws InvocationTargetException, IllegalAccessException{
@@ -588,7 +587,6 @@ void testInformUser_SendingEmailOnAgentExpired() throws InvocationTargetExceptio
                     "agent-xxxxxx",null,
                     "The instance (agent-xxxxxx) you subscribed to has expired. Please keep your auto-renewal account with sufficient balance and the system will automatically renew it for you. If the renewal is not completed, please contact customer service for assistance.");
         }
-
     }
     @Test
     void testCheckSending(){
diff --git a/manager/tm/src/test/java/com/tapdata/tm/schedule/ScheduleLimitTest.java b/manager/tm/src/test/java/com/tapdata/tm/schedule/ScheduleLimitTest.java
new file mode 100644
index 0000000000..526b8b092a
--- /dev/null
+++ b/manager/tm/src/test/java/com/tapdata/tm/schedule/ScheduleLimitTest.java
@@ -0,0 +1,180 @@
+package com.tapdata.tm.schedule;
+
+import com.tapdata.tm.base.exception.BizException;
+import com.tapdata.tm.config.security.UserDetail;
+import com.tapdata.tm.task.service.TaskService;
+import com.tapdata.tm.task.service.impl.TaskScheduleServiceImpl;
+import com.tapdata.tm.worker.dto.TcmInfo;
+import com.tapdata.tm.worker.dto.WorkerDto;
+import com.tapdata.tm.worker.entity.Worker;
+import com.tapdata.tm.worker.service.WorkerService;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.springframework.beans.BeanUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.mock;
+
+public class ScheduleLimitTest {
+
+
+    public static WorkerService workerService;
+
+    public static TaskService taskService;
+
+    public static UserDetail mockUserDetail;
+
+    @BeforeAll
+    protected  static void init() {
+        workerService = mock(WorkerService.class);
+        taskService = mock(TaskService.class);
+        mockUserDetail = mock(UserDetail.class);
+
+    }
+    /**
+     * test handleScheduleLimit function
+     * 测试调度数量限制抛出异常
+     */
+    @Test
+    public void testHandleScheduleLimitThrowScheduleLimitException() {
+        // input param
+
+        // mock data
+        Worker worker = getWorker("worker1");
+        List<Worker> listWorker = new ArrayList<>();
+        listWorker.add(worker);
+
+        // use Mockito mock function   return data
+        Mockito.when(workerService.findAvailableAgent(mockUserDetail)).thenReturn(listWorker);
+
+        // input function  param
+        TaskScheduleServiceImpl taskScheduleService = new TaskScheduleServiceImpl();
+        setParamForObject(taskScheduleService, taskService, workerService);
+
+        WorkerDto workerDto = new WorkerDto();
+        BeanUtils.copyProperties(worker, workerDto);
+
+        // execution method
+        Exception exception = assertThrows(BizException.class, () -> {
+            taskScheduleService.handleScheduleLimit(workerDto, mockUserDetail);
+        });
+
+        // actual data
+        String actualData = ((BizException) exception).getErrorCode();
+
+        // expected data
+        String expectedData = "Task.ScheduleLimit";
+
+        // output results
+        assertEquals(expectedData, actualData);
+    }
+
+
+    /**
+     * test handleScheduleLimit function
+     * 测试调度数量限制只有一个引擎且不是当前任务指定的引擎则提示替换引擎
+     */
+    @Test
+    public void testHandleScheduleLimitThrowManuallyScheduleLimitExceptionWithOneEngine() throws BizException {
+        // input param
+
+        // mock data
+        Worker worker = getWorker("worker1");
+        List<Worker> listWorker = new ArrayList<>();
+        listWorker.add(worker);
+
+        WorkerDto workerDto = new WorkerDto();
+        Worker workerTemp = getWorker("worker2");
+        BeanUtils.copyProperties(workerTemp,workerDto);
+
+        // use Mockito mock function   return data
+        Mockito.when(workerService.findAvailableAgent(mockUserDetail)).thenReturn(listWorker);
+
+        // input function  param
+        TaskScheduleServiceImpl taskScheduleService = new TaskScheduleServiceImpl();
+        setParamForObject(taskScheduleService,taskService,workerService);
+
+
+        // execution method
+        Exception exception = assertThrows(BizException.class, () -> {
+            taskScheduleService.handleScheduleLimit(workerDto,mockUserDetail);
+        });
+
+        // actual data
+        String actualData =  ((BizException) exception).getErrorCode();
+
+        // expected data
+        String expectedData = "Task.ManuallyScheduleLimit";
+
+        // output results
+        assertEquals(expectedData,actualData);
+    }
+
+
+    /**
+     * test handleScheduleLimit function
+     * 测试调度数量限制.存在多个引擎且不是当前任务指定的引擎则提示替换引擎
+     */
+    @Test
+    public void testHandleScheduleLimitThrowScheduleLimitExceptionWithManyEngine() throws BizException {
+        // input param
+
+        // mock data
+        Worker worker = getWorker("worker1");
+        Worker workerTemp = getWorker("worker2");
+        workerTemp.setProcessId("worker2");
+        TcmInfo tcmInfo = mock(TcmInfo.class);
+        List<Worker> listWorker = new ArrayList<>();
+        listWorker.add(worker);
+        listWorker.add(workerTemp);
+
+        // use Mockito mock function   return data
+        Mockito.when(tcmInfo.getAgentName()).thenReturn("worker2");
+        Mockito.when(workerService.findAvailableAgent(mockUserDetail)).thenReturn(listWorker);
+
+        // input function  param
+        TaskScheduleServiceImpl taskScheduleService = new TaskScheduleServiceImpl();
+        setParamForObject(taskScheduleService, taskService, workerService);
+        WorkerDto workerDto = new WorkerDto();
+        BeanUtils.copyProperties(workerTemp, workerDto);
+        Mockito.when(workerService.getLimitTaskNum(workerDto,mockUserDetail)).thenReturn(5);
+        Mockito.when(taskService.runningTaskNum(workerTemp.getProcessId(),mockUserDetail)).thenReturn(2);
+
+        // execution method
+        Exception exception = assertThrows(BizException.class, () -> {
+            taskScheduleService.handleScheduleLimit(workerDto, mockUserDetail);
+        });
+
+        // actual data
+        String actualData = ((BizException) exception).getErrorCode();
+
+        // expected data
+        String expectedData = "Task.ManuallyScheduleLimit";
+
+        // output results
+        assertEquals(expectedData, actualData);
+    }
+
+
+    public Worker getWorker(String processId) {
+        Worker worker = new Worker();
+        worker.setProcessId(processId);
+        return worker;
+    }
+
+
+    public void  setParamForObject(TaskScheduleServiceImpl taskScheduleService,
+                                   TaskService taskService,WorkerService workerService){
+
+        taskScheduleService.setTaskService(taskService);
+        taskScheduleService.setWorkerService(workerService);
+
+    }
+
+
+}